diff --git a/app/src/main/kotlin/net/primal/android/navigation/NavigationArguments.kt b/app/src/main/kotlin/net/primal/android/navigation/NavigationArguments.kt index 9a31235b9..1f02e611e 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/NavigationArguments.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/NavigationArguments.kt @@ -28,6 +28,9 @@ const val RENDER_TYPE = "renderType" inline val SavedStateHandle.renderType: String get() = get(RENDER_TYPE) ?: throw IllegalArgumentException("Missing required renderType argument.") +const val EXTEND_EXISTING_PREMIUM_NAME = "extendExistingPremiumName" +inline val SavedStateHandle.extendExistingPremiumName: String? get() = get(EXTEND_EXISTING_PREMIUM_NAME) + const val INITIAL_QUERY = "initialQuery" inline val SavedStateHandle.initialQuery: String? get() = get(INITIAL_QUERY) diff --git a/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt b/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt index 6bea4e4a4..f52c073fa 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt @@ -249,6 +249,9 @@ fun NavController.navigateToExploreFeed( private fun NavController.navigateToBookmarks() = navigate(route = "bookmarks") private fun NavController.navigateToPremiumBuying() = navigate(route = "premium/buying") +private fun NavController.navigateToPremiumExtendSubscription(primalName: String) = + navigate(route = "premium/buying?$EXTEND_EXISTING_PREMIUM_NAME=$primalName") + private fun NavController.navigateToPremiumHome() = navigate(route = "premium/home") private fun NavController.navigateToPremiumSupportPrimal() = navigate(route = "premium/supportPrimal") private fun NavController.navigateToPremiumMoreInfo() = navigate(route = "premium/info") @@ -435,7 +438,16 @@ fun PrimalAppNavigation() { ), ) - premiumBuying(route = "premium/buying", navController = navController) + premiumBuying( + route = "premium/buying?$EXTEND_EXISTING_PREMIUM_NAME={$EXTEND_EXISTING_PREMIUM_NAME}", + arguments = listOf( + navArgument(EXTEND_EXISTING_PREMIUM_NAME) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) premiumHome(route = "premium/home", navController = navController) @@ -930,25 +942,29 @@ private fun NavGraphBuilder.advancedSearch( ) } -private fun NavGraphBuilder.premiumBuying(route: String, navController: NavController) = - composable( - route = route, - enterTransition = { primalSlideInHorizontallyFromEnd }, - exitTransition = { primalScaleOut }, - popEnterTransition = { primalScaleIn }, - popExitTransition = { primalSlideOutHorizontallyToEnd }, - ) { - val viewModel = hiltViewModel() +private fun NavGraphBuilder.premiumBuying( + route: String, + arguments: List, + navController: NavController, +) = composable( + route = route, + arguments = arguments, + enterTransition = { primalSlideInHorizontallyFromEnd }, + exitTransition = { primalScaleOut }, + popEnterTransition = { primalScaleIn }, + popExitTransition = { primalSlideOutHorizontallyToEnd }, +) { + val viewModel = hiltViewModel() - ApplyEdgeToEdge() - LockToOrientationPortrait() + ApplyEdgeToEdge() + LockToOrientationPortrait() - PremiumBuyingScreen( - viewModel = viewModel, - onClose = { navController.navigateUp() }, - onMoreInfoClick = { navController.navigateToPremiumMoreInfo() }, - ) - } + PremiumBuyingScreen( + viewModel = viewModel, + onClose = { navController.navigateUp() }, + onMoreInfoClick = { navController.navigateToPremiumMoreInfo() }, + ) +} private fun NavGraphBuilder.premiumHome(route: String, navController: NavController) = composable( @@ -987,7 +1003,7 @@ private fun NavGraphBuilder.premiumSupportPrimal(route: String, navController: N viewModel = viewModel, callbacks = SupportPrimalContract.ScreenCallbacks( onClose = { navController.navigateUp() }, - onBuySubscription = { navController.navigateToPremiumBuying() }, + onExtendSubscription = { navController.navigateToPremiumExtendSubscription(primalName = it) }, onBecomeLegend = { navController.navigateToPremiumBecomeLegend() }, ), ) 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 e54e1822f..1de66f57a 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 @@ -8,6 +8,7 @@ import net.primal.android.wallet.store.domain.SubscriptionProduct interface PremiumBuyingContract { data class UiState( val loading: Boolean = true, + val isExtendingPremium: Boolean = false, val subscriptions: List = emptyList(), val stage: PremiumStage = PremiumStage.Home, val primalName: String? = null, 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 c1f3cbf27..8924a3f83 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 @@ -121,11 +121,15 @@ private fun PremiumBuyingScreen( state = state, eventPublisher = eventPublisher, onBack = { - eventPublisher( - PremiumBuyingContract.UiEvent.MoveToPremiumStage( - PremiumBuyingContract.PremiumStage.FindPrimalName, - ), - ) + if (state.isExtendingPremium) { + onClose() + } else { + eventPublisher( + PremiumBuyingContract.UiEvent.MoveToPremiumStage( + PremiumBuyingContract.PremiumStage.FindPrimalName, + ), + ) + } }, onLearnMoreClick = onMoreInfoClick, ) 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 5dde3aade..950a5b2dc 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 @@ -1,5 +1,6 @@ package net.primal.android.premium.buying +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -13,7 +14,9 @@ import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.launch import net.primal.android.core.compose.profile.model.asProfileDetailsUi import net.primal.android.core.utils.isGoogleBuild +import net.primal.android.navigation.extendExistingPremiumName import net.primal.android.networking.sockets.errors.WssException +import net.primal.android.premium.buying.PremiumBuyingContract.PremiumStage import net.primal.android.premium.buying.PremiumBuyingContract.UiEvent import net.primal.android.premium.buying.PremiumBuyingContract.UiState import net.primal.android.premium.domain.MembershipError @@ -27,6 +30,7 @@ import timber.log.Timber @HiltViewModel class PremiumBuyingViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, private val primalBillingClient: PrimalBillingClient, private val premiumRepository: PremiumRepository, private val profileRepository: ProfileRepository, @@ -35,7 +39,13 @@ class PremiumBuyingViewModel @Inject constructor( private var purchase: SubscriptionPurchase? = null - private val _state = MutableStateFlow(UiState()) + private val _state = MutableStateFlow( + UiState( + isExtendingPremium = savedStateHandle.extendExistingPremiumName != null, + primalName = savedStateHandle.extendExistingPremiumName, + stage = if (savedStateHandle.extendExistingPremiumName != null) PremiumStage.Purchase else PremiumStage.Home, + ), + ) val state = _state.asStateFlow() private fun setState(reducer: UiState.() -> UiState) = _state.getAndUpdate { it.reducer() } @@ -108,7 +118,7 @@ class PremiumBuyingViewModel @Inject constructor( primalName = primalName, purchase = purchase, ) - setState { copy(stage = PremiumBuyingContract.PremiumStage.Success) } + setState { copy(stage = PremiumStage.Success) } } catch (error: WssException) { Timber.e(error) this@PremiumBuyingViewModel.purchase = purchase @@ -154,7 +164,7 @@ class PremiumBuyingViewModel @Inject constructor( primalName = primalName, purchase = existingPurchase, ) - setState { copy(stage = PremiumBuyingContract.PremiumStage.Success) } + setState { copy(stage = PremiumStage.Success) } premiumRepository.fetchMembershipStatus(userId = userId) } catch (error: WssException) { Timber.e(error) diff --git a/app/src/main/kotlin/net/primal/android/premium/buying/purchase/PremiumPurchaseStage.kt b/app/src/main/kotlin/net/primal/android/premium/buying/purchase/PremiumPurchaseStage.kt index e0b143e03..90aaad072 100644 --- a/app/src/main/kotlin/net/primal/android/premium/buying/purchase/PremiumPurchaseStage.kt +++ b/app/src/main/kotlin/net/primal/android/premium/buying/purchase/PremiumPurchaseStage.kt @@ -92,7 +92,11 @@ fun PremiumPurchaseStage( Scaffold( topBar = { PrimalTopAppBar( - title = stringResource(id = R.string.premium_purchase_title), + title = if (state.isExtendingPremium) { + stringResource(id = R.string.premium_extend_subscription_title) + } else { + stringResource(id = R.string.premium_purchase_title) + }, navigationIcon = PrimalIcons.ArrowBack, onNavigationIconClick = onBack, showDivider = false, @@ -120,17 +124,29 @@ fun PremiumPurchaseStage( internetIdentifierBadgeSize = 24.dp, fontSize = 20.sp, ) - Text( - modifier = Modifier.padding(horizontal = 12.dp), - text = stringResource(id = R.string.premium_purchase_primal_name_available), - style = AppTheme.typography.bodyLarge, - color = AppTheme.extraColorScheme.successBright, - ) + if (!state.isExtendingPremium) { + Text( + modifier = Modifier.padding(horizontal = 12.dp), + text = stringResource(id = R.string.premium_purchase_primal_name_available), + style = AppTheme.typography.bodyLarge, + color = AppTheme.extraColorScheme.successBright, + ) + } PremiumPrimalNameTable( primalName = state.primalName, ) } + if (state.isExtendingPremium) { + Text( + modifier = Modifier.padding(horizontal = 32.dp), + text = stringResource(id = R.string.premium_extend_subscription_notice), + style = AppTheme.typography.bodyMedium, + color = AppTheme.extraColorScheme.onSurfaceVariantAlt2, + textAlign = TextAlign.Center, + ) + } + MoreInfoPromoCodeRow( modifier = Modifier.padding(vertical = 8.dp), onLearnMoreClick = onLearnMoreClick, diff --git a/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalContract.kt b/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalContract.kt index 361444ec8..69a3a1d3a 100644 --- a/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalContract.kt +++ b/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalContract.kt @@ -3,13 +3,14 @@ package net.primal.android.premium.support interface SupportPrimalContract { data class UiState( + val primalName: String? = null, val hasMembership: Boolean = false, val isPrimalLegend: Boolean = false, ) data class ScreenCallbacks( val onClose: () -> Unit, - val onBuySubscription: () -> Unit, + val onExtendSubscription: (primalName: String) -> Unit, val onBecomeLegend: () -> Unit, ) } diff --git a/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalScreen.kt b/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalScreen.kt index 20aa4ddd1..1bf879b4e 100644 --- a/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalScreen.kt +++ b/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalScreen.kt @@ -98,14 +98,14 @@ private fun SupportPrimalScreen( Spacer(modifier = Modifier.height(20.dp)) - if (!state.hasMembership || true) { + if (!state.hasMembership) { SupportCard( modifier = Modifier.fillMaxSize(), painter = painterResource(R.drawable.support_primal_buy_subscription), title = stringResource(R.string.premium_support_primal_buy_subscription_title), description = stringResource(R.string.premium_support_primal_buy_subscription_description), buttonText = stringResource(R.string.premium_support_primal_buy_subscription_button_text), - onClick = callbacks.onBuySubscription, + onClick = { if (state.primalName != null) callbacks.onExtendSubscription(state.primalName) }, ) Spacer(modifier = Modifier.height(20.dp)) diff --git a/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalViewModel.kt b/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalViewModel.kt index 426f6351e..4786396ef 100644 --- a/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/premium/support/SupportPrimalViewModel.kt @@ -31,6 +31,7 @@ class SupportPrimalViewModel @Inject constructor( activeAccountStore.activeUserAccount.collect { setState { copy( + primalName = it.premiumMembership?.premiumName, isPrimalLegend = it.premiumMembership?.cohort2.isPrimalLegend(), hasMembership = it.premiumMembership != null && !it.premiumMembership.cohort2.isPremiumFree(), ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06a643617..4d6c1bdfc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -127,6 +127,9 @@ By proceeding you acknowledge that\nyou agree to our Terms of Service + Extend Subscription + Your subscription will be extended by the number of months you buy. + Success Success, payment received! Your subscription is now active!