diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt index 8fbd3b265b..3ca93c1080 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt @@ -4,7 +4,6 @@ import android.content.ClipboardManager import android.content.Context import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsEnabled -import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createEmptyComposeRule @@ -103,9 +102,6 @@ class MessageBackupsCheckoutActivityTest { state = InAppPaymentTable.State.END ) ) - - composeTestRule.waitForIdle() - composeTestRule.onNodeWithTag("dialog-circular-progress-indicator").assertIsNotDisplayed() } @Test diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InAppPaymentsRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InAppPaymentsRule.kt index 24a45210c1..118eb4f726 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InAppPaymentsRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InAppPaymentsRule.kt @@ -19,6 +19,7 @@ class InAppPaymentsRule : ExternalResource() { override fun before() { initialiseConfigurationResponse() initialisePutSubscription() + initialiseSetArchiveBackupId() } private fun initialiseConfigurationResponse() { @@ -39,4 +40,12 @@ class InAppPaymentsRule : ExternalResource() { } ) } + + private fun initialiseSetArchiveBackupId() { + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Put("/v1/archives/backupid") { + MockResponse().success() + } + ) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index e30942f6f3..74553ce711 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -142,6 +142,16 @@ object BackupRepository { } } + /** + * Triggers backup id reservation. As documented, this is safe to perform multiple times. + */ + @WorkerThread + fun triggerBackupIdReservation(): NetworkResult { + val messageBackupKey = SignalStore.backup.messageBackupKey + val mediaRootBackupKey = SignalStore.backup.mediaRootBackupKey + return SignalNetwork.archive.triggerBackupIdReservation(messageBackupKey, mediaRootBackupKey, SignalStore.account.requireAci()) + } + /** * Refreshes backup via server */ 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 42f40036f9..c19af70664 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 @@ -14,12 +14,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.core.os.bundleOf import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.coroutines.rx3.asFlowable +import org.signal.core.ui.Dialogs import org.signal.core.util.getSerializableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.backup.v2.MessageBackupTier @@ -121,6 +123,7 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega composable(route = MessageBackupsStage.Route.TYPE_SELECTION.name) { MessageBackupsTypeSelectionScreen( stage = state.stage, + paymentReadyState = state.paymentReadyState, currentBackupTier = state.currentMessageBackupTier, selectedBackupTier = state.selectedMessageBackupTier, availableBackupTypes = state.availableBackupTypes, @@ -152,6 +155,14 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega requireActivity().finishAfterTransition() } } + + if (state.paymentReadyState == MessageBackupsFlowState.PaymentReadyState.FAILED) { + Dialogs.SimpleMessageDialog( + message = stringResource(R.string.MessageBackupsFlowFragment__a_network_failure_occurred), + dismiss = stringResource(android.R.string.ok), + onDismiss = { requireActivity().finishAfterTransition() } + ) + } } override fun onUserLaunchedAnExternalApplication() = error("Not supported by this fragment.") 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 db3fc95e08..ae7088746d 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 @@ -18,5 +18,12 @@ data class MessageBackupsFlowState( val startScreen: MessageBackupsStage, val stage: MessageBackupsStage = startScreen, val accountEntropyPool: AccountEntropyPool = SignalStore.account.accountEntropyPool, - val failure: Throwable? = null -) + val failure: Throwable? = null, + val paymentReadyState: PaymentReadyState = PaymentReadyState.NOT_READY +) { + enum class PaymentReadyState { + NOT_READY, + READY, + FAILED + } +} 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 f3b10ba767..d07bf280ec 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 @@ -69,6 +69,22 @@ class MessageBackupsFlowViewModel( init { check(SignalStore.backup.backupTier != MessageBackupTier.PAID) { "This screen does not support cancellation or downgrades." } + viewModelScope.launch { + val result = withContext(Dispatchers.IO) { + BackupRepository.triggerBackupIdReservation() + } + + result.runIfSuccessful { + Log.d(TAG, "Successfully triggered backup id reservation.") + internalStateFlow.update { it.copy(paymentReadyState = MessageBackupsFlowState.PaymentReadyState.READY) } + } + + result.runOnStatusCodeError { + Log.d(TAG, "Failed to trigger backup id reservation. ($it)") + internalStateFlow.update { it.copy(paymentReadyState = MessageBackupsFlowState.PaymentReadyState.FAILED) } + } + } + viewModelScope.launch { internalStateFlow.update { it.copy( diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt index 9f8687f288..9dceca3b4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt @@ -70,6 +70,7 @@ import org.signal.core.ui.R as CoreUiR @Composable fun MessageBackupsTypeSelectionScreen( stage: MessageBackupsStage, + paymentReadyState: MessageBackupsFlowState.PaymentReadyState, currentBackupTier: MessageBackupTier?, selectedBackupTier: MessageBackupTier?, availableBackupTypes: List, @@ -160,7 +161,7 @@ fun MessageBackupsTypeSelectionScreen( Buttons.LargePrimary( onClick = onNextClicked, - enabled = selectedBackupTier != currentBackupTier && selectedBackupTier != null, + enabled = selectedBackupTier != currentBackupTier && selectedBackupTier != null && paymentReadyState == MessageBackupsFlowState.PaymentReadyState.READY, modifier = Modifier .fillMaxWidth() .padding(vertical = if (hasCurrentBackupTier) 10.dp else 16.dp) @@ -200,7 +201,8 @@ private fun MessageBackupsTypeSelectionScreenPreview() { onNavigationClick = {}, onReadMoreClicked = {}, onNextClicked = {}, - currentBackupTier = null + currentBackupTier = null, + paymentReadyState = MessageBackupsFlowState.PaymentReadyState.READY ) } } @@ -219,7 +221,8 @@ private fun MessageBackupsTypeSelectionScreenWithCurrentTierPreview() { onNavigationClick = {}, onReadMoreClicked = {}, onNextClicked = {}, - currentBackupTier = MessageBackupTier.PAID + currentBackupTier = MessageBackupTier.PAID, + paymentReadyState = MessageBackupsFlowState.PaymentReadyState.READY ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e29113cd8d..8c98036bdd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8047,6 +8047,10 @@ Contact support + + + A network failure occurred. Please try again later. + Back up your messages and media and using Signal\'s secure, end-to-end encrypted storage service. Never lose a message when you get a new phone or reinstall Signal.