Update backups bottom sheet data handling.

This commit is contained in:
Alex Hart
2024-11-06 09:31:20 -04:00
committed by Greyson Parrelli
parent 3901c52e45
commit f14f7f7478
16 changed files with 163 additions and 114 deletions

View File

@@ -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.
*/

View File

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

View File

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

View File

@@ -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
}
/**

View File

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

View File

@@ -119,7 +119,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
}
fun skipMediaRestore() {
// TODO [backups] -- Clear the error as necessary
BackupRepository.skipMediaRestore()
}
fun cancelMediaRestore() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7431,15 +7431,12 @@
<string name="BackupAlertBottomSheet__your_media_will_be_deleted_today">Your media will be deleted today</string>
<!-- Sheet body part 1 when user\'s media will be deleted today -->
<string name="BackupAlertBottomSheet__your_signal_media_backup_plan_has_been">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.</string>
<!-- Sheet title when user does not have enough space to download their backup -->
<string name="BackupAlertBottomSheet__cant_complete_download">Can\'t complete download</string>
<!-- Sheet title when user does not have enough space to download their backup. Placeholder is formatted byte size, for example 12GB. -->
<string name="BackupAlertBottomSheet__free_up_s_on_this_device">Free up %1$s on this device</string>
<!-- Sheet body part 1 when user does not have enough space to download their backup. Placeholder is the amount of space needed. -->
<string name="BackupAlertBottomSheet__your_device_does_not_have_enough_free_space">Your device does not have enough free space. Free up %1$s of space to download the media stored in your backup.</string>
<!-- Sheet body part 2 when user does not have enough space to download their backup. Placeholder is the number of days until deletion -->
<plurals name="BackupAlertBottomSheet__if_you_choose_skip">
<item quantity="one">If you choose \"Skip\" the media in your backup will be deleted in %1$d day.</item>
<item quantity="other">If you choose \"Skip\" the media in your backup will be deleted in %1$d days.</item>
</plurals>
<string name="BackupAlertBottomSheet__to_finish_downloading_your_signal_backup">To finish downloading your Signal Backup your device needs %1$s of storage space.</string>
<!-- Sheet body part 2 when user does not have enough space to download their backup. -->
<string name="BackupAlertBottomSheet__to_free_up_space_offload">To free up space offload or delete unused apps or content large in file size.</string>
<!-- Sheet title when user payment failed to process -->
<string name="BackupAlertBottomSheet__your_backups_subscription_failed_to_renew">Your backups subscription failed to renew</string>
<!-- Sheet body when user payment failed to process -->
@@ -7451,9 +7448,11 @@
<!-- Clickable text to learn more about the content of this bottom sheet -->
<string name="BackupAlertBottomSheet__learn_more">Learn more</string>
<!-- Secondary action button text when user does not have enough free space to download their backup. -->
<string name="BackupAlertBottomSheet__skip">Skip</string>
<string name="BackupAlertBottomSheet__skip_restore">Skip restore</string>
<!-- Primary action button to start backup immediately -->
<string name="BackupAlertBottomSheet__back_up_now">Back up now</string>
<!-- Primary action button when user doesn't have enough space -->
<string name="BackupAlertBottomSheet__got_it">Got it</string>
<!-- Primary action button to manage subscription in Google Play -->
<string name="BackupAlertBottomSheet__manage_subscription">Manage subscription</string>
<!-- Primary action button text prompting user to subscriber. Placeholder is formatted price -->
@@ -7464,6 +7463,8 @@
<string name="BackupAlertBottomSheet__dont_download_media">Don\'t download media</string>
<!-- Secondary generic action button to dismiss sheet without performing an action -->
<string name="BackupAlertBottomSheet__not_now">Not now</string>
<!-- Secondary action button to dismiss could not complete backup sheet -->
<string name="BackupAlertBottomSheet__try_later">Try later</string>
<!-- Dialog title for last chance to download backup -->
<string name="BackupAlertBottomSheet__media_will_be_deleted">Media will be deleted</string>
<!-- Dialog message for last chance to download backup -->
@@ -7472,6 +7473,12 @@
<string name="BackupAlertBottomSheet__download">Download</string>
<!-- Dialog action to not download now -->
<string name="BackupAlertBottomSheet__dont_download">Don\'t download</string>
<!-- Dialog action to skip media download -->
<string name="BackupAlertBottomSheet__skip">Skip</string>
<!-- Dialog title for skipping media restore -->
<string name="BackupAlertBottomSheet__skip_restore_question">Skip restore?</string>
<!-- Dialog text for skipping media restore -->
<string name="BackupAlertBottomSheet__if_you_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.</string>
<!-- BackupStatus -->
<!-- Status title when user does not have enough free space to download their media. Placeholder is required disk space. -->
@@ -7482,6 +7489,8 @@
<string name="BackupStatus__restore_paused">Restore paused</string>
<!-- Status title for banner when user has completed restoring restore media from a backup -->
<string name="BackupStatus__restore_complete">Restore complete</string>
<!-- Status action label for seeing more details about storage space -->
<string name="BackupStatus__details">Details</string>
<!-- Status subtitle for banner when restoring media pauses for Wi-Fi -->
<string name="BackupStatus__status_waiting_for_wifi">Waiting for Wi-Fi…</string>

View File

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

View File

@@ -19,7 +19,7 @@ interface BillingApi {
*/
fun getBillingPurchaseResults(): Flow<BillingPurchaseResult> = emptyFlow()
fun isApiAvailable(): Boolean = false
suspend fun isApiAvailable(): Boolean = false
suspend fun queryProduct(): BillingProduct? = null

View File

@@ -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<String, CurrencyConfiguration> getCurrencies() {