From 543adc0d6853269a695c5c6d9ceed70f06a135e0 Mon Sep 17 00:00:00 2001 From: Lucas Lima Date: Sun, 3 Jan 2021 12:06:02 -0300 Subject: [PATCH] Update Feature Flags to use Firebase --- .../dev/lucasnlm/antimine/GameActivity.kt | 6 +-- .../lucasnlm/antimine/di/ViewModelModule.kt | 8 ++- .../antimine/splash/SplashActivity.kt | 6 +++ .../tutorial/viewmodel/TutorialViewModel.kt | 3 ++ app/src/main/res/layout/dialog_end_game.xml | 2 +- .../common/level/viewmodel/GameViewModel.kt | 34 +++++++------ .../antimine/core/models/Analytics.kt | 4 -- .../lucasnlm/external/IFeatureFlagManager.kt | 11 +++-- .../lucasnlm/external/FeatureFlagManager.kt | 13 +++-- proprietary/build.gradle | 3 +- .../lucasnlm/external/FeatureFlagManager.kt | 49 +++++++++++++++++-- wear/build.gradle | 24 +++++++++ .../antimine/wear/di/ViewModelModule.kt | 2 +- 13 files changed, 126 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt index df61c4af..ea50ccf2 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt @@ -488,8 +488,8 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O val isNotInstant = !instantAppManager.isEnabled(applicationContext) findItem(R.id.share_now).isVisible = isNotInstant findItem(R.id.remove_ads).isVisible = !preferencesRepository.isPremiumEnabled() && isNotInstant - findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled() - findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled() + findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled + findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled if (!playGamesManager.hasGooglePlayGames()) { removeGroup(R.id.play_games_group) @@ -928,7 +928,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O } private fun refreshAds() { - if (featureFlagManager.isInAppAdsEnabled()) { + if (featureFlagManager.isInAppAdsEnabled) { val isTutorialComplete = preferencesRepository.isTutorialCompleted() if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) { if (!instantAppManager.isEnabled(this)) { diff --git a/app/src/main/java/dev/lucasnlm/antimine/di/ViewModelModule.kt b/app/src/main/java/dev/lucasnlm/antimine/di/ViewModelModule.kt index 917a1e72..8c535ccb 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/di/ViewModelModule.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/di/ViewModelModule.kt @@ -27,9 +27,13 @@ val ViewModelModule = module { viewModel { ThemeViewModel(get(), get(), get(), get()) } viewModel { SplashViewModel(get(), get(), get(), get()) } 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 { - 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() + ) } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/splash/SplashActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/splash/SplashActivity.kt index c762d6ae..31d85d6d 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/splash/SplashActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/splash/SplashActivity.kt @@ -8,6 +8,7 @@ import androidx.core.app.ActivityCompat import androidx.lifecycle.lifecycleScope import dev.lucasnlm.antimine.GameActivity import dev.lucasnlm.antimine.splash.viewmodel.SplashViewModel +import dev.lucasnlm.external.IFeatureFlagManager import dev.lucasnlm.external.IPlayGamesManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -16,6 +17,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel class SplashActivity : AppCompatActivity() { private val playGamesManager: IPlayGamesManager by inject() + private val featureFlagManager: IFeatureFlagManager by inject() private val splashViewMode: SplashViewModel by viewModel() @@ -24,6 +26,10 @@ class SplashActivity : AppCompatActivity() { splashViewMode.startIap() + lifecycleScope.launchWhenCreated { + featureFlagManager.refresh() + } + lifecycleScope.launchWhenCreated { if (playGamesManager.hasGooglePlayGames()) { var logged: Boolean diff --git a/app/src/main/java/dev/lucasnlm/antimine/tutorial/viewmodel/TutorialViewModel.kt b/app/src/main/java/dev/lucasnlm/antimine/tutorial/viewmodel/TutorialViewModel.kt index f8bc640b..d622a4cf 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/tutorial/viewmodel/TutorialViewModel.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/tutorial/viewmodel/TutorialViewModel.kt @@ -18,6 +18,7 @@ import dev.lucasnlm.antimine.preferences.IPreferencesRepository import dev.lucasnlm.antimine.core.sound.ISoundManager import dev.lucasnlm.antimine.ui.repository.IThemeRepository import dev.lucasnlm.antimine.tutorial.view.TutorialField +import dev.lucasnlm.external.IFeatureFlagManager import dev.lucasnlm.external.IPlayGamesManager import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.flow.MutableStateFlow @@ -31,6 +32,7 @@ class TutorialViewModel( minefieldRepository: IMinefieldRepository, analyticsManager: IAnalyticsManager, playGamesManager: IPlayGamesManager, + featureFlagManager: IFeatureFlagManager, tipRepository: ITipRepository, private val clock: Clock, private val context: Context, @@ -48,6 +50,7 @@ class TutorialViewModel( analyticsManager, playGamesManager, tipRepository, + featureFlagManager, clock, ) { val tutorialState = MutableStateFlow( diff --git a/app/src/main/res/layout/dialog_end_game.xml b/app/src/main/res/layout/dialog_end_game.xml index da451495..2d79448e 100644 --- a/app/src/main/res/layout/dialog_end_game.xml +++ b/app/src/main/res/layout/dialog_end_game.xml @@ -95,7 +95,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginVertical="8dp" - android:minHeight="66dp" + android:minHeight="60dp" android:visibility="gone" tools:visibility="visible" tools:background="@color/accent" diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt index 13174295..11c42e74 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt @@ -27,6 +27,7 @@ import dev.lucasnlm.antimine.preferences.models.GameControl import dev.lucasnlm.antimine.ui.model.AppTheme import dev.lucasnlm.antimine.ui.repository.IThemeRepository import dev.lucasnlm.external.Achievement +import dev.lucasnlm.external.IFeatureFlagManager import dev.lucasnlm.external.IPlayGamesManager import dev.lucasnlm.external.Leaderboard import kotlinx.coroutines.Dispatchers @@ -48,6 +49,7 @@ open class GameViewModel( private val analyticsManager: IAnalyticsManager, private val playGamesManager: IPlayGamesManager, private val tipRepository: ITipRepository, + private val featureFlagManager: IFeatureFlagManager, private val clock: Clock, ) : ViewModel() { val eventObserver = MutableLiveData() @@ -308,21 +310,23 @@ open class GameViewModel( } private fun onFeedbackAnalytics(action: ActionResponse, index: Int) { - when (action) { - ActionResponse.OpenTile -> { - analyticsManager.sentEvent(Analytics.OpenTile(index)) - } - ActionResponse.SwitchMark -> { - analyticsManager.sentEvent(Analytics.SwitchMark(index)) - } - ActionResponse.HighlightNeighbors -> { - analyticsManager.sentEvent(Analytics.HighlightNeighbors(index)) - } - ActionResponse.OpenNeighbors -> { - analyticsManager.sentEvent(Analytics.OpenNeighbors(index)) - } - ActionResponse.OpenOrMark -> { - analyticsManager.sentEvent(Analytics.OpenOrFlagTile(index)) + if (featureFlagManager.isGameplayAnalyticsEnabled) { + when (action) { + ActionResponse.OpenTile -> { + analyticsManager.sentEvent(Analytics.OpenTile(index)) + } + ActionResponse.SwitchMark -> { + analyticsManager.sentEvent(Analytics.SwitchMark(index)) + } + ActionResponse.HighlightNeighbors -> { + analyticsManager.sentEvent(Analytics.HighlightNeighbors(index)) + } + ActionResponse.OpenNeighbors -> { + analyticsManager.sentEvent(Analytics.OpenNeighbors(index)) + } + ActionResponse.OpenOrMark -> { + analyticsManager.sentEvent(Analytics.OpenOrFlagTile(index)) + } } } } diff --git a/core/src/main/java/dev/lucasnlm/antimine/core/models/Analytics.kt b/core/src/main/java/dev/lucasnlm/antimine/core/models/Analytics.kt index 5e480317..0d2f75fb 100644 --- a/core/src/main/java/dev/lucasnlm/antimine/core/models/Analytics.kt +++ b/core/src/main/java/dev/lucasnlm/antimine/core/models/Analytics.kt @@ -113,10 +113,6 @@ sealed class Analytics( 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 UseTip : Analytics("Use Tip") diff --git a/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt b/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt index 5ed9f4e9..ad9929d9 100644 --- a/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt +++ b/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt @@ -1,7 +1,10 @@ package dev.lucasnlm.external -interface IFeatureFlagManager { - fun isGameHistoryEnabled(): Boolean - fun isRateUsEnabled(): Boolean - fun isInAppAdsEnabled(): Boolean +abstract class IFeatureFlagManager { + abstract val isGameHistoryEnabled: Boolean + abstract val isRateUsEnabled: Boolean + abstract val isInAppAdsEnabled: Boolean + abstract val isGameplayAnalyticsEnabled: Boolean + + abstract suspend fun refresh() } diff --git a/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt b/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt index 80dcbfc4..c4922e20 100644 --- a/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt +++ b/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt @@ -1,7 +1,12 @@ package dev.lucasnlm.external -class FeatureFlagManager : IFeatureFlagManager { - override fun isGameHistoryEnabled(): Boolean = true - override fun isRateUsEnabled(): Boolean = false - override fun isInAppAdsEnabled(): Boolean = false +class FeatureFlagManager : IFeatureFlagManager() { + override val isGameHistoryEnabled: Boolean = true + override val isRateUsEnabled: Boolean = false + override val isInAppAdsEnabled: Boolean = false + override val isGameplayAnalyticsEnabled: Boolean = false + + override suspend fun refresh() { + // No Feature Flags on FOSS + } } diff --git a/proprietary/build.gradle b/proprietary/build.gradle index 69bf05f4..44cc693d 100644 --- a/proprietary/build.gradle +++ b/proprietary/build.gradle @@ -50,7 +50,8 @@ dependencies { // Firebase implementation 'com.google.firebase:firebase-analytics-ktx:18.0.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 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' diff --git a/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt b/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt index 1d092ec4..dc91aca0 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt @@ -1,7 +1,48 @@ package dev.lucasnlm.external -class FeatureFlagManager : IFeatureFlagManager { - override fun isGameHistoryEnabled(): Boolean = false - override fun isRateUsEnabled(): Boolean = true - override fun isInAppAdsEnabled(): Boolean = false +import com.google.android.gms.tasks.Tasks +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import kotlinx.coroutines.Dispatchers +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" + } } diff --git a/wear/build.gradle b/wear/build.gradle index 39ddbbc8..8367f488 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -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 { targetCompatibility 1.8 sourceCompatibility 1.8 diff --git a/wear/src/main/java/dev/lucasnlm/antimine/wear/di/ViewModelModule.kt b/wear/src/main/java/dev/lucasnlm/antimine/wear/di/ViewModelModule.kt index 2b3b3d63..feed6ce8 100644 --- a/wear/src/main/java/dev/lucasnlm/antimine/wear/di/ViewModelModule.kt +++ b/wear/src/main/java/dev/lucasnlm/antimine/wear/di/ViewModelModule.kt @@ -6,6 +6,6 @@ import org.koin.dsl.module val ViewModelModule = module { 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()) } }