From fc3fc8270c4b9ee53ad32630f88d4232a691397f Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Fri, 8 Nov 2024 15:05:15 +0100 Subject: [PATCH] Add error handler to buying subscription --- .../premium/buying/PremiumBuyingContract.kt | 5 + .../premium/buying/PremiumBuyingScreen.kt | 155 ++++++++++-------- .../premium/buying/PremiumBuyingViewModel.kt | 9 +- 3 files changed, 102 insertions(+), 67 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingContract.kt b/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingContract.kt index e5214b34..e54e1822 100644 --- a/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingContract.kt +++ b/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingContract.kt @@ -2,6 +2,7 @@ package net.primal.android.premium.buying import android.app.Activity import net.primal.android.core.compose.profile.model.ProfileDetailsUi +import net.primal.android.premium.domain.MembershipError import net.primal.android.wallet.store.domain.SubscriptionProduct interface PremiumBuyingContract { @@ -15,6 +16,8 @@ interface PremiumBuyingContract { val profile: ProfileDetailsUi? = null, val promoCodeValidity: Boolean? = null, val isCheckingPromoCodeValidity: Boolean = false, + + val error: MembershipError? = null, ) sealed class UiEvent { @@ -26,6 +29,8 @@ interface PremiumBuyingContract { data object RestoreSubscription : UiEvent() + data object DismissError : UiEvent() + data class RequestPurchase( val activity: Activity, val subscriptionProduct: SubscriptionProduct, diff --git a/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingScreen.kt b/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingScreen.kt index 264928c3..c1f3cbf2 100644 --- a/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingScreen.kt +++ b/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingScreen.kt @@ -7,17 +7,25 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import net.primal.android.R +import net.primal.android.core.compose.SnackbarErrorHandler import net.primal.android.premium.buying.home.PremiumBuyingHomeStage import net.primal.android.premium.buying.name.PremiumPrimalNameStage import net.primal.android.premium.buying.purchase.PremiumPurchaseStage import net.primal.android.premium.buying.success.PremiumBuyingSuccessStage +import net.primal.android.premium.ui.toHumanReadableString import net.primal.android.theme.AppTheme @Composable @@ -50,77 +58,92 @@ private fun PremiumBuyingScreen( eventPublisher = eventPublisher, ) - AnimatedContent( - modifier = Modifier - .background(AppTheme.colorScheme.surfaceVariant) - .fillMaxSize(), - label = "PremiumStages", - targetState = state.stage, - transitionSpec = { transitionSpecBetweenStages() }, - ) { stage -> - when (stage) { - PremiumBuyingContract.PremiumStage.Home -> { - PremiumBuyingHomeStage( - loading = state.loading, - subscriptions = state.subscriptions, - onClose = onClose, - onLearnMoreClick = onMoreInfoClick, - onFindPrimalName = { - eventPublisher( - PremiumBuyingContract.UiEvent.MoveToPremiumStage( - PremiumBuyingContract.PremiumStage.FindPrimalName, - ), - ) - }, - ) - } + val snackbarHostState = remember { SnackbarHostState() } + SnackbarErrorHandler( + error = state.error, + snackbarHostState = snackbarHostState, + errorMessageResolver = { it.toHumanReadableString() }, + onErrorDismiss = { eventPublisher(PremiumBuyingContract.UiEvent.DismissError) }, + ) - PremiumBuyingContract.PremiumStage.FindPrimalName -> { - PremiumPrimalNameStage( - titleText = stringResource(id = R.string.premium_primal_name_title), - initialName = state.primalName, - onBack = { - eventPublisher( - PremiumBuyingContract.UiEvent.MoveToPremiumStage( - PremiumBuyingContract.PremiumStage.Home, - ), - ) - }, - onPrimalNameAvailable = { - eventPublisher( - PremiumBuyingContract.UiEvent.SetPrimalName(primalName = it), - ) - eventPublisher( - PremiumBuyingContract.UiEvent.MoveToPremiumStage( - PremiumBuyingContract.PremiumStage.Purchase, - ), - ) - }, - ) - } + Box(contentAlignment = Alignment.BottomCenter) { + AnimatedContent( + modifier = Modifier + .background(AppTheme.colorScheme.surfaceVariant) + .fillMaxSize(), + label = "BuyingPremiumStages", + targetState = state.stage, + transitionSpec = { transitionSpecBetweenStages() }, + ) { stage -> + when (stage) { + PremiumBuyingContract.PremiumStage.Home -> { + PremiumBuyingHomeStage( + loading = state.loading, + subscriptions = state.subscriptions, + onClose = onClose, + onLearnMoreClick = onMoreInfoClick, + onFindPrimalName = { + eventPublisher( + PremiumBuyingContract.UiEvent.MoveToPremiumStage( + PremiumBuyingContract.PremiumStage.FindPrimalName, + ), + ) + }, + ) + } - PremiumBuyingContract.PremiumStage.Purchase -> { - PremiumPurchaseStage( - state = state, - eventPublisher = eventPublisher, - onBack = { - eventPublisher( - PremiumBuyingContract.UiEvent.MoveToPremiumStage( - PremiumBuyingContract.PremiumStage.FindPrimalName, - ), - ) - }, - onLearnMoreClick = onMoreInfoClick, - ) - } + PremiumBuyingContract.PremiumStage.FindPrimalName -> { + PremiumPrimalNameStage( + titleText = stringResource(id = R.string.premium_primal_name_title), + initialName = state.primalName, + onBack = { + eventPublisher( + PremiumBuyingContract.UiEvent.MoveToPremiumStage( + PremiumBuyingContract.PremiumStage.Home, + ), + ) + }, + onPrimalNameAvailable = { + eventPublisher( + PremiumBuyingContract.UiEvent.SetPrimalName(primalName = it), + ) + eventPublisher( + PremiumBuyingContract.UiEvent.MoveToPremiumStage( + PremiumBuyingContract.PremiumStage.Purchase, + ), + ) + }, + ) + } - PremiumBuyingContract.PremiumStage.Success -> { - PremiumBuyingSuccessStage( - modifier = Modifier.fillMaxSize(), - onDoneClick = onClose, - ) + PremiumBuyingContract.PremiumStage.Purchase -> { + PremiumPurchaseStage( + state = state, + eventPublisher = eventPublisher, + onBack = { + eventPublisher( + PremiumBuyingContract.UiEvent.MoveToPremiumStage( + PremiumBuyingContract.PremiumStage.FindPrimalName, + ), + ) + }, + onLearnMoreClick = onMoreInfoClick, + ) + } + + PremiumBuyingContract.PremiumStage.Success -> { + PremiumBuyingSuccessStage( + modifier = Modifier.fillMaxSize(), + onDoneClick = onClose, + ) + } } } + + SnackbarHost( + modifier = Modifier.navigationBarsPadding(), + hostState = snackbarHostState, + ) } } diff --git a/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingViewModel.kt b/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingViewModel.kt index fe385f0b..5dde3aad 100644 --- a/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/premium/buying/PremiumBuyingViewModel.kt @@ -16,6 +16,7 @@ import net.primal.android.core.utils.isGoogleBuild import net.primal.android.networking.sockets.errors.WssException import net.primal.android.premium.buying.PremiumBuyingContract.UiEvent import net.primal.android.premium.buying.PremiumBuyingContract.UiState +import net.primal.android.premium.domain.MembershipError import net.primal.android.premium.repository.PremiumRepository import net.primal.android.profile.repository.ProfileRepository import net.primal.android.user.accounts.active.ActiveAccountStore @@ -80,6 +81,7 @@ class PremiumBuyingViewModel @Inject constructor( UiEvent.ClearPromoCodeValidity -> setState { copy(promoCodeValidity = null) } is UiEvent.RequestPurchase -> launchBillingFlow(it) UiEvent.RestoreSubscription -> restorePurchase() + UiEvent.DismissError -> setState { copy(error = null) } } } } @@ -110,7 +112,12 @@ class PremiumBuyingViewModel @Inject constructor( } catch (error: WssException) { Timber.e(error) this@PremiumBuyingViewModel.purchase = purchase - setState { copy(hasActiveSubscription = true) } + setState { + copy( + hasActiveSubscription = true, + error = MembershipError.FailedToProcessSubscriptionPurchase(cause = error), + ) + } } } }