diff --git a/app/build.gradle b/app/build.gradle index 6e0b85a97..c12eb555d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,6 @@ plugins { id 'com.android.application' - id('kotlin-android') + id 'kotlin-android' } android { @@ -8,8 +8,8 @@ android { defaultConfig { // versionCode and versionName must be hardcoded to support F-droid - versionCode 1705011 - versionName '17.5.1' + versionCode 1705061 + versionName '17.5.6' minSdk 21 targetSdk 34 compileSdk 34 @@ -73,8 +73,7 @@ android { versionNameSuffix ' S' apply plugin: 'com.google.gms.google-services' - apply plugin: 'com.google.firebase.crashlytics' - apply plugin: 'com.google.firebase.firebase-perf' + apply plugin: 'com.bugsnag.android.gradle' } googleInstant { @@ -84,8 +83,6 @@ android { versionNameSuffix ' I' apply plugin: 'com.google.gms.google-services' - apply plugin: 'com.google.firebase.crashlytics' - apply plugin: 'com.google.firebase.firebase-perf' } foss { diff --git a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt index e6b87d832..75f1e17e4 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt @@ -569,7 +569,7 @@ class GameActivity : isVisible = canUseHelpNow text = if (canRequestHelpWithAds) { - "+5" + "+10" } else { gameViewModel.getTips().toL10nString() } @@ -577,7 +577,11 @@ class GameActivity : binding.shortcutIcon.apply { TooltipCompat.setTooltipText(this, getString(i18n.string.help)) - setImageResource(R.drawable.hint) + if (canRequestHelpWithAds) { + setImageResource(R.drawable.movie) + } else { + setImageResource(R.drawable.hint) + } setColorFilter(binding.minesCount.currentTextColor) if (canUseHelpNow) { @@ -596,7 +600,6 @@ class GameActivity : val wasPlaying = gameAudioManager.isPlayingMusic() adsManager.showRewardedAd( activity = this@GameActivity, - skipIfFrequent = false, onStart = { if (wasPlaying) { gameAudioManager.pauseMusic() @@ -696,26 +699,16 @@ class GameActivity : } private fun startNewGameWithAds() { - if (!preferencesRepository.isPremiumEnabled() && featureFlagManager.isAdsOnNewGameEnabled) { + if (!preferencesRepository.isPremiumEnabled()) { if (featureFlagManager.useInterstitialAd) { adsManager.showInterstitialAd( activity = this, - onDismiss = { - lifecycleScope.launch { - gameViewModel.startNewGame() - } - }, - ) - } else { - adsManager.showRewardedAd( - activity = this, - skipIfFrequent = true, - onRewarded = { + onError = { lifecycleScope.launch { gameViewModel.startNewGame() } }, - onFail = { + onDismiss = { lifecycleScope.launch { gameViewModel.startNewGame() } @@ -769,7 +762,7 @@ class GameActivity : if (!instantAppManager.isEnabled(applicationContext)) { preferencesRepository.incrementUseCount() - if (preferencesRepository.getUseCount() > featureFlagManager.minUsageToReview) { + if (preferencesRepository.getUseCount() > MIN_USAGE_TO_REVIEW) { reviewWrapper.startInAppReview(this) } } @@ -889,7 +882,7 @@ class GameActivity : const val START_GAME = "start_game" const val RETRY_GAME = "retry_game" - const val TIP_COOLDOWN_MS = 5 * 1000L + const val TIP_COOLDOWN_MS = 2 * 1000L const val MINE_COUNTER_ANIM_COUNTER_MS = 250L const val LOADING_INDICATOR_MS = 500L @@ -897,7 +890,7 @@ class GameActivity : val CONFETTI_COLORS = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def) val CONFETTI_POSITION = Position.Relative(0.5, 0.2) - const val TOAST_OFFSET_Y_DP = 128 + const val MIN_USAGE_TO_REVIEW = 2 const val ENABLED_SHORTCUT_ALPHA = 1.0f const val DISABLED_SHORTCUT_ALPHA = 0.3f diff --git a/app/src/main/java/dev/lucasnlm/antimine/MainApplication.kt b/app/src/main/java/dev/lucasnlm/antimine/MainApplication.kt index 05e1f47ba..ac92837e9 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/MainApplication.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/MainApplication.kt @@ -13,6 +13,7 @@ import dev.lucasnlm.antimine.preferences.PreferencesRepository import dev.lucasnlm.antimine.support.IapHandler import dev.lucasnlm.external.AdsManager import dev.lucasnlm.external.AnalyticsManager +import dev.lucasnlm.external.CrashReporter import dev.lucasnlm.external.FeatureFlagManager import dev.lucasnlm.external.di.ExternalModule import kotlinx.coroutines.CoroutineScope @@ -29,18 +30,19 @@ open class MainApplication : MultiDexApplication() { private val featureFlagManager: FeatureFlagManager by inject() private val adsManager: AdsManager by inject() private val iapHandler: IapHandler by inject() + private val crashReporter: CrashReporter by inject() override fun onCreate() { super.onCreate() - DynamicColors.applyToActivitiesIfAvailable(this) - stopKoin() startKoin { androidContext(applicationContext) modules(AppModule, CommonModule, CommonIoModule, ExternalModule, LevelModule, ViewModelModule) } + crashReporter.start(this) + appScope.launch { iapHandler.start() } @@ -53,9 +55,6 @@ open class MainApplication : MultiDexApplication() { if (featureFlagManager.isFoss) { preferencesRepository.setPremiumFeatures(true) } else { - appScope.launch { - featureFlagManager.refresh() - } adsManager.start(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 430a16cda..20ada6d5c 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/di/ViewModelModule.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/di/ViewModelModule.kt @@ -30,7 +30,7 @@ val ViewModelModule = viewModel { LocalizationViewModel(get(), get()) } viewModel { GameViewModel( - get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), + get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), ) } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/gameover/CommonGameDialogFragment.kt b/app/src/main/java/dev/lucasnlm/antimine/gameover/CommonGameDialogFragment.kt index a0d699001..e7832b758 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/gameover/CommonGameDialogFragment.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/gameover/CommonGameDialogFragment.kt @@ -123,7 +123,7 @@ abstract class CommonGameDialogFragment : AppCompatDialogFragment() { ) preferencesRepository.setShowMusicBanner(false) gameAudioManager.playMonetization() - openComposer(composer.composerLink) + openComposer(it.context, composer.composerLink) } } @@ -138,9 +138,10 @@ abstract class CommonGameDialogFragment : AppCompatDialogFragment() { } } - private fun openComposer(composerLink: String) { - val context = requireContext() - + private fun openComposer( + context: Context, + composerLink: String, + ) { runCatching { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(composerLink)).apply { @@ -163,7 +164,7 @@ abstract class CommonGameDialogFragment : AppCompatDialogFragment() { post { addView( adsManager.createBannerAd( - requireContext(), + adFrame.context, onError = { showHexBanner(this) }, @@ -212,7 +213,6 @@ abstract class CommonGameDialogFragment : AppCompatDialogFragment() { if (!activity.isFinishing) { adsManager.showRewardedAd( activity, - skipIfFrequent = false, onRewarded = { continueGame() }, diff --git a/app/src/main/java/dev/lucasnlm/antimine/gameover/GameOverDialogFragment.kt b/app/src/main/java/dev/lucasnlm/antimine/gameover/GameOverDialogFragment.kt index 3c0e0bf2f..7d9e3b546 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/gameover/GameOverDialogFragment.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/gameover/GameOverDialogFragment.kt @@ -97,7 +97,7 @@ class GameOverDialogFragment : CommonGameDialogFragment() { continueGame.setOnClickListener { analyticsManager.sentEvent(Analytics.ContinueGame) - if (featureFlagManager.isAdsOnContinueEnabled && !isPremiumEnabled) { + if (!isPremiumEnabled) { showAdsAndContinue() } else { gameViewModel.sendEvent(GameEvent.ContinueGame) @@ -128,12 +128,9 @@ class GameOverDialogFragment : CommonGameDialogFragment() { showAdBannerDialog(adFrame) } - if (!state.showTutorial && - state.showContinueButton && - featureFlagManager.isContinueGameEnabled - ) { + if (!state.showTutorial && state.showContinueButton) { continueGame.isVisible = true - if (!isPremiumEnabled && featureFlagManager.isAdsOnContinueEnabled) { + if (!isPremiumEnabled) { continueGame.compoundDrawablePadding = 0 continueGame.setCompoundDrawablesWithIntrinsicBounds( R.drawable.watch_ads_icon, @@ -165,11 +162,7 @@ class GameOverDialogFragment : CommonGameDialogFragment() { val intent = Intent(context, TutorialActivity::class.java) context.startActivity(intent) } - } else if ( - !isPremiumEnabled && - !isInstantMode && - featureFlagManager.isGameOverAdEnabled - ) { + } else if (!isPremiumEnabled && !isInstantMode) { activity?.let { activity -> val label = context.getString(i18n.string.remove_ad) val priceModel = billingManager.getPrice() diff --git a/app/src/main/java/dev/lucasnlm/antimine/gameover/WinGameDialogFragment.kt b/app/src/main/java/dev/lucasnlm/antimine/gameover/WinGameDialogFragment.kt index 6d84c8e67..1b44e614e 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/gameover/WinGameDialogFragment.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/gameover/WinGameDialogFragment.kt @@ -105,15 +105,14 @@ class WinGameDialogFragment : CommonGameDialogFragment() { } newGame.setOnClickListener { - if (featureFlagManager.isAdsOnContinueEnabled && !isPremiumEnabled) { + if (!isPremiumEnabled) { showAdsAndContinue() } else { continueGame() } } - if (!isPremiumEnabled && featureFlagManager.isAdsOnContinueEnabled - ) { + if (!isPremiumEnabled) { newGame.compoundDrawablePadding = 0 newGame.setCompoundDrawablesWithIntrinsicBounds( R.drawable.watch_ads_icon, @@ -143,10 +142,7 @@ class WinGameDialogFragment : CommonGameDialogFragment() { stats.isVisible = true } - if (!isPremiumEnabled && - !isInstantMode && - featureFlagManager.isGameOverAdEnabled - ) { + if (!isPremiumEnabled && !isInstantMode) { activity?.let { activity -> val label = context.getString(i18n.string.remove_ad) val price = billingManager.getPrice()?.price diff --git a/app/src/main/java/dev/lucasnlm/antimine/main/MainActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/main/MainActivity.kt index e6db4cd62..df3d32971 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/main/MainActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/main/MainActivity.kt @@ -37,10 +37,12 @@ import dev.lucasnlm.antimine.preferences.PreferencesActivity import dev.lucasnlm.antimine.preferences.PreferencesRepository import dev.lucasnlm.antimine.preferences.models.Minefield import dev.lucasnlm.antimine.stats.StatsActivity +import dev.lucasnlm.antimine.support.IapHandler import dev.lucasnlm.antimine.themes.ThemeActivity import dev.lucasnlm.antimine.ui.ext.ThemedActivity import dev.lucasnlm.external.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject @@ -63,6 +65,7 @@ class MainActivity : ThemedActivity() { private val preferenceRepository: PreferencesRepository by inject() private val soundManager: GameAudioManager by inject() private val gameLocaleManager: GameLocaleManager by inject() + private val iapHandler: IapHandler by inject() private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) @@ -109,9 +112,22 @@ class MainActivity : ThemedActivity() { handleBackPressed() } + listenToPurchase() redirectToGame() } + private fun listenToPurchase() { + if (!preferenceRepository.isPremiumEnabled() && iapHandler.isEnabled()) { + lifecycleScope.launch { + iapHandler.listenPurchase().collect { + if (it) { + recreate() + } + } + } + } + } + private fun bindMenuButtons() { binding.continueGame.apply { if (preferencesRepository.showContinueGame()) { @@ -431,7 +447,6 @@ class MainActivity : ThemedActivity() { } private fun afterGooglePlayGames() { - playGamesManager.signInToFirebase(this) inAppUpdateManager.checkUpdate(this) } diff --git a/app/src/main/java/dev/lucasnlm/antimine/playgames/viewmodel/PlayGamesEvent.kt b/app/src/main/java/dev/lucasnlm/antimine/playgames/viewmodel/PlayGamesEvent.kt index 41e0fec4b..9286fcf6c 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/playgames/viewmodel/PlayGamesEvent.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/playgames/viewmodel/PlayGamesEvent.kt @@ -2,5 +2,6 @@ package dev.lucasnlm.antimine.playgames.viewmodel sealed class PlayGamesEvent { data object OpenAchievements : PlayGamesEvent() + data object OpenLeaderboards : PlayGamesEvent() } diff --git a/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsEvent.kt b/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsEvent.kt index bc7436ccd..effffd5cf 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsEvent.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsEvent.kt @@ -2,5 +2,6 @@ package dev.lucasnlm.antimine.stats.viewmodel sealed class StatsEvent { data object LoadStats : StatsEvent() + data object DeleteStats : StatsEvent() } diff --git a/app/src/main/res/drawable/movie.xml b/app/src/main/res/drawable/movie.xml new file mode 100644 index 000000000..e190aac50 --- /dev/null +++ b/app/src/main/res/drawable/movie.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d98990df1..860dac17e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -26,7 +26,8 @@ android:animateLayoutChanges="true" android:orientation="vertical" android:paddingHorizontal="@dimen/main_activity_padding" - android:paddingVertical="32dp"> + android:paddingTop="32dp" + android:paddingBottom="48dp"> @@ -165,16 +165,15 @@ diff --git a/app/src/main/res/layout/win_dialog.xml b/app/src/main/res/layout/win_dialog.xml index d61d90a88..d33ec8e3a 100644 --- a/app/src/main/res/layout/win_dialog.xml +++ b/app/src/main/res/layout/win_dialog.xml @@ -11,7 +11,7 @@ android:layout_height="wrap_content" android:background="@drawable/round_background" android:padding="16dp" - app:layout_constraintBottom_toTopOf="@+id/adFrame" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -161,8 +161,7 @@ android:gravity="center" android:minHeight="50dp" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toStartOf="@+id/dialog" + app:layout_constraintEnd_toEndOf="@+id/dialog" app:layout_constraintTop_toBottomOf="@+id/dialog" /> \ No newline at end of file diff --git a/app/src/test/java/dev/lucasnlm/antimine/di/TestAppModule.kt b/app/src/test/java/dev/lucasnlm/antimine/di/TestAppModule.kt index 6e2f5eb88..51ce508d3 100644 --- a/app/src/test/java/dev/lucasnlm/antimine/di/TestAppModule.kt +++ b/app/src/test/java/dev/lucasnlm/antimine/di/TestAppModule.kt @@ -103,10 +103,6 @@ val AppModule = } override fun shouldRequestLogin(): Boolean = false - - override fun signInToFirebase(activity: Activity) { - // Not implemented - } } } bind PlayGamesManager::class diff --git a/build.gradle b/build.gradle index 2660e0e8b..eac8650b4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,15 @@ +buildscript { + dependencies { + classpath 'com.bugsnag:bugsnag-android-gradle-plugin:8.1.0' + } +} + plugins { - id('com.android.application') version '8.1.2' apply false - id('com.android.library') version '8.1.2' apply false - id('org.jetbrains.kotlin.android') version '1.9.10' apply false + id 'com.android.application' version '8.1.2' apply false + id 'com.android.library' version '8.1.2' apply false + id 'org.jetbrains.kotlin.android' version '1.9.10' apply false - id('com.google.gms.google-services') version '4.4.0' apply false - id('com.google.firebase.crashlytics') version '2.9.9' apply false - id('com.google.firebase.firebase-perf') version '1.4.2' apply false + id 'com.google.gms.google-services' version '4.4.0' apply false } allprojects { diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/GameRenderFragment.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/GameRenderFragment.kt index 59ebcc930..38912ccdb 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/GameRenderFragment.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/GameRenderFragment.kt @@ -123,7 +123,7 @@ open class GameRenderFragment : AndroidFragmentApplication() { useCompass = false useGyroscope = false useWakelock = false - useImmersiveMode = true + useImmersiveMode = false disableAudio = true } return initializeForView(levelApplicationListener, config) @@ -168,6 +168,18 @@ open class GameRenderFragment : AndroidFragmentApplication() { .collect(::refreshState) } + lifecycleScope.launch { + gameViewModel + .observeState() + .distinctUntilChangedBy { it.isActive } + .collect { state -> + val areActionsEnabled = state.isActive + levelApplicationListener.setActionsEnabled(areActionsEnabled) + Gdx.graphics.requestRendering() + syncControlSwitcher(state.selectedAction) + } + } + lifecycleScope.launch { gameViewModel.observeState() .map { it.seed to it.minefield } @@ -182,7 +194,7 @@ open class GameRenderFragment : AndroidFragmentApplication() { private fun refreshState(state: GameState) { levelApplicationListener.bindField(state.field) - val areActionsEnabled = state.isActive && !state.isGameCompleted + val areActionsEnabled = state.isActive levelApplicationListener.setActionsEnabled(areActionsEnabled) syncControlSwitcher(state.selectedAction) 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 26ee9b3cd..2bfb57dcf 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 @@ -29,7 +29,6 @@ import dev.lucasnlm.antimine.preferences.models.GameControl import dev.lucasnlm.antimine.preferences.models.Minefield import dev.lucasnlm.external.Achievement import dev.lucasnlm.external.AnalyticsManager -import dev.lucasnlm.external.FeatureFlagManager import dev.lucasnlm.external.Leaderboard import dev.lucasnlm.external.PlayGamesManager import kotlinx.coroutines.Dispatchers @@ -52,7 +51,6 @@ open class GameViewModel( private val analyticsManager: AnalyticsManager, private val playGamesManager: PlayGamesManager, private val tipRepository: TipRepository, - private val featureFlagManager: FeatureFlagManager, private val clockManager: ClockManager, ) : IntentViewModel() { private lateinit var gameController: GameController @@ -102,7 +100,7 @@ open class GameViewModel( sendSideEffect(GameEvent.ShowNewGameDialog) } is GameEvent.GiveMoreTip -> { - tipRepository.increaseTip(5) + tipRepository.increaseTip(10) val newState = state.copy( @@ -143,12 +141,17 @@ open class GameViewModel( emit( state.copy( isLoadingMap = false, - selectedAction = gameController.getSelectedAction(), + selectedAction = + if (initialized) { + gameController.getSelectedAction() + } else { + preferencesRepository.defaultSwitchButton() + }, ), ) } - if (!state.isGameCompleted && state.hasMines && !state.isLoadingMap) { + if (initialized && !state.isGameCompleted && state.hasMines && !state.isLoadingMap) { if ( !gameController.isGameOver() && !gameController.isVictory() && @@ -517,7 +520,6 @@ open class GameViewModel( .longPress(index) .filterNotNull() .collect { actionCompleted -> - onFeedbackAnalytics(actionCompleted.action, index) onPostAction() playActionSound(actionCompleted) refreshField() @@ -537,7 +539,6 @@ open class GameViewModel( .doubleClick(index) .filterNotNull() .collect { actionCompleted -> - onFeedbackAnalytics(actionCompleted.action, index) onPostAction() playActionSound(actionCompleted) refreshField() @@ -553,7 +554,6 @@ open class GameViewModel( .singleClick(index) .filterNotNull() .collect { actionCompleted -> - onFeedbackAnalytics(actionCompleted.action, index) onPostAction() playActionSound(actionCompleted) refreshField() @@ -590,31 +590,6 @@ open class GameViewModel( updateGameState() } - private fun onFeedbackAnalytics( - action: Action, - index: Int, - ) { - if (featureFlagManager.isGameplayAnalyticsEnabled) { - when (action) { - Action.OpenTile -> { - analyticsManager.sentEvent(Analytics.OpenTile(index)) - } - Action.SwitchMark -> { - analyticsManager.sentEvent(Analytics.SwitchMark(index)) - } - Action.OpenNeighbors -> { - analyticsManager.sentEvent(Analytics.OpenNeighbors(index)) - } - Action.OpenOrMark -> { - analyticsManager.sentEvent(Analytics.OpenOrFlagTile(index)) - } - Action.QuestionMark -> { - analyticsManager.sentEvent(Analytics.QuestionMark(index)) - } - } - } - } - private fun updateGameState() { when { gameController.isGameOver() -> { diff --git a/core/src/main/java/dev/lucasnlm/antimine/core/repository/DimensionRepositoryImpl.kt b/core/src/main/java/dev/lucasnlm/antimine/core/repository/DimensionRepositoryImpl.kt index 4e68435bd..29cf9763c 100644 --- a/core/src/main/java/dev/lucasnlm/antimine/core/repository/DimensionRepositoryImpl.kt +++ b/core/src/main/java/dev/lucasnlm/antimine/core/repository/DimensionRepositoryImpl.kt @@ -28,7 +28,7 @@ class DimensionRepositoryImpl( } override fun areaSize(): Float { - return context.resources.getDimension(R.dimen.field_size) + return displaySize().width / 11.0f } override fun areaSeparator(): Float { diff --git a/external/src/main/java/dev/lucasnlm/external/AdsManager.kt b/external/src/main/java/dev/lucasnlm/external/AdsManager.kt index 51f7650c2..e024786f8 100644 --- a/external/src/main/java/dev/lucasnlm/external/AdsManager.kt +++ b/external/src/main/java/dev/lucasnlm/external/AdsManager.kt @@ -11,7 +11,6 @@ interface AdsManager { fun showRewardedAd( activity: Activity, - skipIfFrequent: Boolean, onStart: (() -> Unit)? = null, onRewarded: (() -> Unit)?, onFail: (() -> Unit)?, diff --git a/external/src/main/java/dev/lucasnlm/external/CrashReporter.kt b/external/src/main/java/dev/lucasnlm/external/CrashReporter.kt index 7b24f946f..1f4c674d6 100644 --- a/external/src/main/java/dev/lucasnlm/external/CrashReporter.kt +++ b/external/src/main/java/dev/lucasnlm/external/CrashReporter.kt @@ -1,5 +1,9 @@ package dev.lucasnlm.external +import android.app.Application + interface CrashReporter { fun sendError(message: String) + + fun start(application: Application) } diff --git a/external/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt b/external/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt index 22ab0a2d1..9977aeae1 100644 --- a/external/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt +++ b/external/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt @@ -1,18 +1,28 @@ package dev.lucasnlm.external -abstract class FeatureFlagManager { - abstract val isGameHistoryEnabled: Boolean - abstract val isRateUsEnabled: Boolean - abstract val isGameplayAnalyticsEnabled: Boolean - abstract val isGameOverAdEnabled: Boolean - abstract val isAdsOnContinueEnabled: Boolean - abstract val isAdsOnNewGameEnabled: Boolean - abstract val useInterstitialAd: Boolean - abstract val isContinueGameEnabled: Boolean - abstract val isFoss: Boolean - abstract val minUsageToReview: Int - abstract val isBannerAdEnabled: Boolean - abstract val showCountdownToContinue: Boolean +interface FeatureFlagManager { + /** + * Whether the game history feature is enabled. + */ + val isGameHistoryEnabled: Boolean - abstract suspend fun refresh() + /** + * Whether the interstitial ad should be used. + */ + val useInterstitialAd: Boolean + + /** + * Whether the app is the FOSS version. + */ + val isFoss: Boolean + + /** + * Whether the banner ad should be used. + */ + val isBannerAdEnabled: Boolean + + /** + * Whether the countdown to continue should be shown. + */ + val showCountdownToContinue: Boolean } diff --git a/external/src/main/java/dev/lucasnlm/external/PlayGamesManager.kt b/external/src/main/java/dev/lucasnlm/external/PlayGamesManager.kt index 6932db961..1be6c6896 100644 --- a/external/src/main/java/dev/lucasnlm/external/PlayGamesManager.kt +++ b/external/src/main/java/dev/lucasnlm/external/PlayGamesManager.kt @@ -65,6 +65,4 @@ interface PlayGamesManager { fun keepRequestingLogin(status: Boolean) fun shouldRequestLogin(): Boolean - - fun signInToFirebase(activity: Activity) } diff --git a/foss/build.gradle b/foss/build.gradle index 1e828e01e..1b4843d31 100644 --- a/foss/build.gradle +++ b/foss/build.gradle @@ -19,12 +19,15 @@ android { } } + buildFeatures { + buildConfig true + viewBinding true + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - - } dependencies { @@ -32,6 +35,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':external') + implementation project(':i18n') // Kotlin implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' @@ -41,4 +45,10 @@ dependencies { // Koin implementation 'io.insert-koin:koin-android:3.1.2' testImplementation 'io.insert-koin:koin-test:3.1.2' + + // Acra + implementation 'ch.acra:acra-core:5.11.2' + implementation 'ch.acra:acra-mail:5.11.2' + implementation 'ch.acra:acra-toast:5.11.2' + implementation 'ch.acra:acra-limiter:5.11.2' } diff --git a/foss/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt b/foss/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt index 00c7c1b35..3a4d8b916 100644 --- a/foss/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt +++ b/foss/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt @@ -1,7 +1,38 @@ package dev.lucasnlm.external +import android.app.Application +import dev.lucasnlm.antimine.i18n.R +import org.acra.BuildConfig +import org.acra.config.limiter +import org.acra.config.mailSender +import org.acra.config.toast +import org.acra.data.StringFormat +import org.acra.ktx.initAcra + class CrashReporterImpl : CrashReporter { override fun sendError(message: String) { // FOSS build doesn't log errors. } + + override fun start(application: Application) { + val context = application.applicationContext + application.initAcra { + buildConfigClass = BuildConfig::class.java + reportFormat = StringFormat.JSON + + mailSender { + subject = "[antimine] crash report" + mailTo = "me@lucasnlm.dev" + reportFileName = "report.txt" + } + + toast { + text = context.getString(R.string.acra_toast_text) + } + + limiter { + // No changes + } + } + } } diff --git a/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt b/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt index 075225a02..19956bb03 100644 --- a/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt +++ b/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt @@ -1,20 +1,9 @@ package dev.lucasnlm.external -class FeatureFlagManagerImpl : FeatureFlagManager() { +class FeatureFlagManagerImpl : FeatureFlagManager { override val isGameHistoryEnabled: Boolean = true - override val isRateUsEnabled: Boolean = false - override val isGameplayAnalyticsEnabled: Boolean = false - override val isGameOverAdEnabled: Boolean = false - override val isAdsOnNewGameEnabled: Boolean = false - override val isAdsOnContinueEnabled: Boolean = false - override val isContinueGameEnabled: Boolean = true override val isFoss: Boolean = true - override val minUsageToReview: Int = Int.MAX_VALUE override val useInterstitialAd: Boolean = false override val isBannerAdEnabled: Boolean = false override val showCountdownToContinue: Boolean = false - - override suspend fun refresh() { - // No Feature Flags on FOSS - } } diff --git a/foss/src/main/java/dev/lucasnlm/external/NoAdsManager.kt b/foss/src/main/java/dev/lucasnlm/external/NoAdsManager.kt index b021f2263..460dbbbcf 100644 --- a/foss/src/main/java/dev/lucasnlm/external/NoAdsManager.kt +++ b/foss/src/main/java/dev/lucasnlm/external/NoAdsManager.kt @@ -9,7 +9,6 @@ class NoAdsManager : AdsManager { override fun showRewardedAd( activity: Activity, - skipIfFrequent: Boolean, onStart: (() -> Unit)?, onRewarded: (() -> Unit)?, onFail: (() -> Unit)?, diff --git a/foss/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt b/foss/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt index 0ab837ad6..b9dd72790 100644 --- a/foss/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt +++ b/foss/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt @@ -68,8 +68,4 @@ class PlayGamesManagerImpl( override fun shouldRequestLogin(): Boolean { return false } - - override fun signInToFirebase(activity: Activity) { - // F-droid build doesn't have Google Play Games - } } diff --git a/gdx/check_import.sh b/gdx/check_import.sh index f4475aef8..ba94cfbd9 100755 --- a/gdx/check_import.sh +++ b/gdx/check_import.sh @@ -14,9 +14,7 @@ fi # Check if the file contains the string in an unique line if grep -xq "preBuild.dependsOn copyAndroidNatives" "$1"; then - echo "achou" exit 0 else - echo "n achou" exit 1 fi diff --git a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/GameApplicationListener.kt b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/GameApplicationListener.kt index 389fc72f5..7f049ac43 100644 --- a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/GameApplicationListener.kt +++ b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/GameApplicationListener.kt @@ -123,6 +123,7 @@ class GameApplicationListener( } Gdx.input.inputProcessor = InputMultiplexer(GestureDetector(minefieldInputController), minefieldStage) + minefieldStage.setZoom(GameContext.zoom) } override fun dispose() { @@ -142,7 +143,6 @@ class GameApplicationListener( fun onPause() { GameContext.run { zoom = 1.0f - minefieldStage.setZoom(1.0f) } } diff --git a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaActor.kt b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaActor.kt index 1bab44421..fba57a63a 100644 --- a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaActor.kt +++ b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaActor.kt @@ -31,21 +31,21 @@ class AreaActor( inputListener: InputListener, var isPressed: Boolean = false, private var focusScale: Float = 1.0f, - private var pieces: Map = mapOf(), + private var pieces: Set = setOf(), private val gameRenderingContext: GameRenderingContext, ) : Actor() { var area: Area? = null private var areaForm: Int? = null - private var topId: Int = -1 - private var bottomId: Int = -1 - private var leftId: Int = -1 - private var rightId: Int = -1 - private var topLeftId: Int = -1 - private var topRightId: Int = -1 - private var bottomLeftId: Int = -1 - private var bottomRightId: Int = -1 + private var topId: Int = NO_LINK + private var bottomId: Int = NO_LINK + private var leftId: Int = NO_LINK + private var rightId: Int = NO_LINK + private var topLeftId: Int = NO_LINK + private var topRightId: Int = NO_LINK + private var bottomLeftId: Int = NO_LINK + private var bottomRightId: Int = NO_LINK init { width = gameRenderingContext.areaSize @@ -65,11 +65,9 @@ class AreaActor( } if (this.area != area) { - if (this.area?.id != area.id) { - x = area.posX * width - y = area.posY * height - refreshLinks(area, field) - } + x = area.posX * width + y = area.posY * height + refreshLinks(area, field) this.area = area } @@ -170,17 +168,15 @@ class AreaActor( ) } else { pieces.forEach { piece -> - if (piece.value) { - batch.drawRegion( - texture = GameContext.gameTextures!!.pieces[piece.key]!!, - x = x - 0.5f, - y = y - 0.5f, - width = width + 0.5f, - height = height + 0.5f, - color = coverColor, - blend = false, - ) - } + batch.drawRegion( + texture = GameContext.gameTextures!!.pieces[piece]!!, + x = x - 0.5f, + y = y - 0.5f, + width = width + 0.5f, + height = height + 0.5f, + color = coverColor, + blend = false, + ) } } } @@ -191,17 +187,15 @@ class AreaActor( GameContext.atlas?.let { atlas -> pieces.forEach { piece -> - if (piece.value) { - batch.drawRegion( - texture = atlas.findRegion(piece.key), - x = x - 0.5f, - y = y - 0.5f, - width = width + 1.0f, - height = height + 1.0f, - color = coverColor, - blend = false, - ) - } + batch.drawRegion( + texture = atlas.findRegion(piece), + x = x - 0.5f, + y = y - 0.5f, + width = width + 1.0f, + height = height + 1.0f, + color = coverColor, + blend = false, + ) } } } @@ -384,14 +378,14 @@ class AreaActor( bottomLeftId = area.getNeighborIdAtPos(field, -1, -1) bottomRightId = area.getNeighborIdAtPos(field, 1, -1) } else { - topId = -1 - bottomId = -1 - leftId = -1 - rightId = -1 - topLeftId = -1 - topRightId = -1 - bottomLeftId = -1 - bottomRightId = -1 + topId = NO_LINK + bottomId = NO_LINK + leftId = NO_LINK + rightId = NO_LINK + topLeftId = NO_LINK + topRightId = NO_LINK + bottomLeftId = NO_LINK + bottomRightId = NO_LINK } } @@ -419,6 +413,7 @@ class AreaActor( const val MIN_SCALE = 1.0f const val MAX_SCALE = 1.15f const val BASE_ICON_SCALE = 0.8f + const val NO_LINK = -1 private fun Area.canLinkTo(area: Area): Boolean { return isCovered && mark.ligatureMask == area.mark.ligatureMask diff --git a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaForm.kt b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaForm.kt index bdc49f0af..feb18c9a5 100644 --- a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaForm.kt +++ b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/actors/AreaForm.kt @@ -16,21 +16,21 @@ object AreaForm { return (this and flag) != 0 } - private fun Int.top(): Boolean = checkForm(AreaForm.TOP) + private fun Int.top(): Boolean = checkForm(TOP) - private fun Int.bottom(): Boolean = checkForm(AreaForm.BOTTOM) + private fun Int.bottom(): Boolean = checkForm(BOTTOM) - private fun Int.left(): Boolean = checkForm(AreaForm.LEFT) + private fun Int.left(): Boolean = checkForm(LEFT) - private fun Int.right(): Boolean = checkForm(AreaForm.RIGHT) + private fun Int.right(): Boolean = checkForm(RIGHT) - private fun Int.topLeft(): Boolean = checkForm(AreaForm.TOP_LEFT) + private fun Int.topLeft(): Boolean = checkForm(TOP_LEFT) - private fun Int.topRight(): Boolean = checkForm(AreaForm.TOP_RIGHT) + private fun Int.topRight(): Boolean = checkForm(TOP_RIGHT) - private fun Int.bottomLeft(): Boolean = checkForm(AreaForm.BOTTOM_LEFT) + private fun Int.bottomLeft(): Boolean = checkForm(BOTTOM_LEFT) - private fun Int.bottomRight(): Boolean = checkForm(AreaForm.BOTTOM_RIGHT) + private fun Int.bottomRight(): Boolean = checkForm(BOTTOM_RIGHT) fun areaFormOf( top: Boolean, @@ -79,7 +79,7 @@ object AreaForm { return result } - fun Int.toAtlasNames(): Map { + fun Int.toAtlasNames(): Set { return mapOf( AtlasNames.CORE to true, AtlasNames.TOP to top(), @@ -98,7 +98,9 @@ object AreaForm { AtlasNames.FILL_TOP_RIGHT to (top() && right() && topRight()), AtlasNames.FILL_BOTTOM_LEFT to (bottom() && left() && bottomLeft()), AtlasNames.FILL_BOTTOM_RIGHT to (bottom() && right() && bottomRight()), - ) + ).filter { + it.value + }.keys } const val AREA_NO_FORM = 0b00000000 diff --git a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/stages/MinefieldStage.kt b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/stages/MinefieldStage.kt index 5de8a6da1..d1d317558 100644 --- a/gdx/src/main/java/dev/lucasnlm/antimine/gdx/stages/MinefieldStage.kt +++ b/gdx/src/main/java/dev/lucasnlm/antimine/gdx/stages/MinefieldStage.kt @@ -83,11 +83,9 @@ class MinefieldStage( this.newBoundAreas = null } - if (actors.size != boundAreas.size) { - if (boundAreas.size < actors.size) { - actors.removeRange(boundAreas.size, actors.size) - actors.shrink() - } else { + val forceRebind = actors.size != boundAreas.size + if (forceRebind) { + if (boundAreas.size > actors.size) { actors.ensureCapacity(boundAreas.size + 1) } @@ -101,10 +99,13 @@ class MinefieldStage( ) actors.add(areaActor) } + } else if (actors.size > boundAreas.size) { + actors.removeRange(boundAreas.size, actors.size - 1) } } val areaSize = gameRenderingContext.areaSize + val checkShape = forceRefreshVisibleAreas || forceRebind Gdx.graphics.isContinuousRendering = true actors.forEachIndexed { index, actor -> val areaActor = (actor as AreaActor) @@ -122,7 +123,7 @@ class MinefieldStage( reset = resetEvents, area = area, field = boundAreas, - checkShape = forceRefreshVisibleAreas, + checkShape = checkShape, ) } Gdx.graphics.isContinuousRendering = false diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 3c39fa780..76fa9bf4f 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -146,4 +146,5 @@ Show clock If you like this game, consider making a donation. It will help keep this project active! + Sorry, an error occurred. Please, send the report to the developers. diff --git a/preferences/src/main/java/dev/lucasnlm/antimine/preferences/models/GameControl.kt b/preferences/src/main/java/dev/lucasnlm/antimine/preferences/models/GameControl.kt index a866c9a96..e90438596 100644 --- a/preferences/src/main/java/dev/lucasnlm/antimine/preferences/models/GameControl.kt +++ b/preferences/src/main/java/dev/lucasnlm/antimine/preferences/models/GameControl.kt @@ -42,84 +42,84 @@ sealed class GameControl( val onUncovered: Actions, ) { data object Standard : GameControl( - id = ControlStyle.Standard, - onCovered = - Actions( - singleClick = Action.OpenTile, - longPress = Action.SwitchMark, - doubleClick = null, - ), - onUncovered = - Actions( - singleClick = null, - longPress = Action.OpenNeighbors, - doubleClick = null, - ), - ) + id = ControlStyle.Standard, + onCovered = + Actions( + singleClick = Action.OpenTile, + longPress = Action.SwitchMark, + doubleClick = null, + ), + onUncovered = + Actions( + singleClick = null, + longPress = Action.OpenNeighbors, + doubleClick = null, + ), + ) data object FastFlag : GameControl( - id = ControlStyle.FastFlag, - onCovered = - Actions( - singleClick = Action.SwitchMark, - longPress = Action.OpenTile, - doubleClick = null, - ), - onUncovered = - Actions( - singleClick = Action.OpenNeighbors, - longPress = null, - doubleClick = null, - ), - ) + id = ControlStyle.FastFlag, + onCovered = + Actions( + singleClick = Action.SwitchMark, + longPress = Action.OpenTile, + doubleClick = null, + ), + onUncovered = + Actions( + singleClick = Action.OpenNeighbors, + longPress = null, + doubleClick = null, + ), + ) data object DoubleClick : GameControl( - id = ControlStyle.DoubleClick, - onCovered = - Actions( - singleClick = Action.SwitchMark, - longPress = null, - doubleClick = Action.OpenTile, - ), - onUncovered = - Actions( - singleClick = Action.OpenNeighbors, - longPress = null, - doubleClick = null, - ), - ) + id = ControlStyle.DoubleClick, + onCovered = + Actions( + singleClick = Action.SwitchMark, + longPress = null, + doubleClick = Action.OpenTile, + ), + onUncovered = + Actions( + singleClick = Action.OpenNeighbors, + longPress = null, + doubleClick = null, + ), + ) data object DoubleClickInverted : GameControl( - id = ControlStyle.DoubleClickInverted, - onCovered = - Actions( - singleClick = Action.OpenTile, - longPress = null, - doubleClick = Action.SwitchMark, - ), - onUncovered = - Actions( - singleClick = Action.OpenNeighbors, - longPress = null, - doubleClick = null, - ), - ) + id = ControlStyle.DoubleClickInverted, + onCovered = + Actions( + singleClick = Action.OpenTile, + longPress = null, + doubleClick = Action.SwitchMark, + ), + onUncovered = + Actions( + singleClick = Action.OpenNeighbors, + longPress = null, + doubleClick = null, + ), + ) data object SwitchMarkOpen : GameControl( - id = ControlStyle.SwitchMarkOpen, - onCovered = - Actions( - singleClick = Action.OpenOrMark, - longPress = null, - doubleClick = null, - ), - onUncovered = - Actions( - singleClick = Action.OpenNeighbors, - longPress = null, - doubleClick = null, - ), - ) + id = ControlStyle.SwitchMarkOpen, + onCovered = + Actions( + singleClick = Action.OpenOrMark, + longPress = null, + doubleClick = null, + ), + onUncovered = + Actions( + singleClick = Action.OpenNeighbors, + longPress = null, + doubleClick = null, + ), + ) companion object { fun fromControlType(controlStyle: ControlStyle): GameControl { diff --git a/proprietary/build.gradle b/proprietary/build.gradle index 221628fb9..9b0546364 100644 --- a/proprietary/build.gradle +++ b/proprietary/build.gradle @@ -40,6 +40,9 @@ dependencies { implementation 'com.amplitude:android-sdk:2.32.1' implementation 'com.squareup.okhttp3:okhttp:4.10.0' + // Bugsnag + implementation 'com.bugsnag:bugsnag-android:5.31.2' + // Google implementation 'com.android.billingclient:billing-ktx:6.0.1' implementation 'com.google.android.gms:play-services-instantapps:18.0.1' @@ -51,15 +54,6 @@ dependencies { // Jetbrains implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3' - // Firebase - implementation platform('com.google.firebase:firebase-bom:32.3.1') - implementation 'com.google.firebase:firebase-analytics-ktx' - implementation 'com.google.firebase:firebase-crashlytics-ktx' - implementation 'com.google.firebase:firebase-firestore-ktx' - implementation 'com.google.firebase:firebase-config-ktx' - implementation 'com.google.firebase:firebase-auth-ktx' - implementation 'com.google.firebase:firebase-perf-ktx' - // Kotlin implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.10' diff --git a/proprietary/src/main/AndroidManifest.xml b/proprietary/src/main/AndroidManifest.xml index 390db3035..5995c1d71 100644 --- a/proprietary/src/main/AndroidManifest.xml +++ b/proprietary/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + diff --git a/proprietary/src/main/java/dev/lucasnlm/external/AdMobAdsManager.kt b/proprietary/src/main/java/dev/lucasnlm/external/AdMobAdsManager.kt index f0fcc6983..99e0a4863 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/AdMobAdsManager.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/AdMobAdsManager.kt @@ -190,47 +190,42 @@ class AdMobAdsManager( override fun showRewardedAd( activity: Activity, - skipIfFrequent: Boolean, onStart: (() -> Unit)?, onRewarded: (() -> Unit)?, onFail: (() -> Unit)?, ) { - if (skipIfFrequent && (System.currentTimeMillis() - lastShownAd < Ads.MIN_FREQUENCY)) { - onRewarded?.invoke() - } else { - val rewardedAd = this.rewardedAd - val secondRewardedAd = this.secondRewardedAd - - when { - secondRewardedAd != null -> { - onStart?.invoke() - secondRewardedAd.show(activity) { - if (!activity.isFinishing) { - lastShownAd = System.currentTimeMillis() - onRewarded?.invoke() - scope.launch(Dispatchers.Main) { - loadSecondRewardAd() - } + val rewardedAd = this.rewardedAd + val secondRewardedAd = this.secondRewardedAd + + when { + secondRewardedAd != null -> { + onStart?.invoke() + secondRewardedAd.show(activity) { + if (!activity.isFinishing) { + lastShownAd = System.currentTimeMillis() + onRewarded?.invoke() + scope.launch(Dispatchers.Main) { + loadSecondRewardAd() } } } - rewardedAd != null -> { - onStart?.invoke() - rewardedAd.show(activity) { - if (!activity.isFinishing) { - lastShownAd = System.currentTimeMillis() - onRewarded?.invoke() - scope.launch(Dispatchers.Main) { - loadRewardAd() - } + } + rewardedAd != null -> { + onStart?.invoke() + rewardedAd.show(activity) { + if (!activity.isFinishing) { + lastShownAd = System.currentTimeMillis() + onRewarded?.invoke() + scope.launch(Dispatchers.Main) { + loadRewardAd() } } } - else -> { - val message = failErrorCause?.let { "Fail to load Ad\n$it" } ?: "Fail to load Ad" - crashReporter.sendError(message) - onFail?.invoke() - } + } + else -> { + val message = failErrorCause?.let { "Fail to load Ad\n$it" } ?: "Fail to load Ad" + crashReporter.sendError(message) + onFail?.invoke() } } } diff --git a/proprietary/src/main/java/dev/lucasnlm/external/CloudStorageManagerImpl.kt b/proprietary/src/main/java/dev/lucasnlm/external/CloudStorageManagerImpl.kt index 4f895eeaf..a741fe1f9 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/CloudStorageManagerImpl.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/CloudStorageManagerImpl.kt @@ -1,62 +1,14 @@ package dev.lucasnlm.external -import android.text.format.DateUtils -import android.util.Log -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.ktx.firestore -import com.google.firebase.ktx.Firebase -import dev.lucasnlm.antimine.proprietary.BuildConfig import dev.lucasnlm.external.model.CloudSave -import dev.lucasnlm.external.model.cloudSaveOf -import dev.lucasnlm.external.model.toHashMap -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.tasks.await -import kotlinx.coroutines.withContext class CloudStorageManagerImpl : CloudStorageManager { - private val db by lazy { Firebase.firestore } - private var lastSync: Long = 0L override fun uploadSave(cloudSave: CloudSave) { - FirebaseFirestore.setLoggingEnabled(BuildConfig.DEBUG) - val data = cloudSave.toHashMap() - db.collection(SAVES) - .document(cloudSave.playId) - .set(data) - .addOnCompleteListener { - Log.v(TAG, "Cloud storage complete") - } - .addOnCanceledListener { - Log.v(TAG, "Cloud storage canceled") - } - .addOnFailureListener { - Log.e(TAG, "Cloud storage error", it) - } - .addOnSuccessListener { - Log.v(TAG, "Cloud storage success") - } + // Todo } override suspend fun getSave(playId: String): CloudSave? { - return if (System.currentTimeMillis() - lastSync > DateUtils.MINUTE_IN_MILLIS) { - lastSync = System.currentTimeMillis() - - runCatching { - withContext(Dispatchers.IO) { - db.collection(SAVES).document(playId).get().await().data?.let { - cloudSaveOf(playId, it.toMap()) - } - } - }.onFailure { - Log.e(TAG, "Fail to load save on cloud", it) - }.getOrNull() - } else { - null - } - } - - companion object { - val TAG = CloudStorageManagerImpl::class.simpleName - const val SAVES = "saves" + return null } } diff --git a/proprietary/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt b/proprietary/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt index 5bcbba901..ae5d83959 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/CrashReporterImpl.kt @@ -1,9 +1,14 @@ package dev.lucasnlm.external -import com.google.firebase.crashlytics.FirebaseCrashlytics +import android.app.Application +import com.bugsnag.android.Bugsnag class CrashReporterImpl : CrashReporter { override fun sendError(message: String) { - FirebaseCrashlytics.getInstance().recordException(Exception(message)) + // No-op + } + + override fun start(application: Application) { + Bugsnag.start(application.applicationContext) } } diff --git a/proprietary/src/main/java/dev/lucasnlm/external/ExternalAnalyticsWrapperImpl.kt b/proprietary/src/main/java/dev/lucasnlm/external/ExternalAnalyticsWrapperImpl.kt index 11d4ccd81..103cb299f 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/ExternalAnalyticsWrapperImpl.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/ExternalAnalyticsWrapperImpl.kt @@ -1,20 +1,13 @@ package dev.lucasnlm.external import android.content.Context -import android.os.Bundle import com.amplitude.api.Amplitude import com.amplitude.api.AmplitudeClient -import com.google.firebase.analytics.FirebaseAnalytics import org.json.JSONObject class ExternalAnalyticsWrapperImpl( private val context: Context, ) : ExternalAnalyticsWrapper { - private val firebaseAnalytics: FirebaseAnalytics? by lazy { - runCatching { - FirebaseAnalytics.getInstance(context) - }.getOrNull() - } private val amplitudeClient: AmplitudeClient? by lazy { runCatching { Amplitude @@ -28,10 +21,6 @@ class ExternalAnalyticsWrapperImpl( context: Context, properties: Map, ) { - properties.forEach { (key, value) -> - firebaseAnalytics?.setUserProperty(key, value) - } - amplitudeClient?.setUserProperties(JSONObject(properties)) } @@ -39,15 +28,6 @@ class ExternalAnalyticsWrapperImpl( name: String, content: Map, ) { - val bundle = - Bundle().apply { - putString(FirebaseAnalytics.Param.ITEM_NAME, name) - content.forEach { (key, value) -> - putString(key, value) - } - } - - firebaseAnalytics?.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle) amplitudeClient?.logEvent(name, JSONObject(content)) } } diff --git a/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt b/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt index 55ba7ff47..aad4110ce 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManagerImpl.kt @@ -1,130 +1,13 @@ package dev.lucasnlm.external -import android.util.Log -import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import dev.lucasnlm.antimine.proprietary.BuildConfig -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.tasks.await -import kotlinx.coroutines.withContext - -class FeatureFlagManagerImpl : FeatureFlagManager() { - private val defaultMap by lazy { - mapOf( - HISTORY_ENABLED to isGameHistoryEnabled, - RATE_US_ENABLED to isRateUsEnabled, - GAMEPLAY_EVENTS_ENABLED to isGameplayAnalyticsEnabled, - GAME_OVER_AD_ENABLED to isGameOverAdEnabled, - SHOW_ADS_ON_CONTINUE_ENABLED to isAdsOnContinueEnabled, - SHOW_ADS_ON_NEW_GAME_ENABLED to isAdsOnNewGameEnabled, - CONTINUE_ENABLED to isContinueGameEnabled, - MIN_USAGE_TO_REVIEW to minUsageToReview, - USE_INTERSTITIAL_AD to useInterstitialAd, - BANNER_AD_ENABLED to isBannerAdEnabled, - SHOW_COUNTDOWN_TO_CONTINUE to showCountdownToContinue, - ) - } - - private val remoteConfig: FirebaseRemoteConfig? by lazy { - runCatching { - FirebaseRemoteConfig.getInstance().apply { - setDefaultsAsync(defaultMap) - } - }.getOrNull() - } - - private fun getBoolean( - key: String, - default: Boolean, - ): Boolean { - return when { - BuildConfig.DEBUG -> default - else -> remoteConfig?.getBoolean(key) ?: default - } - } - - private fun getInt( - key: String, - default: Int, - ): Int { - return when { - BuildConfig.DEBUG -> default - else -> remoteConfig?.getLong(key)?.toInt() ?: default - } - } - +class FeatureFlagManagerImpl : FeatureFlagManager { override val isFoss: Boolean = false - override val isGameHistoryEnabled: Boolean by lazy { - getBoolean(HISTORY_ENABLED, false) - } - - override val isRateUsEnabled: Boolean by lazy { - getBoolean(RATE_US_ENABLED, true) - } - - override val isGameplayAnalyticsEnabled: Boolean by lazy { - getBoolean(GAMEPLAY_EVENTS_ENABLED, false) - } - - override val isGameOverAdEnabled: Boolean by lazy { - getBoolean(GAME_OVER_AD_ENABLED, true) - } - - override val isAdsOnContinueEnabled: Boolean by lazy { - getBoolean(SHOW_ADS_ON_CONTINUE_ENABLED, true) - } - - override val isAdsOnNewGameEnabled: Boolean by lazy { - getBoolean(SHOW_ADS_ON_NEW_GAME_ENABLED, true) - } - - override val isContinueGameEnabled: Boolean by lazy { - getBoolean(CONTINUE_ENABLED, true) - } - - override val minUsageToReview: Int by lazy { - getInt(MIN_USAGE_TO_REVIEW, DEFAULT_MIN_USAGE_TO_REVIEW) - } - - override val useInterstitialAd: Boolean by lazy { - getBoolean(USE_INTERSTITIAL_AD, true) - } - - override val isBannerAdEnabled: Boolean by lazy { - getBoolean(BANNER_AD_ENABLED, true) - } - - override val showCountdownToContinue: Boolean by lazy { - getBoolean(SHOW_COUNTDOWN_TO_CONTINUE, true) - } - - override suspend fun refresh() { - if (!BuildConfig.DEBUG && remoteConfig != null) { - withContext(Dispatchers.IO) { - runCatching { - remoteConfig?.fetchAndActivate()?.await() - }.onFailure { - Log.e(TAG, "Fail to fetch flags", it) - } - } - } - } + override val isGameHistoryEnabled: Boolean = false - companion object { - private val TAG = FeatureFlagManagerImpl::class.simpleName + override val useInterstitialAd: Boolean = true - private const val HISTORY_ENABLED = "history_enabled" - private const val RATE_US_ENABLED = "rate_us_enabled" - private const val GAMEPLAY_EVENTS_ENABLED = "gameplay_events_enabled" - private const val GAME_OVER_AD_ENABLED = "game_over_ad_enabled" - private const val SHOW_ADS_ON_CONTINUE_ENABLED = "ad_on_continue_enabled" - private const val SHOW_ADS_ON_NEW_GAME_ENABLED = "ad_on_new_game_enabled" - private const val CONTINUE_ENABLED = "continue_enabled" - private const val MIN_USAGE_TO_REVIEW = "min_usage_to_review" - private const val USE_INTERSTITIAL_AD = "use_interstitial_ad" - private const val BANNER_AD_ENABLED = "banner_ad_enabled" - private const val SHOW_COUNTDOWN_TO_CONTINUE = "show_countdown_to_continue" + override val isBannerAdEnabled: Boolean = true - private const val DEFAULT_MIN_USAGE_TO_REVIEW = 5 - } + override val showCountdownToContinue: Boolean = false } diff --git a/proprietary/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt b/proprietary/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt index 113b275a2..c153764ea 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/PlayGamesManagerImpl.kt @@ -11,10 +11,6 @@ import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.games.Games -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.PlayGamesAuthProvider -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase import kotlinx.coroutines.tasks.await class PlayGamesManagerImpl( @@ -23,9 +19,6 @@ class PlayGamesManagerImpl( ) : PlayGamesManager { private var account: GoogleSignInAccount? = null private var requestLogin: Boolean = true - private val firebaseAuth: FirebaseAuth by lazy { - Firebase.auth - } private fun setupPopUp( activity: Activity, @@ -179,28 +172,6 @@ class PlayGamesManagerImpl( return requestLogin } - override fun signInToFirebase(activity: Activity) { - val googleAccount = account - val serverAuthCode = googleAccount?.serverAuthCode - - if (googleAccount != null && serverAuthCode != null) { - val credential = PlayGamesAuthProvider.getCredential(serverAuthCode) - - firebaseAuth.signInWithCredential(credential) - .addOnCompleteListener(activity) { task -> - if (task.isSuccessful) { - Log.d(TAG, "signInWithCredential:success") - } else { - Log.w(TAG, "signInWithCredential:failure", task.exception) - } - } - .addOnFailureListener { - Log.e(TAG, "Fail to signIn with firebase", it) - crashReporter.sendError("Fail to signIn with firebase. $it") - } - } - } - companion object { val TAG = PlayGamesManagerImpl::class.simpleName }