mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Add Out of Remote Storage sheet.
This commit is contained in:
committed by
Cody Henthorne
parent
50d809029e
commit
53a80589e3
@@ -420,7 +420,7 @@ object BackupRepository {
|
||||
ServiceUtil.getNotificationManager(AppDependencies.application).cancel(NotificationIds.INITIAL_BACKUP_FAILED)
|
||||
}
|
||||
|
||||
fun markOutOfRemoteStorageError() {
|
||||
fun markOutOfRemoteStorageSpaceError() {
|
||||
val context = AppDependencies.application
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, AppSettingsActivity.remoteBackups(context), cancelCurrent())
|
||||
@@ -434,15 +434,15 @@ object BackupRepository {
|
||||
|
||||
ServiceUtil.getNotificationManager(context).notify(NotificationIds.OUT_OF_REMOTE_STORAGE, notification)
|
||||
|
||||
SignalStore.backup.isNotEnoughRemoteStorageSpace = true
|
||||
SignalStore.backup.markNotEnoughRemoteStorageSpace()
|
||||
}
|
||||
|
||||
fun clearOutOfRemoteStorageError() {
|
||||
SignalStore.backup.isNotEnoughRemoteStorageSpace = false
|
||||
fun clearOutOfRemoteStorageSpaceError() {
|
||||
SignalStore.backup.clearNotEnoughRemoteStorageSpace()
|
||||
ServiceUtil.getNotificationManager(AppDependencies.application).cancel(NotificationIds.OUT_OF_REMOTE_STORAGE)
|
||||
}
|
||||
|
||||
fun shouldDisplayOutOfStorageSpaceUx(): Boolean {
|
||||
fun shouldDisplayOutOfRemoteStorageSpaceUx(): Boolean {
|
||||
if (shouldNotDisplayBackupFailedMessaging()) {
|
||||
return false
|
||||
}
|
||||
@@ -450,6 +450,18 @@ object BackupRepository {
|
||||
return SignalStore.backup.isNotEnoughRemoteStorageSpace
|
||||
}
|
||||
|
||||
fun shouldDisplayOutOfRemoteStorageSpaceSheet(): Boolean {
|
||||
if (shouldNotDisplayBackupFailedMessaging()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return SignalStore.backup.shouldDisplayNotEnoughRemoteStorageSpaceSheet
|
||||
}
|
||||
|
||||
fun dismissOutOfRemoteStorageSpaceSheet() {
|
||||
SignalStore.backup.dismissNotEnoughRemoteStorageSpaceSheet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the yellow dot should be displayed on the conversation list avatar.
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,7 @@ object BackupAlertDelegate {
|
||||
} else if (BackupRepository.shouldDisplayNoManualBackupForTimeoutSheet()) {
|
||||
NoManualBackupBottomSheet().show(fragmentManager, FRAGMENT_TAG)
|
||||
BackupRepository.displayManualBackupNotCreatedInThresholdNotification()
|
||||
} else if (BackupRepository.shouldDisplayOutOfRemoteStorageSpaceSheet()) {
|
||||
}
|
||||
|
||||
displayBackupDownloadNotifier(fragmentManager)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalPreview
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.components.contactsupport.ContactSupportDialogFragment
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
|
||||
class NoRemoteStorageSpaceAvailableBottomSheet : ComposeBottomSheetDialogFragment() {
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
val context = LocalContext.current
|
||||
|
||||
NoRemoteStorageSpaceAvailableBottomSheetContent(
|
||||
onLearnMoreClick = {
|
||||
CommunicationActions.openBrowserLink(context, context.getString(R.string.backup_failed_support_url))
|
||||
},
|
||||
onContactSupportClick = {
|
||||
ContactSupportDialogFragment.create(
|
||||
subject = R.string.BackupAlertBottomSheet_network_failure_support_email,
|
||||
filter = R.string.BackupAlertBottomSheet_export_failure_filter
|
||||
).show(parentFragmentManager, null)
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
},
|
||||
onOkClick = {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
BackupRepository.dismissOutOfRemoteStorageSpaceSheet()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NoRemoteStorageSpaceAvailableBottomSheetContent(
|
||||
onLearnMoreClick: () -> Unit,
|
||||
onContactSupportClick: () -> Unit,
|
||||
onOkClick: () -> Unit
|
||||
) {
|
||||
val primaryActionButtonLabel = stringResource(R.string.BackupAlertBottomSheet__contact_support)
|
||||
val primaryActionButtonState = remember(primaryActionButtonLabel, onContactSupportClick) {
|
||||
BackupAlertActionButtonState(
|
||||
label = primaryActionButtonLabel,
|
||||
callback = onContactSupportClick
|
||||
)
|
||||
}
|
||||
|
||||
val secondaryActionButtonLabel = stringResource(android.R.string.ok)
|
||||
val secondaryActionButtonState = remember(secondaryActionButtonLabel, onOkClick) {
|
||||
BackupAlertActionButtonState(
|
||||
label = secondaryActionButtonLabel,
|
||||
callback = onOkClick
|
||||
)
|
||||
}
|
||||
|
||||
BackupAlertBottomSheetContainer(
|
||||
icon = {
|
||||
BackupAlertIcon(iconColors = BackupsIconColors.Warning)
|
||||
},
|
||||
title = stringResource(R.string.BackupAlertBottomSheet__backup_failed),
|
||||
primaryActionButtonState = primaryActionButtonState,
|
||||
secondaryActionButtonState = secondaryActionButtonState
|
||||
) {
|
||||
val text = buildAnnotatedString {
|
||||
append(stringResource(id = R.string.BackupAlertBottomSheet__an_error_occurred_and))
|
||||
append(" ")
|
||||
|
||||
withLink(
|
||||
LinkAnnotation.Clickable(tag = "learn-more") {
|
||||
onLearnMoreClick()
|
||||
}
|
||||
) {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
||||
append(stringResource(id = R.string.BackupAlertBottomSheet__learn_more))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackupAlertText(
|
||||
text = text,
|
||||
modifier = Modifier.padding(bottom = 36.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun NoRemoteStorageSpaceAvailableBottomSheetContentPreview() {
|
||||
Previews.BottomSheetPreview {
|
||||
NoRemoteStorageSpaceAvailableBottomSheetContent({}, {}, {})
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class AppSettingsViewModel : ViewModel() {
|
||||
private fun getBackupFailureState(): BackupFailureState {
|
||||
return if (!RemoteConfig.messageBackups) {
|
||||
BackupFailureState.NONE
|
||||
} else if (BackupRepository.shouldDisplayOutOfStorageSpaceUx()) {
|
||||
} else if (BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx()) {
|
||||
BackupFailureState.OUT_OF_STORAGE_SPACE
|
||||
} else if (BackupRepository.shouldDisplayBackupFailedSettingsRow()) {
|
||||
BackupFailureState.BACKUP_FAILED
|
||||
|
||||
@@ -270,7 +270,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private suspend fun performStateRefresh(lastPurchase: InAppPaymentTable.InAppPayment?) {
|
||||
if (BackupRepository.shouldDisplayOutOfStorageSpaceUx()) {
|
||||
if (BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx()) {
|
||||
val paidType = BackupRepository.getPaidType()
|
||||
|
||||
if (paidType is NetworkResult.Success) {
|
||||
@@ -278,7 +278,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
val estimatedSize = SignalDatabase.attachments.getEstimatedArchiveMediaSize().bytes
|
||||
|
||||
if (estimatedSize + 300.mebiBytes <= remoteStorageAllowance) {
|
||||
BackupRepository.clearOutOfRemoteStorageError()
|
||||
BackupRepository.clearOutOfRemoteStorageSpaceError()
|
||||
}
|
||||
|
||||
_state.update {
|
||||
@@ -301,7 +301,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
backupsFrequency = SignalStore.backup.backupFrequency,
|
||||
canBackUpUsingCellular = SignalStore.backup.backupWithCellular,
|
||||
canRestoreUsingCellular = SignalStore.backup.restoreWithCellular,
|
||||
isOutOfStorageSpace = BackupRepository.shouldDisplayOutOfStorageSpaceUx(),
|
||||
isOutOfStorageSpace = BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx(),
|
||||
hasRedemptionError = lastPurchase?.data?.error?.data_ == "409"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ fun Screen(
|
||||
Rows.TextRow(
|
||||
text = "Mark out of remote storage space",
|
||||
onClick = {
|
||||
BackupRepository.markOutOfRemoteStorageError()
|
||||
BackupRepository.markOutOfRemoteStorageSpaceError()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
|
||||
val remoteStorageQuota = getServerQuota() ?: return Result.retry(defaultBackoff()).logW(TAG, "[$attachmentId] Failed to fetch server quota! Retrying.")
|
||||
|
||||
if (SignalDatabase.attachments.getEstimatedArchiveMediaSize() > remoteStorageQuota.inWholeBytes) {
|
||||
BackupRepository.markOutOfRemoteStorageError()
|
||||
BackupRepository.markOutOfRemoteStorageSpaceError()
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
private const val KEY_BACKUP_ALREADY_REDEEMED = "backup.already.redeemed"
|
||||
private const val KEY_INVALID_BACKUP_VERSION = "backup.invalid.version"
|
||||
private const val KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE = "backup.not.enough.remote.storage.space"
|
||||
private const val KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET = "backup.not.enough.remote.storage.space.display.sheet"
|
||||
private const val KEY_MANUAL_NO_BACKUP_NOTIFIED = "backup.manual.no.backup.notified"
|
||||
|
||||
private const val KEY_USER_MANUALLY_SKIPPED_MEDIA_RESTORE = "backup.user.manually.skipped.media.restore"
|
||||
@@ -233,6 +234,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
|
||||
Log.i(TAG, "Setting backup tier to $value", Throwable(), true)
|
||||
val serializedValue = MessageBackupTier.serialize(value)
|
||||
val storedValue = MessageBackupTier.deserialize(getLong(KEY_BACKUP_TIER, -1))
|
||||
|
||||
if (value != null) {
|
||||
store.beginWrite()
|
||||
.putLong(KEY_BACKUP_TIER, serializedValue)
|
||||
@@ -240,6 +243,12 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
.putBoolean(KEY_BACKUP_TIMESTAMP_RESTORED, true)
|
||||
.apply()
|
||||
|
||||
if (storedValue != value) {
|
||||
clearNotEnoughRemoteStorageSpace()
|
||||
clearMessageBackupFailure()
|
||||
clearMessageBackupFailureSheetWatermark()
|
||||
}
|
||||
|
||||
deletionState = DeletionState.NONE
|
||||
} else {
|
||||
putLong(KEY_BACKUP_TIER, serializedValue)
|
||||
@@ -356,7 +365,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
/** Store that lets you interact with media ZK credentials. */
|
||||
val mediaCredentials = CredentialStore(KEY_MEDIA_CREDENTIALS, KEY_MEDIA_CDN_READ_CREDENTIALS, KEY_MEDIA_CDN_READ_CREDENTIALS_TIMESTAMP)
|
||||
|
||||
var isNotEnoughRemoteStorageSpace by booleanValue(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE, false)
|
||||
val isNotEnoughRemoteStorageSpace by booleanValue(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE, false)
|
||||
val shouldDisplayNotEnoughRemoteStorageSpaceSheet by booleanValue(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET, false)
|
||||
|
||||
var isNoBackupForManualUploadNotified by booleanValue(KEY_MANUAL_NO_BACKUP_NOTIFIED, false)
|
||||
|
||||
@@ -383,6 +393,36 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
NoRemoteArchiveGarbageCollectionPendingConstraint.Observer.notifyListeners()
|
||||
}
|
||||
|
||||
/**
|
||||
* When we are told by the server that we are out of storage space, we should show
|
||||
* UX treatment to make the user aware of this.
|
||||
*/
|
||||
fun markNotEnoughRemoteStorageSpace() {
|
||||
store.beginWrite()
|
||||
.putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE, true)
|
||||
.putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET, false)
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* When we've regained enough space, we can remove the error.
|
||||
*/
|
||||
fun clearNotEnoughRemoteStorageSpace() {
|
||||
store.beginWrite()
|
||||
.putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE, false)
|
||||
.putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET, false)
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the sheet so as not to irritate the user.
|
||||
*/
|
||||
fun dismissNotEnoughRemoteStorageSpaceSheet() {
|
||||
store.beginWrite()
|
||||
.putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET, false)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun markMessageBackupFailure() {
|
||||
store.beginWrite()
|
||||
.putBoolean(KEY_BACKUP_FAIL, true)
|
||||
|
||||
@@ -46,7 +46,7 @@ class MainToolbarViewModel : ViewModel() {
|
||||
internalStateFlow.update {
|
||||
it.copy(
|
||||
hasFailedBackups = BackupRepository.shouldDisplayBackupFailedIndicator() || BackupRepository.shouldDisplayBackupAlreadyRedeemedIndicator(),
|
||||
isOutOfRemoteStorageSpace = BackupRepository.shouldDisplayOutOfStorageSpaceUx(),
|
||||
isOutOfRemoteStorageSpace = BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx(),
|
||||
hasPassphrase = !SignalStore.settings.passphraseDisabled
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7951,6 +7951,8 @@
|
||||
<string name="BackupAlertBottomSheet__couldnt_redeem_your_backups_subscription">Couldn\'t redeem your backups subscription</string>
|
||||
<!-- Dialog text for when a backup fails to be created and ways to fix it -->
|
||||
<string name="BackupAlertBottomSheet__an_error_occurred">An error occurred and your backup could not be completed. Make sure you\'re on the latest version of Signal and try again. If this problem persists, contact support.</string>
|
||||
<!-- Dialog text for when a backup fails to be created and ways to fix it. Used for out of remote storage space error. -->
|
||||
<string name="BackupAlertBottomSheet__an_error_occurred_and">An error occurred and your backup could not be completed. If this problem persists, contact support.</string>
|
||||
<!-- Dialog action button that will allow you to check for any Signal version updates -->
|
||||
<string name="BackupAlertBottomSheet__check_for_update">Check for update</string>
|
||||
<!-- Backup redemption error sheet text line 1 -->
|
||||
|
||||
Reference in New Issue
Block a user