From dd0cc81a429a888fe566bb46066745a386035bd4 Mon Sep 17 00:00:00 2001 From: Lucas Lima Date: Wed, 16 Dec 2020 16:43:01 -0300 Subject: [PATCH] Show price --- .../dev/lucasnlm/antimine/GameActivity.kt | 54 +++++++++++++++---- .../lucasnlm/antimine/about/AboutActivity.kt | 2 +- .../about/views/info/AboutInfoFragment.kt | 8 +-- .../splash/viewmodel/SplashViewModel.kt | 6 ++- .../lucasnlm/antimine/support/IapHandler.kt | 16 +++--- .../support/SupportAppDialogFragment.kt | 24 ++++++--- .../lucasnlm/antimine/theme/ThemeActivity.kt | 15 +++++- common/src/main/res/values-pt-rBR/strings.xml | 2 +- .../dev/lucasnlm/external/IBillingManager.kt | 1 + .../lucasnlm/external/IFeatureFlagManager.kt | 1 + .../dev/lucasnlm/external/BillingManager.kt | 2 + .../lucasnlm/external/FeatureFlagManager.kt | 1 + .../dev/lucasnlm/external/BillingManager.kt | 41 +++++++++----- .../lucasnlm/external/FeatureFlagManager.kt | 1 + 14 files changed, 124 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt index 7f1e046f..ce8840c3 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt @@ -10,6 +10,7 @@ import android.view.Gravity import android.view.View import android.view.WindowManager import android.widget.FrameLayout +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AlertDialog @@ -68,6 +69,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import kotlinx.coroutines.launch import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.singleOrNull import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import java.lang.Exception @@ -121,6 +123,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O bindNavigationMenu() bindSwitchControlButton() bindAds() + bindPrice() findViewById(R.id.levelContainer).doOnLayout { if (!isFinishing) { @@ -488,14 +491,11 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O 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() if (!playGamesManager.hasGooglePlayGames()) { removeGroup(R.id.play_games_group) } - - // New Features - findItem(R.id.themes).setActionView(R.layout.new_icon) - findItem(R.id.tutorial).setActionView(R.layout.new_icon) } } @@ -899,6 +899,36 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O } } + private fun bindPrice() { + if (billingManager.isEnabled() && !preferencesRepository.isPremiumEnabled()) { + billingManager.start() + + lifecycleScope.launchWhenResumed { + billingManager.getPrice().collect { price -> + if (price.isNotBlank()) { + try { + navigationView.menu.findItem(R.id.remove_ads).apply { + actionView = TextView(baseContext).apply { + text = price + gravity = Gravity.CENTER_VERTICAL + setTextColor(ContextCompat.getColor(context, R.color.mines_around_2)) + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + ).apply { + gravity = Gravity.CENTER + } + } + } + } catch (e: Exception) { + Log.e(TAG, "Fail to create price text") + } + } + } + } + } + } + private fun refreshAds() { val isTutorialComplete = preferencesRepository.isTutorialCompleted() if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) { @@ -946,12 +976,16 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null && !instantAppManager.isEnabled(this) ) { - if (billingManager.isEnabled()) { - SupportAppDialogFragment.newRemoveAdsSupportDialog() - .show(supportFragmentManager, SupportAppDialogFragment.TAG) - } else { - SupportAppDialogFragment.newRequestSupportDialog() - .show(supportFragmentManager, SupportAppDialogFragment.TAG) + lifecycleScope.launch { + if (billingManager.isEnabled()) { + SupportAppDialogFragment.newRemoveAdsSupportDialog( + applicationContext, + billingManager.getPrice().singleOrNull() + ).show(supportFragmentManager, SupportAppDialogFragment.TAG) + } else { + SupportAppDialogFragment.newRequestSupportDialog(applicationContext) + .show(supportFragmentManager, SupportAppDialogFragment.TAG) + } } } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt index 402b1f01..eab647d2 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt @@ -53,7 +53,7 @@ class AboutActivity : ThematicActivity(R.layout.activity_empty) { private fun showSupportAppDialog() { if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null) { - SupportAppDialogFragment.newRequestSupportDialog() + SupportAppDialogFragment.newRequestSupportDialog(applicationContext) .show(supportFragmentManager, SupportAppDialogFragment.TAG) } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/about/views/info/AboutInfoFragment.kt b/app/src/main/java/dev/lucasnlm/antimine/about/views/info/AboutInfoFragment.kt index f3ee9425..542718f3 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/about/views/info/AboutInfoFragment.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/about/views/info/AboutInfoFragment.kt @@ -10,6 +10,7 @@ import dev.lucasnlm.antimine.R import dev.lucasnlm.antimine.about.viewmodel.AboutEvent import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository +import dev.lucasnlm.external.IBillingManager import kotlinx.android.synthetic.main.fragment_about_info.* import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel @@ -17,6 +18,7 @@ import org.koin.androidx.viewmodel.ext.android.sharedViewModel class AboutInfoFragment : Fragment(R.layout.fragment_about_info) { private val aboutViewModel: AboutViewModel by sharedViewModel() private val preferencesRepository: IPreferencesRepository by inject() + private val billingManager: IBillingManager by inject() override fun onResume() { super.onResume() @@ -38,12 +40,12 @@ class AboutInfoFragment : Fragment(R.layout.fragment_about_info) { } } - if (preferencesRepository.showSupport()) { + if (preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) { + supportUs.visibility = View.GONE + } else { supportUs.setOnClickListener { aboutViewModel.sendEvent(AboutEvent.SupportUs) } - } else { - supportUs.visibility = View.GONE } thirdsParties.setOnClickListener { diff --git a/app/src/main/java/dev/lucasnlm/antimine/splash/viewmodel/SplashViewModel.kt b/app/src/main/java/dev/lucasnlm/antimine/splash/viewmodel/SplashViewModel.kt index c76d9a2e..985ddeb0 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/splash/viewmodel/SplashViewModel.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/splash/viewmodel/SplashViewModel.kt @@ -2,6 +2,7 @@ package dev.lucasnlm.antimine.splash.viewmodel import android.util.Log import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dev.lucasnlm.antimine.common.level.database.models.Stats import dev.lucasnlm.antimine.common.level.repository.IStatsRepository import dev.lucasnlm.antimine.core.control.ControlStyle @@ -9,6 +10,7 @@ import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository import dev.lucasnlm.antimine.support.IapHandler import dev.lucasnlm.external.ICloudStorageManager import dev.lucasnlm.external.model.CloudSave +import kotlinx.coroutines.launch class SplashViewModel( private val preferencesRepository: IPreferencesRepository, @@ -17,7 +19,9 @@ class SplashViewModel( private val iapHandler: IapHandler, ) : ViewModel() { fun startIap() { - iapHandler.start() + viewModelScope.launch { + iapHandler.start() + } } suspend fun migrateCloudSave(playGamesId: String) { diff --git a/app/src/main/java/dev/lucasnlm/antimine/support/IapHandler.kt b/app/src/main/java/dev/lucasnlm/antimine/support/IapHandler.kt index f31744de..7543d943 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/support/IapHandler.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/support/IapHandler.kt @@ -7,9 +7,7 @@ import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository import dev.lucasnlm.external.IBillingManager import dev.lucasnlm.external.model.PurchaseInfo import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class IapHandler( @@ -17,14 +15,12 @@ class IapHandler( private val preferencesManager: IPreferencesRepository, private val billingManager: IBillingManager, ) { - fun start() { - GlobalScope.launch { - billingManager.listenPurchases().collect { - if (it is PurchaseInfo.PurchaseResult) { - onLockStatusChanged(it.unlockStatus) - } else { - showFailToConnectFeedback() - } + suspend fun start() { + billingManager.listenPurchases().collect { + if (it is PurchaseInfo.PurchaseResult) { + onLockStatusChanged(it.unlockStatus) + } else { + showFailToConnectFeedback() } } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/support/SupportAppDialogFragment.kt b/app/src/main/java/dev/lucasnlm/antimine/support/SupportAppDialogFragment.kt index 12752bdd..21f54d54 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/support/SupportAppDialogFragment.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/support/SupportAppDialogFragment.kt @@ -1,6 +1,8 @@ package dev.lucasnlm.antimine.support +import android.annotation.SuppressLint import android.app.Dialog +import android.content.Context import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater @@ -27,7 +29,7 @@ class SupportAppDialogFragment : AppCompatDialogFragment() { private val adsManager: IAdsManager by inject() private val instantAppManager: InstantAppManager by inject() - private var unlockMessage: Int = R.string.support_action + private lateinit var unlockMessage: String private var targetThemeId: Long = -1L private var isInstantMode: Boolean = true @@ -40,11 +42,13 @@ class SupportAppDialogFragment : AppCompatDialogFragment() { analyticsManager.sentEvent(Analytics.ShowIapDialog) unlockMessage = - (arguments?.getInt(UNLOCK_LABEL) ?: savedInstanceState?.getInt(UNLOCK_LABEL)) ?: R.string.support_action + (arguments?.getString(UNLOCK_LABEL) ?: savedInstanceState?.getString(UNLOCK_LABEL)) + ?: getString(R.string.support_action) targetThemeId = (arguments?.getLong(TARGET_THEME_ID, -1L)) ?: -1L } + @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return AlertDialog.Builder(requireContext()).apply { val view = LayoutInflater @@ -148,26 +152,30 @@ class SupportAppDialogFragment : AppCompatDialogFragment() { private const val UNLOCK_LABEL = "support_unlock_label" private const val TARGET_THEME_ID = "target_theme_id" - fun newRequestSupportDialog(): SupportAppDialogFragment { + fun newRequestSupportDialog(context: Context): SupportAppDialogFragment { return SupportAppDialogFragment().apply { arguments = Bundle().apply { - putInt(UNLOCK_LABEL, R.string.support_action) + putString(UNLOCK_LABEL, context.getString(R.string.support_action)) } } } - fun newRemoveAdsSupportDialog(): SupportAppDialogFragment { + fun newRemoveAdsSupportDialog(context: Context, price: String?): SupportAppDialogFragment { return SupportAppDialogFragment().apply { + val label = context.getString(R.string.remove_ad) + val unlockLabel = price?.let { "$label - $it" } ?: label arguments = Bundle().apply { - putInt(UNLOCK_LABEL, R.string.remove_ad) + putString(UNLOCK_LABEL, unlockLabel) } } } - fun newChangeThemeDialog(targetThemeId: Long): SupportAppDialogFragment { + fun newChangeThemeDialog(context: Context, targetThemeId: Long, price: String?): SupportAppDialogFragment { return SupportAppDialogFragment().apply { + val label = context.getString(R.string.unlock_all) + val unlockLabel = price?.let { "$label - $it" } ?: label arguments = Bundle().apply { - putInt(UNLOCK_LABEL, R.string.unlock_all) + putString(UNLOCK_LABEL, unlockLabel) putLong(TARGET_THEME_ID, targetThemeId) } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/theme/ThemeActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/theme/ThemeActivity.kt index 8d1a0df8..66c11fba 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/theme/ThemeActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/theme/ThemeActivity.kt @@ -15,8 +15,11 @@ import dev.lucasnlm.antimine.support.SupportAppDialogFragment import dev.lucasnlm.antimine.theme.view.ThemeAdapter import dev.lucasnlm.antimine.theme.viewmodel.ThemeEvent import dev.lucasnlm.antimine.theme.viewmodel.ThemeViewModel +import dev.lucasnlm.external.IBillingManager +import kotlinx.android.synthetic.main.activity_game.* import kotlinx.android.synthetic.main.activity_theme.* import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel @@ -30,6 +33,8 @@ class ThemeActivity : ThematicActivity(R.layout.activity_theme) { private val preferencesRepository: IPreferencesRepository by inject() + private val billingManager: IBillingManager by inject() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launchWhenCreated { @@ -90,8 +95,14 @@ class ThemeActivity : ThematicActivity(R.layout.activity_theme) { private fun showUnlockDialog(themeId: Long) { if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null) { - SupportAppDialogFragment.newChangeThemeDialog(themeId).apply { - show(supportFragmentManager, SupportAppDialogFragment.TAG) + lifecycleScope.launch { + SupportAppDialogFragment.newChangeThemeDialog( + applicationContext, + themeId, + billingManager.getPrice().singleOrNull() + ).apply { + show(supportFragmentManager, SupportAppDialogFragment.TAG) + } } } } diff --git a/common/src/main/res/values-pt-rBR/strings.xml b/common/src/main/res/values-pt-rBR/strings.xml index fee4545d..85772e32 100644 --- a/common/src/main/res/values-pt-rBR/strings.xml +++ b/common/src/main/res/values-pt-rBR/strings.xml @@ -4,7 +4,7 @@ Tutorial Concluído Para começar dê um %1$s em qualquer quadrado aleatório. Os números indicam quantas minas existem adjacentes ao quadrado destacado. - Portanto, se o canto 1️⃣ tem apenas um quadrado adjacente, ele deve ter uma minha. Use %1$s para sinalizá-lo. + Portanto, se o canto 1️⃣ tem apenas um quadrado adjacente, ele deve ter uma mina. Use %1$s para sinalizá-lo. O próximo quadrado fechado não tem uma mina porque o número é 1️⃣ , não 2️⃣ . Você pode abri-lo com %1$s. O mesmo acontece nesse 1️⃣ . Você sabe onde está a mina adjacente. diff --git a/external/src/main/java/dev/lucasnlm/external/IBillingManager.kt b/external/src/main/java/dev/lucasnlm/external/IBillingManager.kt index cb84cc42..eadea052 100644 --- a/external/src/main/java/dev/lucasnlm/external/IBillingManager.kt +++ b/external/src/main/java/dev/lucasnlm/external/IBillingManager.kt @@ -8,5 +8,6 @@ interface IBillingManager { fun start() fun isEnabled(): Boolean suspend fun charge(activity: Activity) + fun getPrice(): Flow fun listenPurchases(): Flow } diff --git a/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt b/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt index 9f41c931..2b843615 100644 --- a/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt +++ b/external/src/main/java/dev/lucasnlm/external/IFeatureFlagManager.kt @@ -2,4 +2,5 @@ package dev.lucasnlm.external interface IFeatureFlagManager { fun isGameHistoryEnabled(): Boolean + fun isRateUsEnabled(): Boolean } diff --git a/foss/src/main/java/dev/lucasnlm/external/BillingManager.kt b/foss/src/main/java/dev/lucasnlm/external/BillingManager.kt index b7331d91..cbf07dda 100644 --- a/foss/src/main/java/dev/lucasnlm/external/BillingManager.kt +++ b/foss/src/main/java/dev/lucasnlm/external/BillingManager.kt @@ -23,6 +23,8 @@ class BillingManager( activity.startActivity(intent) } + override fun getPrice(): Flow = flowOf() + override fun listenPurchases(): Flow = flowOf() companion object { diff --git a/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt b/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt index a71f9e8f..40b6cf35 100644 --- a/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt +++ b/foss/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt @@ -2,4 +2,5 @@ package dev.lucasnlm.external class FeatureFlagManager : IFeatureFlagManager { override fun isGameHistoryEnabled(): Boolean = true + override fun isRateUsEnabled(): Boolean = false } diff --git a/proprietary/src/main/java/dev/lucasnlm/external/BillingManager.kt b/proprietary/src/main/java/dev/lucasnlm/external/BillingManager.kt index cb1224b9..877e82c0 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/BillingManager.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/BillingManager.kt @@ -14,7 +14,10 @@ import com.android.billingclient.api.querySkuDetails import dev.lucasnlm.external.model.PurchaseInfo import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.filterNotNull class BillingManager( private val context: Context, @@ -22,6 +25,8 @@ class BillingManager( private val purchaseBroadcaster = ConflatedBroadcastChannel() + private var unlockPrice = MutableStateFlow(null) + private val billingClient by lazy { BillingClient.newBuilder(context) .setListener(this) @@ -29,6 +34,8 @@ class BillingManager( .build() } + override fun getPrice(): Flow = unlockPrice.asSharedFlow().filterNotNull() + override fun listenPurchases(): Flow = purchaseBroadcaster.asFlow() private fun handlePurchases(purchases: List) { @@ -61,17 +68,23 @@ class BillingManager( } override fun onBillingSetupFinished(billingResult: BillingResult) { - val purchasesList: List = if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { - // The BillingClient is ready. You can query purchases here. + if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { + val skuDetailsParams = SkuDetailsParams.newBuilder() + .setSkusList(listOf(BASIC_SUPPORT)) + .setType(BillingClient.SkuType.INAPP) + .build() - billingClient.queryPurchases(BillingClient.SkuType.INAPP).purchasesList.let { - it?.toList() ?: listOf() - } - } else { - listOf() + billingClient + .querySkuDetailsAsync(skuDetailsParams) { _, list -> + unlockPrice.tryEmit(list?.firstOrNull()?.price) + } + + val purchasesList: List = billingClient + .queryPurchases(BillingClient.SkuType.INAPP) + .purchasesList.let { it?.toList() ?: listOf() } + + handlePurchases(purchasesList) } - - handlePurchases(purchasesList) } override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList?) { @@ -94,12 +107,12 @@ class BillingManager( } override suspend fun charge(activity: Activity) { - val skuDetailsParams = SkuDetailsParams.newBuilder() - .setSkusList(listOf(BASIC_SUPPORT)) - .setType(BillingClient.SkuType.INAPP) - .build() - if (billingClient.isReady) { + val skuDetailsParams = SkuDetailsParams.newBuilder() + .setSkusList(listOf(BASIC_SUPPORT)) + .setType(BillingClient.SkuType.INAPP) + .build() + val details = billingClient.querySkuDetails(skuDetailsParams) details.skuDetailsList?.firstOrNull()?.let { val flowParams = BillingFlowParams.newBuilder() diff --git a/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt b/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt index f4c47c31..7c3f285c 100644 --- a/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt +++ b/proprietary/src/main/java/dev/lucasnlm/external/FeatureFlagManager.kt @@ -2,4 +2,5 @@ package dev.lucasnlm.external class FeatureFlagManager : IFeatureFlagManager { override fun isGameHistoryEnabled(): Boolean = false + override fun isRateUsEnabled(): Boolean = true }