Update Feature Flags to use Firebase
This commit is contained in:
parent
460e92bee2
commit
543adc0d68
13 changed files with 126 additions and 39 deletions
|
@ -488,8 +488,8 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
||||||
val isNotInstant = !instantAppManager.isEnabled(applicationContext)
|
val isNotInstant = !instantAppManager.isEnabled(applicationContext)
|
||||||
findItem(R.id.share_now).isVisible = isNotInstant
|
findItem(R.id.share_now).isVisible = isNotInstant
|
||||||
findItem(R.id.remove_ads).isVisible = !preferencesRepository.isPremiumEnabled() && isNotInstant
|
findItem(R.id.remove_ads).isVisible = !preferencesRepository.isPremiumEnabled() && isNotInstant
|
||||||
findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled()
|
findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled
|
||||||
findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled()
|
findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled
|
||||||
|
|
||||||
if (!playGamesManager.hasGooglePlayGames()) {
|
if (!playGamesManager.hasGooglePlayGames()) {
|
||||||
removeGroup(R.id.play_games_group)
|
removeGroup(R.id.play_games_group)
|
||||||
|
@ -928,7 +928,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshAds() {
|
private fun refreshAds() {
|
||||||
if (featureFlagManager.isInAppAdsEnabled()) {
|
if (featureFlagManager.isInAppAdsEnabled) {
|
||||||
val isTutorialComplete = preferencesRepository.isTutorialCompleted()
|
val isTutorialComplete = preferencesRepository.isTutorialCompleted()
|
||||||
if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) {
|
if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) {
|
||||||
if (!instantAppManager.isEnabled(this)) {
|
if (!instantAppManager.isEnabled(this)) {
|
||||||
|
|
|
@ -27,9 +27,13 @@ val ViewModelModule = module {
|
||||||
viewModel { ThemeViewModel(get(), get(), get(), get()) }
|
viewModel { ThemeViewModel(get(), get(), get(), get()) }
|
||||||
viewModel { SplashViewModel(get(), get(), get(), get()) }
|
viewModel { SplashViewModel(get(), get(), get(), get()) }
|
||||||
viewModel {
|
viewModel {
|
||||||
GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
|
GameViewModel(
|
||||||
|
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel {
|
viewModel {
|
||||||
TutorialViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
|
TutorialViewModel(
|
||||||
|
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.core.app.ActivityCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dev.lucasnlm.antimine.GameActivity
|
import dev.lucasnlm.antimine.GameActivity
|
||||||
import dev.lucasnlm.antimine.splash.viewmodel.SplashViewModel
|
import dev.lucasnlm.antimine.splash.viewmodel.SplashViewModel
|
||||||
|
import dev.lucasnlm.external.IFeatureFlagManager
|
||||||
import dev.lucasnlm.external.IPlayGamesManager
|
import dev.lucasnlm.external.IPlayGamesManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -16,6 +17,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class SplashActivity : AppCompatActivity() {
|
class SplashActivity : AppCompatActivity() {
|
||||||
private val playGamesManager: IPlayGamesManager by inject()
|
private val playGamesManager: IPlayGamesManager by inject()
|
||||||
|
private val featureFlagManager: IFeatureFlagManager by inject()
|
||||||
|
|
||||||
private val splashViewMode: SplashViewModel by viewModel()
|
private val splashViewMode: SplashViewModel by viewModel()
|
||||||
|
|
||||||
|
@ -24,6 +26,10 @@ class SplashActivity : AppCompatActivity() {
|
||||||
|
|
||||||
splashViewMode.startIap()
|
splashViewMode.startIap()
|
||||||
|
|
||||||
|
lifecycleScope.launchWhenCreated {
|
||||||
|
featureFlagManager.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
if (playGamesManager.hasGooglePlayGames()) {
|
if (playGamesManager.hasGooglePlayGames()) {
|
||||||
var logged: Boolean
|
var logged: Boolean
|
||||||
|
|
|
@ -18,6 +18,7 @@ import dev.lucasnlm.antimine.preferences.IPreferencesRepository
|
||||||
import dev.lucasnlm.antimine.core.sound.ISoundManager
|
import dev.lucasnlm.antimine.core.sound.ISoundManager
|
||||||
import dev.lucasnlm.antimine.ui.repository.IThemeRepository
|
import dev.lucasnlm.antimine.ui.repository.IThemeRepository
|
||||||
import dev.lucasnlm.antimine.tutorial.view.TutorialField
|
import dev.lucasnlm.antimine.tutorial.view.TutorialField
|
||||||
|
import dev.lucasnlm.external.IFeatureFlagManager
|
||||||
import dev.lucasnlm.external.IPlayGamesManager
|
import dev.lucasnlm.external.IPlayGamesManager
|
||||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -31,6 +32,7 @@ class TutorialViewModel(
|
||||||
minefieldRepository: IMinefieldRepository,
|
minefieldRepository: IMinefieldRepository,
|
||||||
analyticsManager: IAnalyticsManager,
|
analyticsManager: IAnalyticsManager,
|
||||||
playGamesManager: IPlayGamesManager,
|
playGamesManager: IPlayGamesManager,
|
||||||
|
featureFlagManager: IFeatureFlagManager,
|
||||||
tipRepository: ITipRepository,
|
tipRepository: ITipRepository,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
@ -48,6 +50,7 @@ class TutorialViewModel(
|
||||||
analyticsManager,
|
analyticsManager,
|
||||||
playGamesManager,
|
playGamesManager,
|
||||||
tipRepository,
|
tipRepository,
|
||||||
|
featureFlagManager,
|
||||||
clock,
|
clock,
|
||||||
) {
|
) {
|
||||||
val tutorialState = MutableStateFlow(
|
val tutorialState = MutableStateFlow(
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="8dp"
|
android:layout_marginVertical="8dp"
|
||||||
android:minHeight="66dp"
|
android:minHeight="60dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"
|
tools:visibility="visible"
|
||||||
tools:background="@color/accent"
|
tools:background="@color/accent"
|
||||||
|
|
|
@ -27,6 +27,7 @@ import dev.lucasnlm.antimine.preferences.models.GameControl
|
||||||
import dev.lucasnlm.antimine.ui.model.AppTheme
|
import dev.lucasnlm.antimine.ui.model.AppTheme
|
||||||
import dev.lucasnlm.antimine.ui.repository.IThemeRepository
|
import dev.lucasnlm.antimine.ui.repository.IThemeRepository
|
||||||
import dev.lucasnlm.external.Achievement
|
import dev.lucasnlm.external.Achievement
|
||||||
|
import dev.lucasnlm.external.IFeatureFlagManager
|
||||||
import dev.lucasnlm.external.IPlayGamesManager
|
import dev.lucasnlm.external.IPlayGamesManager
|
||||||
import dev.lucasnlm.external.Leaderboard
|
import dev.lucasnlm.external.Leaderboard
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -48,6 +49,7 @@ open class GameViewModel(
|
||||||
private val analyticsManager: IAnalyticsManager,
|
private val analyticsManager: IAnalyticsManager,
|
||||||
private val playGamesManager: IPlayGamesManager,
|
private val playGamesManager: IPlayGamesManager,
|
||||||
private val tipRepository: ITipRepository,
|
private val tipRepository: ITipRepository,
|
||||||
|
private val featureFlagManager: IFeatureFlagManager,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val eventObserver = MutableLiveData<Event>()
|
val eventObserver = MutableLiveData<Event>()
|
||||||
|
@ -308,6 +310,7 @@ open class GameViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFeedbackAnalytics(action: ActionResponse, index: Int) {
|
private fun onFeedbackAnalytics(action: ActionResponse, index: Int) {
|
||||||
|
if (featureFlagManager.isGameplayAnalyticsEnabled) {
|
||||||
when (action) {
|
when (action) {
|
||||||
ActionResponse.OpenTile -> {
|
ActionResponse.OpenTile -> {
|
||||||
analyticsManager.sentEvent(Analytics.OpenTile(index))
|
analyticsManager.sentEvent(Analytics.OpenTile(index))
|
||||||
|
@ -326,6 +329,7 @@ open class GameViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun refreshMineCount() = mineCount.postValue(gameController.remainingMines())
|
private fun refreshMineCount() = mineCount.postValue(gameController.remainingMines())
|
||||||
|
|
||||||
|
|
|
@ -113,10 +113,6 @@ sealed class Analytics(
|
||||||
|
|
||||||
object UnlockIapDialog : Analytics("IAP Dialog Unlock")
|
object UnlockIapDialog : Analytics("IAP Dialog Unlock")
|
||||||
|
|
||||||
object UnlockRewardedDialog : Analytics("Rewarded Dialog Unlock")
|
|
||||||
|
|
||||||
class ShowRatingRequest(usages: Int) : Analytics("Shown Rating Request", mapOf("Usages" to usages.toString()))
|
|
||||||
|
|
||||||
object TapRatingRequest : Analytics("Rating Request")
|
object TapRatingRequest : Analytics("Rating Request")
|
||||||
|
|
||||||
object UseTip : Analytics("Use Tip")
|
object UseTip : Analytics("Use Tip")
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package dev.lucasnlm.external
|
package dev.lucasnlm.external
|
||||||
|
|
||||||
interface IFeatureFlagManager {
|
abstract class IFeatureFlagManager {
|
||||||
fun isGameHistoryEnabled(): Boolean
|
abstract val isGameHistoryEnabled: Boolean
|
||||||
fun isRateUsEnabled(): Boolean
|
abstract val isRateUsEnabled: Boolean
|
||||||
fun isInAppAdsEnabled(): Boolean
|
abstract val isInAppAdsEnabled: Boolean
|
||||||
|
abstract val isGameplayAnalyticsEnabled: Boolean
|
||||||
|
|
||||||
|
abstract suspend fun refresh()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package dev.lucasnlm.external
|
package dev.lucasnlm.external
|
||||||
|
|
||||||
class FeatureFlagManager : IFeatureFlagManager {
|
class FeatureFlagManager : IFeatureFlagManager() {
|
||||||
override fun isGameHistoryEnabled(): Boolean = true
|
override val isGameHistoryEnabled: Boolean = true
|
||||||
override fun isRateUsEnabled(): Boolean = false
|
override val isRateUsEnabled: Boolean = false
|
||||||
override fun isInAppAdsEnabled(): Boolean = false
|
override val isInAppAdsEnabled: Boolean = false
|
||||||
|
override val isGameplayAnalyticsEnabled: Boolean = false
|
||||||
|
|
||||||
|
override suspend fun refresh() {
|
||||||
|
// No Feature Flags on FOSS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,8 @@ dependencies {
|
||||||
// Firebase
|
// Firebase
|
||||||
implementation 'com.google.firebase:firebase-analytics-ktx:18.0.0'
|
implementation 'com.google.firebase:firebase-analytics-ktx:18.0.0'
|
||||||
implementation 'com.google.firebase:firebase-crashlytics:17.3.0'
|
implementation 'com.google.firebase:firebase-crashlytics:17.3.0'
|
||||||
implementation 'com.google.firebase:firebase-firestore-ktx:22.0.0'
|
implementation 'com.google.firebase:firebase-firestore-ktx:22.0.1'
|
||||||
|
implementation 'com.google.firebase:firebase-config-ktx:20.0.2'
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
|
||||||
|
|
|
@ -1,7 +1,48 @@
|
||||||
package dev.lucasnlm.external
|
package dev.lucasnlm.external
|
||||||
|
|
||||||
class FeatureFlagManager : IFeatureFlagManager {
|
import com.google.android.gms.tasks.Tasks
|
||||||
override fun isGameHistoryEnabled(): Boolean = false
|
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||||
override fun isRateUsEnabled(): Boolean = true
|
import kotlinx.coroutines.Dispatchers
|
||||||
override fun isInAppAdsEnabled(): Boolean = false
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class FeatureFlagManager : IFeatureFlagManager() {
|
||||||
|
private val remoteConfig: FirebaseRemoteConfig by lazy {
|
||||||
|
FirebaseRemoteConfig.getInstance().apply {
|
||||||
|
setDefaultsAsync(mapOf(
|
||||||
|
HISTORY_ENABLED to false,
|
||||||
|
RATE_US_ENABLED to true,
|
||||||
|
IN_APP_ADS_ENABLED to false,
|
||||||
|
GAMEPLAY_EVENTS_ENABLED to false,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isGameHistoryEnabled: Boolean by lazy {
|
||||||
|
remoteConfig.getBoolean(HISTORY_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isRateUsEnabled: Boolean by lazy {
|
||||||
|
remoteConfig.getBoolean(RATE_US_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isInAppAdsEnabled: Boolean by lazy {
|
||||||
|
remoteConfig.getBoolean(IN_APP_ADS_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isGameplayAnalyticsEnabled: Boolean by lazy {
|
||||||
|
remoteConfig.getBoolean(GAMEPLAY_EVENTS_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun refresh() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Tasks.await(remoteConfig.fetchAndActivate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val HISTORY_ENABLED = "history_enabled"
|
||||||
|
private const val RATE_US_ENABLED = "rate_us_enabled"
|
||||||
|
private const val IN_APP_ADS_ENABLED = "in_app_ads_enabled"
|
||||||
|
private const val GAMEPLAY_EVENTS_ENABLED = "gameplay_events_enabled"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,30 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
if (System.getenv('IS_RELEASE_BUILD')) {
|
||||||
|
storeFile file(System.getenv('KEYSTORE'))
|
||||||
|
keyAlias System.getenv('KEY_ALIAS')
|
||||||
|
storePassword System.getenv('KEY_STORE_PASSWORD')
|
||||||
|
keyPassword System.getenv('KEY_PASSWORD')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
targetCompatibility 1.8
|
targetCompatibility 1.8
|
||||||
sourceCompatibility 1.8
|
sourceCompatibility 1.8
|
||||||
|
|
|
@ -6,6 +6,6 @@ import org.koin.dsl.module
|
||||||
|
|
||||||
val ViewModelModule = module {
|
val ViewModelModule = module {
|
||||||
viewModel {
|
viewModel {
|
||||||
GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
|
GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue