Skip to content

Commit

Permalink
Implement purchase flow
Browse files Browse the repository at this point in the history
  • Loading branch information
abdrasulov committed Dec 12, 2024
1 parent 6b2b08e commit a23b51f
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory {
else -> signingInfo?.signingCertificateHistory // Send one with signingCertificateHistory
}
} else {
@Suppress("DEPRECATION")
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fun NavController.paidAction(paidAction: IPaidAction, block: () -> Unit) {
R.id.buySubscriptionFragment,
BuySubscriptionFragment.Input(paidAction)
) {

block.invoke()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.horizontalsystems.bankwallet.modules.usersubscription

import android.os.Parcelable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import io.horizontalsystems.bankwallet.core.BaseComposeFragment
import io.horizontalsystems.bankwallet.core.requireInput
import io.horizontalsystems.bankwallet.core.setNavigationResultX
import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme
import io.horizontalsystems.bankwallet.ui.compose.components.AppBar
import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryDefault
import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton
import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning
import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer
import io.horizontalsystems.core.SnackbarDuration
import io.horizontalsystems.core.helpers.HudHelper
import io.horizontalsystems.subscriptions.core.BasePlan
import kotlinx.coroutines.delay
import kotlinx.parcelize.Parcelize

class BuySubscriptionChoosePlanFragment : BaseComposeFragment() {

@Composable
override fun GetContent(navController: NavController) {
BuySubscriptionChoosePlanScreen(navController, requireActivity(), navController.requireInput())
}

@Parcelize
data class Input(val subscriptionId: String) : Parcelable

@Parcelize
class Result : Parcelable
}

@Composable
fun BuySubscriptionChoosePlanScreen(
navController: NavController,
activity: FragmentActivity,
input: BuySubscriptionChoosePlanFragment.Input
) {
val viewModel = viewModel<BuySubscriptionChoosePlanViewModel>(
factory = BuySubscriptionChoosePlanViewModel.Factory(input.subscriptionId)
)

val view = LocalView.current

val uiState = viewModel.uiState

LaunchedEffect(uiState.purchase) {
uiState.purchase?.let { purchase ->
HudHelper.showSuccessMessage(view, purchase.toString(), SnackbarDuration.LONG)

delay(300)

navController.setNavigationResultX(BuySubscriptionChoosePlanFragment.Result())
navController.popBackStack()
}
}

Scaffold(
backgroundColor = ComposeAppTheme.colors.tyler,
topBar = {
AppBar(
title = "Subscriptions",
navigationIcon = {
HsBackButton(onClick = { navController.popBackStack() })
},
)
}
) {
Column(modifier = Modifier.padding(it)) {
uiState.basePlans.forEach { basePlan ->
ButtonPrimaryDefault(
modifier = Modifier.fillMaxWidth(),
title = basePlan.stringRepresentation(),
onClick = {
viewModel.launchPurchaseFlow(basePlan.id, activity)
},
enabled = uiState.choosePlanEnabled
)
VSpacer(height = 12.dp)
}

uiState.error?.let {
TextImportantWarning(text = it.message ?: it.javaClass.name)
}
}
}
}

fun BasePlan.stringRepresentation(): String {
return pricingPhases.map {
"${it.formattedPrice}/${it.billingPeriod}"
}.joinToString(" then ")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.horizontalsystems.bankwallet.modules.usersubscription

import android.app.Activity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import io.horizontalsystems.bankwallet.core.ViewModelUiState
import io.horizontalsystems.subscriptions.core.BasePlan
import io.horizontalsystems.subscriptions.core.HSPurchase
import io.horizontalsystems.subscriptions.core.UserSubscriptionManager
import kotlinx.coroutines.launch

class BuySubscriptionChoosePlanViewModel(private val subscriptionId: String) : ViewModelUiState<BuySubscriptionChoosePlanUiState>() {
private var basePlans: List<BasePlan> = listOf()
private var purchaseInProgress = false
private var purchase: HSPurchase? = null
private var error: Throwable? = null

init {
viewModelScope.launch {
basePlans = UserSubscriptionManager.getBasePlans(subscriptionId)

emitState()
}
}

override fun createState() = BuySubscriptionChoosePlanUiState(
basePlans = basePlans,
purchaseInProgress = purchaseInProgress,
error = error,
purchase = purchase
)

fun launchPurchaseFlow(planId: String, activity: Activity) {
purchaseInProgress = true
error = null
emitState()

viewModelScope.launch {
try {
val hsPurchase =
UserSubscriptionManager.launchPurchaseFlow(subscriptionId, planId, activity)

purchase = hsPurchase
} catch (e: Throwable) {
error = e
}

purchaseInProgress = false
emitState()
}
}

class Factory(private val subscriptionId: String) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return BuySubscriptionChoosePlanViewModel(subscriptionId) as T
}
}
}

data class BuySubscriptionChoosePlanUiState(
val basePlans: List<BasePlan>,
val purchaseInProgress: Boolean,
val error: Throwable?,
val purchase: HSPurchase?
) {
val choosePlanEnabled = !purchaseInProgress
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.BaseComposeFragment
import io.horizontalsystems.bankwallet.core.setNavigationResultX
import io.horizontalsystems.bankwallet.core.slideFromRightForResult
import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme
import io.horizontalsystems.bankwallet.ui.compose.components.AppBar
import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryDefault
Expand All @@ -30,7 +33,7 @@ class BuySubscriptionFragment : BaseComposeFragment() {
data class Input(val action: IPaidAction) : Parcelable

@Parcelize
data class Result(val result: Boolean) : Parcelable
class Result : Parcelable
}

@Composable
Expand All @@ -39,7 +42,7 @@ private fun BuySubscriptionScreen(navController: NavController, activity: Fragme

val uiState = viewModel.uiState

val plans = uiState.plans
val subscriptions = uiState.subscriptions

Scaffold(
backgroundColor = ComposeAppTheme.colors.tyler,
Expand All @@ -53,12 +56,18 @@ private fun BuySubscriptionScreen(navController: NavController, activity: Fragme
}
) {
Column(modifier = Modifier.padding(it)) {
plans.forEach { plan ->
subscriptions.forEach { subscription ->
ButtonPrimaryDefault(
modifier = Modifier.fillMaxWidth(),
title = plan.name,
title = subscription.name,
onClick = {
viewModel.launchPurchaseFlow(plan.id, activity)
navController.slideFromRightForResult<BuySubscriptionChoosePlanFragment.Result>(
R.id.buySubscriptionChoosePlanFragment,
BuySubscriptionChoosePlanFragment.Input(subscription.id)
) {
navController.setNavigationResultX(BuySubscriptionFragment.Result())
navController.popBackStack()
}
}
)
VSpacer(height = 12.dp)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
package io.horizontalsystems.bankwallet.modules.usersubscription

import android.app.Activity
import androidx.lifecycle.viewModelScope
import io.horizontalsystems.bankwallet.core.ViewModelUiState
import io.horizontalsystems.subscriptions.core.SubscriptionPlan
import io.horizontalsystems.subscriptions.core.Subscription
import io.horizontalsystems.subscriptions.core.UserSubscriptionManager
import kotlinx.coroutines.launch

class BuySubscriptionViewModel : ViewModelUiState<BuySubscriptionUiState>() {
private var plans = listOf<SubscriptionPlan>()

override fun createState() = BuySubscriptionUiState(
plans = plans
)

fun launchPurchaseFlow(planId: String, activity: Activity) {
UserSubscriptionManager.launchPurchaseFlow(planId, activity)
}
private var subscriptions = listOf<Subscription>()

init {
viewModelScope.launch {
plans = UserSubscriptionManager.getPlans()
subscriptions = UserSubscriptionManager.getSubscriptions()

emitState()
}
}

override fun createState() = BuySubscriptionUiState(
subscriptions = subscriptions
)
}

data class BuySubscriptionUiState(val plans: List<SubscriptionPlan>)
data class BuySubscriptionUiState(
val subscriptions: List<Subscription>
)
3 changes: 3 additions & 0 deletions app/src/main/res/navigation/main_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -400,4 +400,7 @@
<fragment
android:id="@+id/buySubscriptionFragment"
android:name="io.horizontalsystems.bankwallet.modules.usersubscription.BuySubscriptionFragment"/>
<fragment
android:id="@+id/buySubscriptionChoosePlanFragment"
android:name="io.horizontalsystems.bankwallet.modules.usersubscription.BuySubscriptionChoosePlanFragment"/>
</navigation>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.horizontalsystems.subscriptions.core

data class HSPurchase(
val status: Status
) {
enum class Status {
Pending, Purchased
}
}

data class HSPurchaseFailure(
val code: String,
override val message: String,
) : Exception()
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.horizontalsystems.subscriptions.core

data class Subscription(val id: String, val name: String, val description: String)

data class BasePlan(
val id: String,
val pricingPhases: List<PricingPhase>,
)

data class PricingPhase(val formattedPrice: String, val billingPeriod: String)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Activity

interface SubscriptionService {
fun isActionAllowed(paidAction: IPaidAction): Boolean
suspend fun getPlans(): List<SubscriptionPlan>
fun launchPurchaseFlow(planId: String, activity: Activity)
suspend fun getSubscriptions(): List<Subscription>
suspend fun launchPurchaseFlow(subscriptionId: String, planId: String, activity: Activity): HSPurchase?
fun getBasePlans(subscriptionId: String): List<BasePlan>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ object UserSubscriptionManager {
return service.isActionAllowed(paidAction)
}

suspend fun getPlans(): List<SubscriptionPlan> {
return service.getPlans()
suspend fun getSubscriptions(): List<Subscription> {
return service.getSubscriptions()
}

fun launchPurchaseFlow(planId: String, activity: Activity) {
service.launchPurchaseFlow(planId, activity)
suspend fun launchPurchaseFlow(subscriptionId: String, planId: String, activity: Activity): HSPurchase? {
return service.launchPurchaseFlow(subscriptionId, planId, activity)
}

fun getBasePlans(subscriptionId: String): List<BasePlan> {
return service.getBasePlans(subscriptionId)
}
}
Loading

0 comments on commit a23b51f

Please sign in to comment.