From 8765fe9bb7fd529e96a0f10790fd6f5c9ce177e1 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 20 Feb 2026 12:26:56 +1100 Subject: [PATCH] Adding new screen to represent the state of an "in progress" refund --- .../prosettings/ProSettingsHomeScreen.kt | 27 +++- .../prosettings/ProSettingsNavHost.kt | 13 ++ .../prosettings/ProSettingsViewModel.kt | 7 + .../prosettings/RefundInProgress.kt | 142 ++++++++++++++++++ 4 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundInProgress.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt index cffd9ed21a..0e644f57b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt @@ -599,7 +599,6 @@ fun ProSettings( ), subtitle = annotatedStringResource(subtitle), subtitleColor = subColor, - enabled = !refunding, endContent = { Box( modifier = Modifier.size(LocalDimensions.current.itemButtonIconSpacing) @@ -942,7 +941,9 @@ fun ProSettingsFooter( sendCommand: (ProSettingsViewModel.Commands) -> Unit, ) { // Manage Pro - Expired has this in the header so exclude it here - if(proStatus !is ProStatus.Expired) { + // We also don't want to show this while refund in process + val refunding = (proStatus as? ProStatus.Active)?.refundInProgress ?: false + if(proStatus !is ProStatus.Expired && !refunding) { Spacer(Modifier.height(LocalDimensions.current.smallSpacing)) ProManage( data = proStatus, @@ -1025,6 +1026,28 @@ fun PreviewProSettingsPro( } } +@Preview +@Composable +fun PreviewProSettingsProiOSRefund( + @PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors +) { + PreviewTheme(colors) { + ProSettingsHome( + data = ProSettingsViewModel.ProSettingsState( + proDataState = ProDataState( + type = previewAutoRenewingApple.copy(refundInProgress = true), + refreshState = State.Success(Unit), + showProBadge = true, + ), + ), + inSheet = false, + sendCommand = {}, + listState = rememberLazyListState(), + onBack = {}, + ) + } +} + @Preview @Composable fun PreviewProSettingsProLoading( diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt index cece2bb768..ef981b54d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt @@ -48,6 +48,10 @@ sealed interface ProSettingsDestination: Parcelable { @Serializable @Parcelize data object RefundSubscription: ProSettingsDestination + + @Serializable + @Parcelize + data object RefundInProgress: ProSettingsDestination } enum class ProNavHostCustomActions { @@ -184,6 +188,15 @@ fun ProSettingsNavHost( ) } + // Refund In Progress + horizontalSlideComposable { entry -> + val viewModel = navController.proGraphViewModel(entry, navigator) + RefundInProgressScreen( + viewModel = viewModel, + onBack = handleBack, + ) + } + // Cancellation horizontalSlideComposable { entry -> val viewModel = navController.proGraphViewModel(entry, navigator) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index 62fbf7e31b..ba32928537 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -500,6 +500,13 @@ class ProSettingsViewModel @AssistedInject constructor( // Not loading nor error. If in grace period show a dialog // otherwise go to the "choose plan" screen else -> { + // if we in the process of refunding on another platform, show that screen instead + if((_proSettingsUIState.value.proDataState.type as? ProStatus.Active)?.refundInProgress == true){ + navigateTo(ProSettingsDestination.RefundInProgress) + return + } + + // otherwise handle the "Choose Plan" val provider = (_proSettingsUIState.value.proDataState.type as? ProStatus.Active)?.providerData if(_proSettingsUIState.value.inGracePeriod){ _dialogState.update { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundInProgress.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundInProgress.kt new file mode 100644 index 0000000000..ef2178699a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundInProgress.kt @@ -0,0 +1,142 @@ +package org.thoughtcrime.securesms.preferences.prosettings + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import com.squareup.phrase.Phrase +import network.loki.messenger.R +import org.session.libsession.utilities.NonTranslatableStringConstants +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.ICON_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.PLATFORM_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY +import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog +import org.thoughtcrime.securesms.pro.ProStatus +import org.thoughtcrime.securesms.pro.getPlatformDisplayName +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple +import org.thoughtcrime.securesms.ui.components.annotatedStringResource +import org.thoughtcrime.securesms.ui.components.iconExternalLink +import org.thoughtcrime.securesms.ui.components.inlineContentMap +import org.thoughtcrime.securesms.ui.theme.LocalColors +import org.thoughtcrime.securesms.ui.theme.LocalDimensions +import org.thoughtcrime.securesms.ui.theme.LocalType +import org.thoughtcrime.securesms.ui.theme.PreviewTheme +import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider +import org.thoughtcrime.securesms.ui.theme.ThemeColors +import org.thoughtcrime.securesms.ui.theme.bold + + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun RefundInProgressScreen( + viewModel: ProSettingsViewModel, + onBack: () -> Unit, +) { + val state by viewModel.proSettingsUIState.collectAsState() + val activePlan = state.proDataState.type as? ProStatus.Active ?: return + + RefundInProgress( + subscription = activePlan, + sendCommand = viewModel::onCommand, + onBack = onBack, + ) +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) +@Composable +fun RefundInProgress( + subscription: ProStatus.Active, + sendCommand: (ProSettingsViewModel.Commands) -> Unit, + onBack: () -> Unit, +){ + val context = LocalContext.current + + BaseCellButtonProSettingsScreen( + disabled = true, + onBack = onBack, + buttonText = stringResource(R.string.theReturn), + dangerButton = false, + onButtonClick = onBack, + title = stringResource(R.string.proRequestedRefund), + ){ + Column { + Text( + text = stringResource(R.string.nextSteps), + style = LocalType.current.base.bold(), + color = LocalColors.current.text, + ) + + Spacer(Modifier.height(LocalDimensions.current.xxxsSpacing)) + + Text( + text = annotatedStringResource( + Phrase.from(context.getText(R.string.proRefundNextSteps)) + .put(PLATFORM_KEY, subscription.providerData.getPlatformDisplayName()) + .put(PRO_KEY, NonTranslatableStringConstants.PRO) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format() + ), + style = LocalType.current.base, + color = LocalColors.current.text, + ) + + Spacer(Modifier.height(LocalDimensions.current.smallSpacing)) + + Text( + text = stringResource(R.string.helpSupport), + style = LocalType.current.base.bold(), + color = LocalColors.current.text, + ) + + Spacer(Modifier.height(LocalDimensions.current.xxxsSpacing)) + + Text( + modifier = Modifier.clickable( + onClick = { + sendCommand(ShowOpenUrlDialog(subscription.providerData.refundSupportUrl)) + } + ), + text = annotatedStringResource( + Phrase.from(context.getText(R.string.proRefundSupport)) + .put(PLATFORM_KEY, subscription.providerData.getPlatformDisplayName()) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .put(ICON_KEY, iconExternalLink) + .format() + ), + style = LocalType.current.base, + color = LocalColors.current.text, + inlineContent = inlineContentMap( + textSize = LocalType.current.small.fontSize, + imageColor = LocalColors.current.accentText + ) + ) + } + } +} + +@Preview +@Composable +private fun PreviewUpdatePlan( + @PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors +) { + PreviewTheme(colors) { + RefundInProgress ( + subscription = previewAutoRenewingApple, + sendCommand = {}, + onBack = {}, + ) + } +} \ No newline at end of file