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 82baed10c0..bf89862e9c 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 @@ -125,6 +125,11 @@ object BackupRepository { } } + @JvmStatic + fun skipMediaRestore() { + // TODO [backups] -- Clear the error as necessary + } + /** * Whether the yellow dot should be displayed on the conversation list avatar. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertBottomSheet.kt index 660597835f..699cf15b56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertBottomSheet.kt @@ -6,7 +6,7 @@ package org.thoughtcrime.securesms.backup.v2.ui import android.os.Parcelable -import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -37,6 +37,7 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat import androidx.core.os.BundleCompat import androidx.core.os.bundleOf import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -46,6 +47,7 @@ import org.signal.core.ui.Buttons import org.signal.core.ui.Previews import org.signal.core.ui.SignalPreview import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.backup.v2.BackupRepository import org.thoughtcrime.securesms.billing.launchManageBackupsSubscription import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment @@ -63,6 +65,7 @@ class BackupAlertBottomSheet : ComposeBottomSheetDialogFragment() { companion object { private const val ARG_ALERT = "alert" + @JvmStatic fun create(backupAlert: BackupAlert): BackupAlertBottomSheet { return BackupAlertBottomSheet().apply { arguments = bundleOf(ARG_ALERT to backupAlert) @@ -94,17 +97,17 @@ class BackupAlertBottomSheet : ComposeBottomSheetDialogFragment() { @Stable private fun performPrimaryAction() { when (backupAlert) { - BackupAlert.COULD_NOT_COMPLETE_BACKUP -> { + BackupAlert.CouldNotCompleteBackup -> { BackupMessagesJob.enqueue() startActivity(AppSettingsActivity.remoteBackups(requireContext())) } - BackupAlert.PAYMENT_PROCESSING -> launchManageBackupsSubscription() - BackupAlert.MEDIA_BACKUPS_ARE_OFF, BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> { + BackupAlert.FailedToRenew -> launchManageBackupsSubscription() + BackupAlert.MediaBackupsAreOff, BackupAlert.MediaWillBeDeletedToday -> { performFullMediaDownload() } - BackupAlert.DISK_FULL -> Unit + is BackupAlert.DiskFull -> Unit } dismissAllowingStateLoss() @@ -113,20 +116,22 @@ class BackupAlertBottomSheet : ComposeBottomSheetDialogFragment() { @Stable private fun performSecondaryAction() { when (backupAlert) { - BackupAlert.COULD_NOT_COMPLETE_BACKUP -> { + BackupAlert.CouldNotCompleteBackup -> { // TODO [backups] - Dismiss and notify later } - BackupAlert.PAYMENT_PROCESSING -> Unit - BackupAlert.MEDIA_BACKUPS_ARE_OFF -> { + BackupAlert.FailedToRenew -> Unit + BackupAlert.MediaBackupsAreOff -> { // TODO [backups] - Silence and remind on last day } - BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> { + BackupAlert.MediaWillBeDeletedToday -> { displayLastChanceDialog() } - BackupAlert.DISK_FULL -> Unit + is BackupAlert.DiskFull -> { + displaySkipRestoreDialog() + } } dismissAllowingStateLoss() @@ -143,6 +148,23 @@ class BackupAlertBottomSheet : ComposeBottomSheetDialogFragment() { .show() } + private fun displaySkipRestoreDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle((R.string.BackupAlertBottomSheet__skip_restore_question)) + .setMessage(R.string.BackupAlertBottomSheet__if_you_skip_restore) + .setPositiveButton(R.string.BackupAlertBottomSheet__skip) { _, _ -> + BackupRepository.skipMediaRestore() + } + .setNegativeButton(android.R.string.cancel, null) + .create() + .apply { + setOnShowListener { + getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.signal_colorError)) + } + } + .show() + } + private fun performFullMediaDownload() { // TODO [backups] -- We need to force this to download everything AppDependencies.jobManager.add(BackupRestoreMediaJob()) @@ -167,7 +189,7 @@ private fun BackupAlertSheetContent( Spacer(modifier = Modifier.size(26.dp)) when (backupAlert) { - BackupAlert.PAYMENT_PROCESSING, BackupAlert.MEDIA_BACKUPS_ARE_OFF -> { + BackupAlert.FailedToRenew, BackupAlert.MediaBackupsAreOff -> { Box { Image( painter = painterResource(id = R.drawable.image_signal_backups), @@ -200,24 +222,21 @@ private fun BackupAlertSheetContent( } Text( - text = stringResource(id = rememberTitleResource(backupAlert = backupAlert)), + text = titleString(backupAlert = backupAlert), style = MaterialTheme.typography.titleLarge, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) ) when (backupAlert) { - BackupAlert.COULD_NOT_COMPLETE_BACKUP -> CouldNotCompleteBackup( + BackupAlert.CouldNotCompleteBackup -> CouldNotCompleteBackup( daysSinceLastBackup = 7 // TODO [backups] ) - BackupAlert.PAYMENT_PROCESSING -> PaymentProcessingBody() - BackupAlert.MEDIA_BACKUPS_ARE_OFF -> MediaBackupsAreOffBody(30) // TODO [backups] -- Get this value from backend - BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> MediaWillBeDeletedTodayBody() - BackupAlert.DISK_FULL -> DiskFullBody( - requiredSpace = "12 GB", // TODO [backups] Where does this value come from? - daysUntilDeletion = 30 // TODO [backups] Where does this value come from? - ) + BackupAlert.FailedToRenew -> PaymentProcessingBody() + BackupAlert.MediaBackupsAreOff -> MediaBackupsAreOffBody(30) // TODO [backups] -- Get this value from backend + BackupAlert.MediaWillBeDeletedToday -> MediaWillBeDeletedTodayBody() + is BackupAlert.DiskFull -> DiskFullBody(requiredSpace = backupAlert.requiredSpace) } val secondaryActionResource = rememberSecondaryActionResource(backupAlert = backupAlert) @@ -299,19 +318,16 @@ private fun MediaWillBeDeletedTodayBody() { } @Composable -private fun DiskFullBody( - requiredSpace: String, - daysUntilDeletion: Long -) { +private fun DiskFullBody(requiredSpace: String) { Text( - text = stringResource(id = R.string.BackupAlertBottomSheet__your_device_does_not_have_enough_free_space, requiredSpace), + text = stringResource(id = R.string.BackupAlertBottomSheet__to_finish_downloading_your_signal_backup, requiredSpace), textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(bottom = 24.dp) ) Text( - text = pluralStringResource(id = R.plurals.BackupAlertBottomSheet__if_you_choose_skip, daysUntilDeletion.toInt(), daysUntilDeletion), // TODO [backups] Learn More link + text = stringResource(R.string.BackupAlertBottomSheet__to_free_up_space_offload), textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(bottom = 36.dp) @@ -322,24 +338,21 @@ private fun DiskFullBody( private fun rememberBackupsIconColors(backupAlert: BackupAlert): BackupsIconColors { return remember(backupAlert) { when (backupAlert) { - BackupAlert.PAYMENT_PROCESSING, BackupAlert.MEDIA_BACKUPS_ARE_OFF -> error("Not icon-based options.") - BackupAlert.COULD_NOT_COMPLETE_BACKUP, BackupAlert.DISK_FULL -> BackupsIconColors.Warning - BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> BackupsIconColors.Error + BackupAlert.FailedToRenew, BackupAlert.MediaBackupsAreOff -> error("Not icon-based options.") + BackupAlert.CouldNotCompleteBackup, is BackupAlert.DiskFull -> BackupsIconColors.Warning + BackupAlert.MediaWillBeDeletedToday -> BackupsIconColors.Error } } } @Composable -@StringRes -private fun rememberTitleResource(backupAlert: BackupAlert): Int { - return remember(backupAlert) { - when (backupAlert) { - BackupAlert.COULD_NOT_COMPLETE_BACKUP -> R.string.BackupAlertBottomSheet__couldnt_complete_backup - BackupAlert.PAYMENT_PROCESSING -> R.string.BackupAlertBottomSheet__your_backups_subscription_failed_to_renew - BackupAlert.MEDIA_BACKUPS_ARE_OFF -> R.string.BackupAlertBottomSheet__your_backups_subscription_expired - BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> R.string.BackupAlertBottomSheet__your_media_will_be_deleted_today - BackupAlert.DISK_FULL -> R.string.BackupAlertBottomSheet__cant_complete_download - } +private fun titleString(backupAlert: BackupAlert): String { + return when (backupAlert) { + BackupAlert.CouldNotCompleteBackup -> stringResource(R.string.BackupAlertBottomSheet__couldnt_complete_backup) + BackupAlert.FailedToRenew -> stringResource(R.string.BackupAlertBottomSheet__your_backups_subscription_failed_to_renew) + BackupAlert.MediaBackupsAreOff -> stringResource(R.string.BackupAlertBottomSheet__your_backups_subscription_expired) + BackupAlert.MediaWillBeDeletedToday -> stringResource(R.string.BackupAlertBottomSheet__your_media_will_be_deleted_today) + is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__free_up_s_on_this_device, backupAlert.requiredSpace) } } @@ -349,11 +362,11 @@ private fun primaryActionString( pricePerMonth: String ): String { return when (backupAlert) { - BackupAlert.COULD_NOT_COMPLETE_BACKUP -> stringResource(R.string.BackupAlertBottomSheet__back_up_now) - BackupAlert.PAYMENT_PROCESSING -> stringResource(R.string.BackupAlertBottomSheet__manage_subscription) - BackupAlert.MEDIA_BACKUPS_ARE_OFF -> stringResource(R.string.BackupAlertBottomSheet__subscribe_for_s_month, pricePerMonth) - BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> stringResource(R.string.BackupAlertBottomSheet__download_media_now) - BackupAlert.DISK_FULL -> stringResource(android.R.string.ok) + BackupAlert.CouldNotCompleteBackup -> stringResource(R.string.BackupAlertBottomSheet__back_up_now) + BackupAlert.FailedToRenew -> stringResource(R.string.BackupAlertBottomSheet__manage_subscription) + BackupAlert.MediaBackupsAreOff -> stringResource(R.string.BackupAlertBottomSheet__subscribe_for_s_month, pricePerMonth) + BackupAlert.MediaWillBeDeletedToday -> stringResource(R.string.BackupAlertBottomSheet__download_media_now) + is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__got_it) } } @@ -361,11 +374,11 @@ private fun primaryActionString( private fun rememberSecondaryActionResource(backupAlert: BackupAlert): Int { return remember(backupAlert) { when (backupAlert) { - BackupAlert.COULD_NOT_COMPLETE_BACKUP -> android.R.string.cancel // TODO [backups] -- Finalized copy - BackupAlert.PAYMENT_PROCESSING -> R.string.BackupAlertBottomSheet__not_now - BackupAlert.MEDIA_BACKUPS_ARE_OFF -> R.string.BackupAlertBottomSheet__not_now - BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> R.string.BackupAlertBottomSheet__dont_download_media - BackupAlert.DISK_FULL -> R.string.BackupAlertBottomSheet__skip + BackupAlert.CouldNotCompleteBackup -> R.string.BackupAlertBottomSheet__try_later + BackupAlert.FailedToRenew -> R.string.BackupAlertBottomSheet__not_now + BackupAlert.MediaBackupsAreOff -> R.string.BackupAlertBottomSheet__not_now + BackupAlert.MediaWillBeDeletedToday -> R.string.BackupAlertBottomSheet__dont_download_media + is BackupAlert.DiskFull -> R.string.BackupAlertBottomSheet__skip_restore } } } @@ -375,7 +388,7 @@ private fun rememberSecondaryActionResource(backupAlert: BackupAlert): Int { private fun BackupAlertSheetContentPreviewGeneric() { Previews.BottomSheetPreview { BackupAlertSheetContent( - backupAlert = BackupAlert.COULD_NOT_COMPLETE_BACKUP + backupAlert = BackupAlert.CouldNotCompleteBackup ) } } @@ -385,7 +398,7 @@ private fun BackupAlertSheetContentPreviewGeneric() { private fun BackupAlertSheetContentPreviewPayment() { Previews.BottomSheetPreview { BackupAlertSheetContent( - backupAlert = BackupAlert.PAYMENT_PROCESSING + backupAlert = BackupAlert.FailedToRenew ) } } @@ -395,7 +408,7 @@ private fun BackupAlertSheetContentPreviewPayment() { private fun BackupAlertSheetContentPreviewMedia() { Previews.BottomSheetPreview { BackupAlertSheetContent( - backupAlert = BackupAlert.MEDIA_BACKUPS_ARE_OFF, + backupAlert = BackupAlert.MediaBackupsAreOff, pricePerMonth = "$2.99" ) } @@ -406,7 +419,7 @@ private fun BackupAlertSheetContentPreviewMedia() { private fun BackupAlertSheetContentPreviewDelete() { Previews.BottomSheetPreview { BackupAlertSheetContent( - backupAlert = BackupAlert.MEDIA_WILL_BE_DELETED_TODAY + backupAlert = BackupAlert.MediaWillBeDeletedToday ) } } @@ -416,16 +429,28 @@ private fun BackupAlertSheetContentPreviewDelete() { private fun BackupAlertSheetContentPreviewDiskFull() { Previews.BottomSheetPreview { BackupAlertSheetContent( - backupAlert = BackupAlert.DISK_FULL + backupAlert = BackupAlert.DiskFull(requiredSpace = "12GB") ) } } +/** + * All necessary information to display the sheet should be handed in through the specific alert. + */ @Parcelize -enum class BackupAlert : Parcelable { - COULD_NOT_COMPLETE_BACKUP, - PAYMENT_PROCESSING, - MEDIA_BACKUPS_ARE_OFF, - MEDIA_WILL_BE_DELETED_TODAY, - DISK_FULL +sealed class BackupAlert : Parcelable { + + data object CouldNotCompleteBackup : BackupAlert() + + data object FailedToRenew : BackupAlert() + + data object MediaBackupsAreOff : BackupAlert() + + data object MediaWillBeDeletedToday : BackupAlert() + + /** + * The disk is full. Contains a value representing the amount of space that must be freed. + * + */ + data class DiskFull(val requiredSpace: String) : BackupAlert() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertDelegate.kt index de391832bc..fccba6773b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupAlertDelegate.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.launch +import org.thoughtcrime.securesms.backup.v2.BackupRepository /** * Delegate that controls whether and which backup alert sheet is displayed. @@ -19,12 +20,12 @@ object BackupAlertDelegate { fun delegate(fragmentManager: FragmentManager, lifecycle: Lifecycle) { lifecycle.coroutineScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { - // TODO [backups] - // 1. Get unnotified backup upload failures - // 2. Get unnotified backup download failures - // 3. Get unnotified backup payment failures + if (BackupRepository.shouldDisplayBackupFailedSheet()) { + BackupAlertBottomSheet.create(BackupAlert.CouldNotCompleteBackup).show(fragmentManager, null) + } - // Decide which do display + // TODO [backups] + // Get unnotified backup download failures & display sheet } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusBanner.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusBanner.kt index 4b6a290952..2e00cf5d81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusBanner.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusBanner.kt @@ -57,7 +57,7 @@ private const val NONE = -1 @Composable fun BackupStatusBanner( data: BackupStatusData, - onSkipClick: () -> Unit = {}, + onActionClick: (BackupStatusData) -> Unit = {}, onDismissClick: () -> Unit = {}, contentPadding: PaddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp) ) { @@ -119,7 +119,7 @@ fun BackupStatusBanner( if (data.actionRes != NONE) { Buttons.Small( - onClick = onSkipClick, + onClick = { onActionClick(data) }, modifier = Modifier.padding(start = 8.dp) ) { Text(text = stringResource(id = data.actionRes)) @@ -250,7 +250,7 @@ sealed interface BackupStatusData { get() = stringResource(R.string.BackupStatus__free_up_s_of_space_to_download_your_media, requiredSpace) override val iconColors: BackupsIconColors = BackupsIconColors.Warning - override val actionRes: Int = R.string.registration_activity__skip + override val actionRes: Int = R.string.BackupStatus__details } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/banner/banners/MediaRestoreProgressBanner.kt b/app/src/main/java/org/thoughtcrime/securesms/banner/banners/MediaRestoreProgressBanner.kt index e2fda7ecd1..e5c0f1ce12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/banner/banners/MediaRestoreProgressBanner.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/banner/banners/MediaRestoreProgressBanner.kt @@ -73,7 +73,7 @@ class MediaRestoreProgressBanner(private val listener: RestoreProgressBannerList override fun DisplayBanner(model: BackupStatusData, contentPadding: PaddingValues) { BackupStatusBanner( data = model, - onSkipClick = listener::onSkip, + onActionClick = listener::onActionClick, onDismissClick = listener::onDismissComplete ) } @@ -124,12 +124,12 @@ class MediaRestoreProgressBanner(private val listener: RestoreProgressBannerList } interface RestoreProgressBannerListener { - fun onSkip() + fun onActionClick(data: BackupStatusData) fun onDismissComplete() } private object EmptyListener : RestoreProgressBannerListener { - override fun onSkip() = Unit + override fun onActionClick(data: BackupStatusData) = Unit override fun onDismissComplete() = 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 583c55206d..e2852c2deb 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 @@ -119,7 +119,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() { } fun skipMediaRestore() { - // TODO [backups] -- Clear the error as necessary + BackupRepository.skipMediaRestore() } fun cancelMediaRestore() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppDonations.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppDonations.kt index 2f02bc3aee..464ac72985 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppDonations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppDonations.kt @@ -5,7 +5,6 @@ import org.signal.donations.InAppPaymentType import org.signal.donations.PaymentSourceType import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.database.model.InAppPaymentReceiptRecord -import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.LocaleRemoteConfig @@ -28,9 +27,9 @@ object InAppDonations { return isCreditCardAvailable() || isPayPalAvailable() || isGooglePayAvailable() || isSEPADebitAvailable() || isIDEALAvailable() } - fun isPaymentSourceAvailable(paymentSourceType: PaymentSourceType, inAppPaymentType: InAppPaymentType): Boolean { + fun isDonationsPaymentSourceAvailable(paymentSourceType: PaymentSourceType, inAppPaymentType: InAppPaymentType): Boolean { if (inAppPaymentType == InAppPaymentType.RECURRING_BACKUP) { - return paymentSourceType == PaymentSourceType.GooglePlayBilling && AppDependencies.billingApi.isApiAvailable() + error("Not supported.") } return when (paymentSourceType) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/completed/InAppPaymentsBottomSheetDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/completed/InAppPaymentsBottomSheetDelegate.kt index afe6f1659a..d846a2e492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/completed/InAppPaymentsBottomSheetDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/completed/InAppPaymentsBottomSheetDelegate.kt @@ -134,9 +134,9 @@ class InAppPaymentsBottomSheetDelegate( }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeBy { inAppPayments -> for (payment in inAppPayments) { if (isPaymentProcessingError(payment.state, payment.data)) { - BackupAlertBottomSheet.create(BackupAlert.COULD_NOT_COMPLETE_BACKUP).show(fragmentManager, null) + BackupAlertBottomSheet.create(BackupAlert.CouldNotCompleteBackup).show(fragmentManager, null) } else if (isUnexpectedCancellation(payment.state, payment.data)) { - BackupAlertBottomSheet.create(BackupAlert.MEDIA_BACKUPS_ARE_OFF).show(fragmentManager, null) + BackupAlertBottomSheet.create(BackupAlert.MediaBackupsAreOff).show(fragmentManager, null) } } } @@ -146,7 +146,7 @@ class InAppPaymentsBottomSheetDelegate( InAppPaymentsRepository.getExpiredBackupDeletionState() }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeBy { if (it == InAppPaymentsRepository.ExpiredBackupDeletionState.DELETE_TODAY) { - BackupAlertBottomSheet.create(BackupAlert.MEDIA_WILL_BE_DELETED_TODAY).show(fragmentManager, null) + BackupAlertBottomSheet.create(BackupAlert.MediaWillBeDeletedToday).show(fragmentManager, null) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/InAppPaymentCheckoutDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/InAppPaymentCheckoutDelegate.kt index b683fbe97e..0c57527214 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/InAppPaymentCheckoutDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/InAppPaymentCheckoutDelegate.kt @@ -101,7 +101,7 @@ class InAppPaymentCheckoutDelegate( } fun handleGatewaySelectionResponse(inAppPayment: InAppPaymentTable.InAppPayment) { - if (InAppDonations.isPaymentSourceAvailable(inAppPayment.data.paymentMethodType.toPaymentSourceType(), inAppPayment.type)) { + if (InAppDonations.isDonationsPaymentSourceAvailable(inAppPayment.data.paymentMethodType.toPaymentSourceType(), inAppPayment.type)) { when (inAppPayment.data.paymentMethodType) { InAppPaymentData.PaymentMethodType.GOOGLE_PAY -> launchGooglePay(inAppPayment) InAppPaymentData.PaymentMethodType.PAYPAL -> launchPayPal(inAppPayment) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt index 82d756d0fe..8d17a5431b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt @@ -26,11 +26,11 @@ class GatewaySelectorViewModel( GatewaySelectorState( gatewayOrderStrategy = GatewayOrderStrategy.getStrategy(), inAppPayment = args.inAppPayment, - isCreditCardAvailable = InAppDonations.isPaymentSourceAvailable(PaymentSourceType.Stripe.CreditCard, args.inAppPayment.type), - isGooglePayAvailable = InAppDonations.isPaymentSourceAvailable(PaymentSourceType.Stripe.GooglePay, args.inAppPayment.type), - isPayPalAvailable = InAppDonations.isPaymentSourceAvailable(PaymentSourceType.PayPal, args.inAppPayment.type), - isSEPADebitAvailable = InAppDonations.isPaymentSourceAvailable(PaymentSourceType.Stripe.SEPADebit, args.inAppPayment.type), - isIDEALAvailable = InAppDonations.isPaymentSourceAvailable(PaymentSourceType.Stripe.IDEAL, args.inAppPayment.type) + isCreditCardAvailable = InAppDonations.isDonationsPaymentSourceAvailable(PaymentSourceType.Stripe.CreditCard, args.inAppPayment.type), + isGooglePayAvailable = InAppDonations.isDonationsPaymentSourceAvailable(PaymentSourceType.Stripe.GooglePay, args.inAppPayment.type), + isPayPalAvailable = InAppDonations.isDonationsPaymentSourceAvailable(PaymentSourceType.PayPal, args.inAppPayment.type), + isSEPADebitAvailable = InAppDonations.isDonationsPaymentSourceAvailable(PaymentSourceType.Stripe.SEPADebit, args.inAppPayment.type), + isIDEALAvailable = InAppDonations.isDonationsPaymentSourceAvailable(PaymentSourceType.Stripe.IDEAL, args.inAppPayment.type) ) ) private val disposables = CompositeDisposable() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 633a2797c1..6e389fac17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -33,7 +33,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.Parcelable; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; @@ -94,11 +93,10 @@ import org.thoughtcrime.securesms.MainNavigator; import org.thoughtcrime.securesms.MuteDialog; import org.thoughtcrime.securesms.NewConversationActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.backup.v2.ArchiveValidator; -import org.thoughtcrime.securesms.backup.v2.BackupRepository; import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert; import org.thoughtcrime.securesms.backup.v2.ui.BackupAlertBottomSheet; import org.thoughtcrime.securesms.backup.v2.ui.BackupAlertDelegate; +import org.thoughtcrime.securesms.backup.v2.ui.status.BackupStatusData; import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.badges.self.expired.ExpiredOneTimeBadgeBottomSheetDialogFragment; import org.thoughtcrime.securesms.badges.self.expired.MonthlyDonationCanceledBottomSheetDialogFragment; @@ -163,7 +161,6 @@ import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment; -import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -178,12 +175,10 @@ import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.BottomSheetUtil; import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.ConversationUtil; -import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.SignalLocalMetrics; import org.thoughtcrime.securesms.util.SignalProxyUtil; import org.thoughtcrime.securesms.util.SnapToTopDataObserver; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter; @@ -193,10 +188,6 @@ import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; @@ -581,10 +572,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode if (this.bannerManager != null) { this.bannerManager.updateContent(bannerView.get()); } - - if (BackupRepository.shouldDisplayBackupFailedSheet()) { - BackupAlertBottomSheet.Companion.create(BackupAlert.COULD_NOT_COMPLETE_BACKUP).show(getParentFragmentManager(), null); - } } @Override @@ -928,8 +915,11 @@ public class ConversationListFragment extends MainFragment implements ActionMode }), new MediaRestoreProgressBanner(new MediaRestoreProgressBanner.RestoreProgressBannerListener() { @Override - public void onSkip() { - // TODO [backups] add skip restore ability + public void onActionClick(@NonNull BackupStatusData backupStatusData) { + if (backupStatusData instanceof BackupStatusData.NotEnoughFreeSpace) { + BackupAlertBottomSheet.create(new BackupAlert.DiskFull(((BackupStatusData.NotEnoughFreeSpace) backupStatusData).getRequiredSpace())) + .show(getParentFragmentManager(), null); + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/GooglePlayBillingDependencies.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/GooglePlayBillingDependencies.kt index e3da749f89..94e6647c0c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/GooglePlayBillingDependencies.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/GooglePlayBillingDependencies.kt @@ -6,20 +6,31 @@ package org.thoughtcrime.securesms.dependencies import android.content.Context +import org.signal.billing.BillingError import org.signal.core.util.billing.BillingDependencies +import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration +import java.util.Locale /** * Dependency object for Google Play Billing. */ object GooglePlayBillingDependencies : BillingDependencies { + private const val BILLING_PRODUCT_ID_NOT_AVAILABLE = -1000 + override val context: Context get() = AppDependencies.application override suspend fun getProductId(): String { - return "backup" // TODO [backups] This really shouldn't be hardcoded into the app. + val config = AppDependencies.donationsService.getDonationsConfiguration(Locale.getDefault()) + + if (config.result.isPresent) { + return config.result.get().backupConfiguration.backupLevelConfigurationMap[SubscriptionsConfiguration.BACKUPS_LEVEL]?.playProductId ?: throw BillingError(BILLING_PRODUCT_ID_NOT_AVAILABLE) + } else { + throw BillingError(BILLING_PRODUCT_ID_NOT_AVAILABLE) + } } override suspend fun getBasePlanId(): String { - return "monthly" // TODO [backups] This really shouldn't be hardcoded into the app. + return "monthly" } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f5527c309..ca1d4b03fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7431,15 +7431,12 @@ Your media will be deleted today Your Signal media backup plan has been canceled because we couldn\'t process your payment. This is your last chance to download the media in your backup before it is deleted. - - Can\'t complete download + + Free up %1$s on this device - Your device does not have enough free space. Free up %1$s of space to download the media stored in your backup. - - - If you choose \"Skip\" the media in your backup will be deleted in %1$d day. - If you choose \"Skip\" the media in your backup will be deleted in %1$d days. - + To finish downloading your Signal Backup your device needs %1$s of storage space. + + To free up space offload or delete unused apps or content large in file size. Your backups subscription failed to renew @@ -7451,9 +7448,11 @@ Learn more - Skip + Skip restore Back up now + + Got it Manage subscription @@ -7464,6 +7463,8 @@ Don\'t download media Not now + + Try later Media will be deleted @@ -7472,6 +7473,12 @@ Download Don\'t download + + Skip + + Skip restore? + + If you skip restore the remaining media and attachments in your backup will be deleted the next time your device completes a new backup. @@ -7482,6 +7489,8 @@ Restore paused Restore complete + + Details Waiting for Wi-Fi… diff --git a/billing/src/main/java/org/signal/billing/BillingApiImpl.kt b/billing/src/main/java/org/signal/billing/BillingApiImpl.kt index 922d0db320..1bfd3b30fb 100644 --- a/billing/src/main/java/org/signal/billing/BillingApiImpl.kt +++ b/billing/src/main/java/org/signal/billing/BillingApiImpl.kt @@ -249,8 +249,10 @@ internal class BillingApiImpl( * Returns whether or not subscriptions are supported by a user's device. Lack of subscription support is generally due * to out-of-date Google Play API */ - override fun isApiAvailable(): Boolean { - return billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).responseCode == BillingResponseCode.OK + override suspend fun isApiAvailable(): Boolean { + return doOnConnectionReady { + billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).responseCode == BillingResponseCode.OK + } } private suspend fun queryProductsInternal(): ProductDetailsResult { diff --git a/core-util/src/main/java/org/signal/core/util/billing/BillingApi.kt b/core-util/src/main/java/org/signal/core/util/billing/BillingApi.kt index e84bdd7815..24e48cf5ed 100644 --- a/core-util/src/main/java/org/signal/core/util/billing/BillingApi.kt +++ b/core-util/src/main/java/org/signal/core/util/billing/BillingApi.kt @@ -19,7 +19,7 @@ interface BillingApi { */ fun getBillingPurchaseResults(): Flow = emptyFlow() - fun isApiAvailable(): Boolean = false + suspend fun isApiAvailable(): Boolean = false suspend fun queryProduct(): BillingProduct? = null diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/SubscriptionsConfiguration.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/SubscriptionsConfiguration.java index 22919ec2c7..2ae3eceadc 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/SubscriptionsConfiguration.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/SubscriptionsConfiguration.java @@ -105,9 +105,16 @@ public class SubscriptionsConfiguration { @JsonProperty("storageAllowanceBytes") private long storageAllowanceBytes; + @JsonProperty("playProductId") + private String playProductId; + public long getStorageAllowanceBytes() { return storageAllowanceBytes; } + + public String getPlayProductId() { + return playProductId; + } } public Map getCurrencies() {