Show price
This commit is contained in:
parent
4edb1b0a61
commit
dd0cc81a42
14 changed files with 124 additions and 50 deletions
|
@ -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<FrameLayout>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<string name="tutorial_completed">Tutorial Concluído</string>
|
||||
<string name="tutorial_0_bottom">Para começar dê um %1$s em qualquer quadrado aleatório.</string>
|
||||
<string name="tutorial_1_top">Os números indicam quantas minas existem adjacentes ao quadrado destacado.</string>
|
||||
<string name="tutorial_1_bottom">Portanto, se o canto 1️⃣ tem apenas um quadrado adjacente, ele deve ter uma minha. Use %1$s para sinalizá-lo.</string>
|
||||
<string name="tutorial_1_bottom">Portanto, se o canto 1️⃣ tem apenas um quadrado adjacente, ele deve ter uma mina. Use %1$s para sinalizá-lo.</string>
|
||||
<string name="tutorial_2_top">O próximo quadrado fechado não tem uma mina porque o número é 1️⃣ , não 2️⃣ .</string>
|
||||
<string name="tutorial_2_bottom">Você pode abri-lo com %1$s.</string>
|
||||
<string name="tutorial_3_top">O mesmo acontece nesse 1️⃣ . Você sabe onde está a mina adjacente.</string>
|
||||
|
|
|
@ -8,5 +8,6 @@ interface IBillingManager {
|
|||
fun start()
|
||||
fun isEnabled(): Boolean
|
||||
suspend fun charge(activity: Activity)
|
||||
fun getPrice(): Flow<String>
|
||||
fun listenPurchases(): Flow<PurchaseInfo>
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ package dev.lucasnlm.external
|
|||
|
||||
interface IFeatureFlagManager {
|
||||
fun isGameHistoryEnabled(): Boolean
|
||||
fun isRateUsEnabled(): Boolean
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ class BillingManager(
|
|||
activity.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun getPrice(): Flow<String> = flowOf()
|
||||
|
||||
override fun listenPurchases(): Flow<PurchaseInfo> = flowOf()
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -2,4 +2,5 @@ package dev.lucasnlm.external
|
|||
|
||||
class FeatureFlagManager : IFeatureFlagManager {
|
||||
override fun isGameHistoryEnabled(): Boolean = true
|
||||
override fun isRateUsEnabled(): Boolean = false
|
||||
}
|
||||
|
|
|
@ -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<PurchaseInfo>()
|
||||
|
||||
private var unlockPrice = MutableStateFlow<String?>(null)
|
||||
|
||||
private val billingClient by lazy {
|
||||
BillingClient.newBuilder(context)
|
||||
.setListener(this)
|
||||
|
@ -29,6 +34,8 @@ class BillingManager(
|
|||
.build()
|
||||
}
|
||||
|
||||
override fun getPrice(): Flow<String> = unlockPrice.asSharedFlow().filterNotNull()
|
||||
|
||||
override fun listenPurchases(): Flow<PurchaseInfo> = purchaseBroadcaster.asFlow()
|
||||
|
||||
private fun handlePurchases(purchases: List<Purchase>) {
|
||||
|
@ -61,17 +68,23 @@ class BillingManager(
|
|||
}
|
||||
|
||||
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
||||
val purchasesList: List<Purchase> = 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<Purchase> = billingClient
|
||||
.queryPurchases(BillingClient.SkuType.INAPP)
|
||||
.purchasesList.let { it?.toList() ?: listOf() }
|
||||
|
||||
handlePurchases(purchasesList)
|
||||
}
|
||||
|
||||
handlePurchases(purchasesList)
|
||||
}
|
||||
|
||||
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
|
||||
|
@ -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()
|
||||
|
|
|
@ -2,4 +2,5 @@ package dev.lucasnlm.external
|
|||
|
||||
class FeatureFlagManager : IFeatureFlagManager {
|
||||
override fun isGameHistoryEnabled(): Boolean = false
|
||||
override fun isRateUsEnabled(): Boolean = true
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue