From fc5aae34f3867e706d7b798f69e7cd2d0eddfdd2 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 15 May 2025 16:21:12 -0300 Subject: [PATCH] Add cancellation support and renew support for canceled subscriptions. --- .../MessageBackupsFlowFragment.kt | 5 ++ .../subscription/MessageBackupsFlowState.kt | 2 +- .../MessageBackupsFlowViewModel.kt | 30 +++++++++++- .../remote/RemoteBackupsSettingsFragment.kt | 48 ++++++++++++------- .../remote/RemoteBackupsSettingsViewModel.kt | 8 ++-- 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt index 62d0228389..f551af155d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt @@ -68,6 +68,11 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega ) } + override fun onResume() { + super.onResume() + viewModel.refreshCurrentTier() + } + @Composable override fun FragmentContent() { val state by viewModel.stateFlow.collectAsStateWithLifecycle() diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt index ae7088746d..4f289e1b00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt @@ -12,7 +12,7 @@ import org.whispersystems.signalservice.api.AccountEntropyPool data class MessageBackupsFlowState( val selectedMessageBackupTier: MessageBackupTier? = SignalStore.backup.backupTier, - val currentMessageBackupTier: MessageBackupTier? = SignalStore.backup.backupTier, + val currentMessageBackupTier: MessageBackupTier? = null, val availableBackupTypes: List = emptyList(), val inAppPayment: InAppPaymentTable.InAppPayment? = null, val startScreen: MessageBackupsStage, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt index f3a92d4754..1454252d93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt @@ -65,8 +65,6 @@ class MessageBackupsFlowViewModel( val stateFlow: StateFlow = internalStateFlow init { - check(SignalStore.backup.backupTier != MessageBackupTier.PAID) { "This screen does not support cancellation or downgrades." } - viewModelScope.launch { val result = withContext(SignalDispatchers.IO) { BackupRepository.triggerBackupIdReservation() @@ -159,6 +157,34 @@ class MessageBackupsFlowViewModel( } } + fun refreshCurrentTier() { + val tier = SignalStore.backup.backupTier + if (tier == MessageBackupTier.PAID) { + Log.d(TAG, "Checking active subscription object for paid status.") + viewModelScope.launch { + val activeSubscription = withContext(SignalDispatchers.IO) { + RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) + } + + activeSubscription.onSuccess { + if (it.isCanceled) { + Log.d(TAG, "Active subscription is cancelled. Clearing tier.") + internalStateFlow.update { it.copy(currentMessageBackupTier = null) } + } else if (it.isActive) { + Log.d(TAG, "Active subscription is active. Setting tier.") + internalStateFlow.update { it.copy(currentMessageBackupTier = SignalStore.backup.backupTier) } + } else { + Log.w(TAG, "Subscription is inactive. Clearing tier.") + internalStateFlow.update { it.copy(currentMessageBackupTier = null) } + } + } + } + } else { + Log.d(TAG, "User is on tier: $tier") + internalStateFlow.update { it.copy(currentMessageBackupTier = tier) } + } + } + /** * Go to the next stage of the pipeline, based off of the current stage and state data. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt index 1fc24906dd..92cb1e246a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt @@ -810,28 +810,44 @@ private fun BackupCard( ) } - val buttonText = when (messageBackupsType) { - is MessageBackupsType.Paid -> stringResource(R.string.RemoteBackupsSettingsFragment__manage_or_cancel) - is MessageBackupsType.Free -> stringResource(R.string.RemoteBackupsSettingsFragment__upgrade) - } - if (backupState.isActive()) { - Buttons.LargeTonal( - onClick = { onBackupTypeActionButtonClicked(messageBackupsType.tier) }, - colors = ButtonDefaults.filledTonalButtonColors().copy( - containerColor = SignalTheme.colors.colorTransparent5, - contentColor = colorResource(R.color.signal_light_colorOnSurface) - ), - modifier = Modifier.padding(top = 12.dp) - ) { - Text( - text = buttonText - ) + val buttonText = when (messageBackupsType) { + is MessageBackupsType.Paid -> stringResource(R.string.RemoteBackupsSettingsFragment__manage_or_cancel) + is MessageBackupsType.Free -> stringResource(R.string.RemoteBackupsSettingsFragment__upgrade) } + + CallToActionButton( + text = buttonText, + onClick = { onBackupTypeActionButtonClicked(messageBackupsType.tier) } + ) + } else if (backupState is RemoteBackupsSettingsState.BackupState.Canceled) { + CallToActionButton( + text = stringResource(R.string.RemoteBackupsSettingsFragment__renew), + onClick = { onBackupTypeActionButtonClicked(MessageBackupTier.FREE) } + ) } } } +@Composable +private fun CallToActionButton( + text: String, + onClick: () -> Unit +) { + Buttons.LargeTonal( + onClick = onClick, + colors = ButtonDefaults.filledTonalButtonColors().copy( + containerColor = SignalTheme.colors.colorTransparent5, + contentColor = colorResource(R.color.signal_light_colorOnSurface) + ), + modifier = Modifier.padding(top = 12.dp) + ) { + Text( + text = text + ) + } +} + @Composable private fun RedemptionErrorAlert( onDetailsClick: () -> Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt index e0685e4a19..cef3949d27 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt @@ -321,19 +321,19 @@ class RemoteBackupsSettingsViewModel : ViewModel() { val subscription = activeSubscription.getOrThrow().activeSubscription if (subscription != null) { - Log.d(TAG, "Subscription found. Updating UI state with subscription details.") + Log.d(TAG, "Subscription found. Updating UI state with subscription details. Status: ${subscription.status}") _state.update { it.copy( hasRedemptionError = lastPurchase?.data?.error?.data_ == "409", backupState = when { - subscription.isActive -> RemoteBackupsSettingsState.BackupState.ActivePaid( + subscription.isCanceled && subscription.isActive -> RemoteBackupsSettingsState.BackupState.Canceled( messageBackupsType = type, - price = FiatMoney.fromSignalNetworkAmount(subscription.amount, Currency.getInstance(subscription.currency)), renewalTime = subscription.endOfCurrentPeriod.seconds ) - subscription.isCanceled -> RemoteBackupsSettingsState.BackupState.Canceled( + subscription.isActive -> RemoteBackupsSettingsState.BackupState.ActivePaid( messageBackupsType = type, + price = FiatMoney.fromSignalNetworkAmount(subscription.amount, Currency.getInstance(subscription.currency)), renewalTime = subscription.endOfCurrentPeriod.seconds )