mirror of https://github.com/tasks/tasks
Add PurchaseActivityViewModel
parent
93231f3c10
commit
04a7eef174
@ -1,138 +1,55 @@
|
|||||||
package org.tasks.billing
|
package org.tasks.billing
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.tasks.LocalBroadcastManager
|
|
||||||
import org.tasks.R
|
|
||||||
import org.tasks.analytics.Firebase
|
|
||||||
import org.tasks.compose.PurchaseText.SubscriptionScreen
|
import org.tasks.compose.PurchaseText.SubscriptionScreen
|
||||||
import org.tasks.extensions.Context.toast
|
import org.tasks.extensions.Context.findActivity
|
||||||
import org.tasks.preferences.Preferences
|
|
||||||
import org.tasks.themes.TasksTheme
|
import org.tasks.themes.TasksTheme
|
||||||
import org.tasks.themes.Theme
|
import org.tasks.themes.Theme
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class PurchaseActivity : AppCompatActivity(), OnPurchasesUpdated {
|
class PurchaseActivity : AppCompatActivity(), OnPurchasesUpdated {
|
||||||
@Inject lateinit var theme: Theme
|
@Inject lateinit var theme: Theme
|
||||||
@Inject lateinit var billingClient: BillingClient
|
|
||||||
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
|
|
||||||
@Inject lateinit var inventory: Inventory
|
|
||||||
@Inject lateinit var preferences: Preferences
|
|
||||||
@Inject lateinit var firebase: Firebase
|
|
||||||
|
|
||||||
private var currentSubscription: Purchase? = null
|
|
||||||
private val purchaseReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val nameYourPrice = mutableStateOf(false)
|
|
||||||
private val sliderPosition = mutableStateOf(-1f)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val github = intent?.extras?.getBoolean(EXTRA_GITHUB) ?: false
|
|
||||||
|
|
||||||
theme.applyToContext(this)
|
theme.applyToContext(this)
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
nameYourPrice.value = intent?.extras?.getBoolean(EXTRA_NAME_YOUR_PRICE) ?: firebase.nameYourPrice
|
|
||||||
} else {
|
|
||||||
nameYourPrice.value = savedInstanceState.getBoolean(EXTRA_NAME_YOUR_PRICE)
|
|
||||||
sliderPosition.value = savedInstanceState.getFloat(EXTRA_PRICE)
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
TasksTheme(theme = theme.themeBase.index) {
|
TasksTheme(theme = theme.themeBase.index) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
val viewModel: PurchaseActivityViewModel = viewModel()
|
||||||
|
val state = viewModel.viewState.collectAsStateWithLifecycle().value
|
||||||
|
val context = LocalContext.current
|
||||||
SubscriptionScreen(
|
SubscriptionScreen(
|
||||||
nameYourPrice = nameYourPrice,
|
nameYourPrice = state.nameYourPrice,
|
||||||
sliderPosition = sliderPosition,
|
sliderPosition = state.price,
|
||||||
github = github,
|
github = state.isGithub,
|
||||||
subscribe = this::purchase,
|
subscribe = { price, isMonthly ->
|
||||||
|
context.findActivity()?.let { viewModel.purchase(it, price, isMonthly) }
|
||||||
|
},
|
||||||
|
setPrice = { viewModel.setPrice(it) },
|
||||||
|
setNameYourPrice = { viewModel.setNameYourPrice(it) },
|
||||||
onBack = { finish() },
|
onBack = { finish() },
|
||||||
)
|
)
|
||||||
LaunchedEffect(key1 = Unit) {
|
|
||||||
firebase.logEvent(R.string.event_showed_purchase_dialog)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
|
|
||||||
localBroadcastManager.registerPurchaseReceiver(purchaseReceiver)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
try {
|
|
||||||
billingClient.queryPurchases(throwError = true)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
toast(e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
localBroadcastManager.unregisterReceiver(purchaseReceiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setup() {
|
|
||||||
currentSubscription = inventory.subscription.value
|
|
||||||
if (sliderPosition.value < 0) {
|
|
||||||
sliderPosition.value =
|
|
||||||
currentSubscription
|
|
||||||
?.subscriptionPrice
|
|
||||||
?.coerceAtMost(25)
|
|
||||||
?.toFloat() ?: 10f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun purchase(price: Int, monthly: Boolean) = lifecycleScope.launch {
|
|
||||||
val newSku = String.format(Locale.US, "%s_%02d", if (monthly) "monthly" else "annual", price)
|
|
||||||
try {
|
|
||||||
billingClient.initiatePurchaseFlow(
|
|
||||||
this@PurchaseActivity,
|
|
||||||
newSku,
|
|
||||||
BillingClientImpl.TYPE_SUBS,
|
|
||||||
currentSubscription?.takeIf { it.sku != newSku }
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
this@PurchaseActivity.toast(e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPurchasesUpdated(success: Boolean) {
|
override fun onPurchasesUpdated(success: Boolean) {
|
||||||
if (success) {
|
if (success) {
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
outState.putFloat(EXTRA_PRICE, sliderPosition.value)
|
|
||||||
outState.putBoolean(EXTRA_NAME_YOUR_PRICE, nameYourPrice.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val EXTRA_GITHUB = "extra_github"
|
|
||||||
private const val EXTRA_PRICE = "extra_price"
|
|
||||||
const val EXTRA_NAME_YOUR_PRICE = "extra_name_your_price"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
package org.tasks.billing
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.LocalBroadcastManager
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.analytics.Firebase
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class PurchaseActivityViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val inventory: Inventory,
|
||||||
|
private val billingClient: BillingClient,
|
||||||
|
private val localBroadcastManager: LocalBroadcastManager,
|
||||||
|
firebase: Firebase,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
data class ViewState(
|
||||||
|
val nameYourPrice: Boolean,
|
||||||
|
val isGithub: Boolean,
|
||||||
|
val price: Float = -1f,
|
||||||
|
val subscription: Purchase? = null,
|
||||||
|
val error: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val purchaseReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val subscription = inventory.subscription.value
|
||||||
|
_viewState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
subscription = subscription,
|
||||||
|
price = state.price.takeIf { it > 0 }
|
||||||
|
?: subscription?.subscriptionPrice?.coerceAtMost(25)?.toFloat()
|
||||||
|
?: 10f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrice(price: Float) {
|
||||||
|
_viewState.update { it.copy(price = price) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _viewState = MutableStateFlow(
|
||||||
|
ViewState(
|
||||||
|
nameYourPrice = savedStateHandle.get<Boolean>(EXTRA_NAME_YOUR_PRICE) ?: firebase.nameYourPrice,
|
||||||
|
isGithub = savedStateHandle.get<Boolean>(EXTRA_GITHUB) ?: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val viewState: StateFlow<ViewState> = _viewState
|
||||||
|
|
||||||
|
init {
|
||||||
|
localBroadcastManager.registerPurchaseReceiver(purchaseReceiver)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
billingClient.queryPurchases(throwError = true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_viewState.update {
|
||||||
|
it.copy(error = e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
firebase.logEvent(R.string.event_showed_purchase_dialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
localBroadcastManager.unregisterReceiver(purchaseReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun purchase(activity: Activity, price: Int, monthly: Boolean) = viewModelScope.launch {
|
||||||
|
val newSku = String.format(Locale.US, "%s_%02d", if (monthly) "monthly" else "annual", price)
|
||||||
|
try {
|
||||||
|
billingClient.initiatePurchaseFlow(
|
||||||
|
activity,
|
||||||
|
newSku,
|
||||||
|
BillingClientImpl.TYPE_SUBS,
|
||||||
|
_viewState.value.subscription?.takeIf { it.sku != newSku },
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_viewState.update {
|
||||||
|
it.copy(error = e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNameYourPrice(nameYourPrice: Boolean) {
|
||||||
|
_viewState.update { it.copy(nameYourPrice = nameYourPrice) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_GITHUB = "extra_github"
|
||||||
|
const val EXTRA_NAME_YOUR_PRICE = "extra_name_your_price"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue