Add cancellation support and renew support for canceled subscriptions.

This commit is contained in:
Alex Hart
2025-05-15 16:21:12 -03:00
committed by Cody Henthorne
parent 1aa2c85edd
commit fc5aae34f3
5 changed files with 70 additions and 23 deletions

View File

@@ -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()

View File

@@ -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<MessageBackupsType> = emptyList(),
val inAppPayment: InAppPaymentTable.InAppPayment? = null,
val startScreen: MessageBackupsStage,

View File

@@ -65,8 +65,6 @@ class MessageBackupsFlowViewModel(
val stateFlow: StateFlow<MessageBackupsFlowState> = 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.
*/

View File

@@ -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

View File

@@ -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
)