diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/CreateBackupBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/CreateBackupBottomSheet.kt index cd914ccc3b..f17532527a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/CreateBackupBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/CreateBackupBottomSheet.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource @@ -29,6 +30,7 @@ import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.SignalPreview import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.backups.BackupStateObserver import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment import org.thoughtcrime.securesms.jobs.BackupMessagesJob import org.signal.core.ui.R as CoreUiR @@ -51,15 +53,15 @@ class CreateBackupBottomSheet : ComposeBottomSheetDialogFragment() { @Composable override fun SheetContent() { + val isPaidTier: Boolean = remember { BackupStateObserver.getNonIOBackupState().isLikelyPaidTier() } + CreateBackupBottomSheetContent( + isPaidTier = isPaidTier, onBackupNowClick = { BackupMessagesJob.enqueue() setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to Result.BACKUP_STARTED)) isResultSet = true dismissAllowingStateLoss() - }, - onBackupLaterClick = { - dismissAllowingStateLoss() } ) } @@ -80,8 +82,8 @@ class CreateBackupBottomSheet : ComposeBottomSheetDialogFragment() { @Composable private fun CreateBackupBottomSheetContent( - onBackupNowClick: () -> Unit, - onBackupLaterClick: () -> Unit + isPaidTier: Boolean, + onBackupNowClick: () -> Unit ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -106,8 +108,14 @@ private fun CreateBackupBottomSheetContent( textAlign = TextAlign.Center ) + val body = if (isPaidTier) { + stringResource(id = R.string.CreateBackupBottomSheet__depending_on_the_size) + } else { + stringResource(id = R.string.CreateBackupBottomSheet__free_tier) + } + Text( - text = stringResource(id = R.string.CreateBackupBottomSheet__depending_on_the_size), + text = body, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, @@ -128,11 +136,22 @@ private fun CreateBackupBottomSheetContent( @SignalPreview @Composable -private fun CreateBackupBottomSheetContentPreview() { +private fun CreateBackupBottomSheetContentPaidPreview() { Previews.BottomSheetPreview { CreateBackupBottomSheetContent( - onBackupNowClick = {}, - onBackupLaterClick = {} + isPaidTier = true, + onBackupNowClick = {} + ) + } +} + +@SignalPreview +@Composable +private fun CreateBackupBottomSheetContentFreePreview() { + Previews.BottomSheetPreview { + CreateBackupBottomSheetContent( + isPaidTier = false, + onBackupNowClick = {} ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt index 1f97f3017f..3d827c66c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt @@ -113,6 +113,7 @@ import org.thoughtcrime.securesms.help.HelpFragment import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState import org.thoughtcrime.securesms.payments.FiatMoneyUtil +import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.viewModel @@ -285,6 +286,14 @@ class RemoteBackupsSettingsFragment : ComposeFragment() { override fun onIncludeDebuglogClick(newState: Boolean) { viewModel.setIncludeDebuglog(newState) } + + override fun onMediaBackupSizeClick() { + viewModel.requestDialog(RemoteBackupsSettingsState.Dialog.FREE_TIER_MEDIA_EXPLAINER) + } + + override fun onFreeTierBackupSizeLearnMore() { + CommunicationActions.openBrowserLink(requireContext(), "https://support.signal.org/hc/articles/9708267671322") + } } private fun displayBackupKey() { @@ -387,6 +396,8 @@ private interface ContentCallbacks { fun onDisplayDownloadingBackupDialog() = Unit fun onManageStorageClick() = Unit fun onIncludeDebuglogClick(newState: Boolean) = Unit + fun onMediaBackupSizeClick() = Unit + fun onFreeTierBackupSizeLearnMore() = Unit object Empty : ContentCallbacks } @@ -634,6 +645,18 @@ private fun RemoteBackupsSettingsContent( onResumeOverCellularClick = contentCallbacks::onRestoreUsingCellularClick ) } + + RemoteBackupsSettingsState.Dialog.FREE_TIER_MEDIA_EXPLAINER -> { + Dialogs.SimpleAlertDialog( + title = stringResource(R.string.RemoteBackupsSettingsFragment__free_tier_storage_title), + body = pluralStringResource(R.plurals.RemoteBackupsSettingsFragment__backup_frequency_dialog_body, state.freeTierMediaRetentionDays, state.freeTierMediaRetentionDays), + confirm = stringResource(android.R.string.ok), + dismiss = stringResource(R.string.RemoteBackupsSettingsFragment__learn_more), + onConfirm = {}, + onDismiss = contentCallbacks::onDialogDismissed, + onDeny = contentCallbacks::onFreeTierBackupSizeLearnMore + ) + } } val snackbarMessageId = remember(state.snackbar) { @@ -902,6 +925,7 @@ private fun LazyListScope.appendBackupDetailsItems( item { InProgressBackupRow( archiveUploadProgressState = backupProgress, + isPaidTier = state.tier == MessageBackupTier.PAID, canBackupMessagesRun = state.canBackupMessagesJobRun, canBackupUsingCellular = state.canBackUpUsingCellular, cancelArchiveUpload = contentCallbacks::onCancelUploadClick @@ -909,15 +933,15 @@ private fun LazyListScope.appendBackupDetailsItems( } } - if (state.backupState.isLikelyPaidTier()) { - item { - val sizeText = if (state.backupMediaSize < 0L) { - stringResource(R.string.RemoteBackupsSettingsFragment__calculating) - } else { - state.backupMediaSize.bytes.toUnitString() - } + item { + val sizeText = if (state.backupMediaSize < 0L) { + stringResource(R.string.RemoteBackupsSettingsFragment__calculating) + } else { + state.backupMediaSize.bytes.toUnitString() + } - Rows.TextRow(text = { + Rows.TextRow( + text = { Column { Text( text = stringResource(id = R.string.RemoteBackupsSettingsFragment__backup_size), @@ -930,8 +954,13 @@ private fun LazyListScope.appendBackupDetailsItems( color = MaterialTheme.colorScheme.onSurfaceVariant ) } - }) - } + }, + onClick = if (state.backupMediaSize >= 0L && state.tier == MessageBackupTier.FREE) { + { contentCallbacks.onMediaBackupSizeClick() } + } else { + null + } + ) } item { @@ -1353,6 +1382,7 @@ private fun SubscriptionMismatchMissingGooglePlayCard( @Composable private fun InProgressBackupRow( archiveUploadProgressState: ArchiveUploadProgressState, + isPaidTier: Boolean, canBackupMessagesRun: Boolean = true, canBackupUsingCellular: Boolean = true, cancelArchiveUpload: () -> Unit = {} @@ -1390,7 +1420,7 @@ private fun InProgressBackupRow( } Text( - text = getProgressStateMessage(archiveUploadProgressState, canBackupMessagesRun, canBackupUsingCellular), + text = getProgressStateMessage(archiveUploadProgressState, isPaidTier, canBackupMessagesRun, canBackupUsingCellular), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -1428,11 +1458,11 @@ private fun ArchiveProgressIndicator( } @Composable -private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadProgressState, canBackupMessagesRun: Boolean, canBackupUsingCellular: Boolean): String { +private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadProgressState, isPaidTier: Boolean, canBackupMessagesRun: Boolean, canBackupUsingCellular: Boolean): String { return when (archiveUploadProgressState.state) { ArchiveUploadProgressState.State.None, ArchiveUploadProgressState.State.UserCanceled -> stringResource(R.string.RemoteBackupsSettingsFragment__processing_backup) ArchiveUploadProgressState.State.Export -> getBackupExportPhaseProgressString(archiveUploadProgressState, canBackupMessagesRun, canBackupUsingCellular) - ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> getBackupUploadPhaseProgressString(archiveUploadProgressState) + ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> getBackupUploadPhaseProgressString(archiveUploadProgressState, isPaidTier) } } @@ -1464,12 +1494,16 @@ private fun getBackupExportPhaseProgressString(state: ArchiveUploadProgressState } @Composable -private fun getBackupUploadPhaseProgressString(state: ArchiveUploadProgressState): String { +private fun getBackupUploadPhaseProgressString(state: ArchiveUploadProgressState, isPaidTier: Boolean): String { val formattedTotalBytes = state.uploadBytesTotal.bytes.toUnitString() val formattedUploadedBytes = state.uploadBytesUploaded.bytes.toUnitString() val percent = (state.uploadProgress() * 100).toInt() - return stringResource(R.string.RemoteBackupsSettingsFragment__uploading_s_of_s_d, formattedUploadedBytes, formattedTotalBytes, percent) + return if (isPaidTier) { + stringResource(R.string.RemoteBackupsSettingsFragment__uploading_s_of_s_d, formattedUploadedBytes, formattedTotalBytes, percent) + } else { + stringResource(R.string.RemoteBackupsSettingsFragment__uploading_d, percent) + } } @Composable @@ -1949,18 +1983,20 @@ private fun LastBackupRowPreview() { private fun InProgressRowPreview() { Previews.Preview { Column { - InProgressBackupRow(archiveUploadProgressState = ArchiveUploadProgressState()) + InProgressBackupRow(archiveUploadProgressState = ArchiveUploadProgressState(), isPaidTier = true) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( state = ArchiveUploadProgressState.State.Export, backupPhase = ArchiveUploadProgressState.BackupPhase.BackupPhaseNone - ) + ), + isPaidTier = true ) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( state = ArchiveUploadProgressState.State.Export, backupPhase = ArchiveUploadProgressState.BackupPhase.Account - ) + ), + isPaidTier = true ) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( @@ -1968,7 +2004,8 @@ private fun InProgressRowPreview() { backupPhase = ArchiveUploadProgressState.BackupPhase.Message, frameExportCount = 1, frameTotalCount = 1 - ) + ), + isPaidTier = true ) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( @@ -1976,7 +2013,8 @@ private fun InProgressRowPreview() { backupPhase = ArchiveUploadProgressState.BackupPhase.Message, frameExportCount = 1000, frameTotalCount = 100_000 - ) + ), + isPaidTier = true ) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( @@ -1984,7 +2022,8 @@ private fun InProgressRowPreview() { backupPhase = ArchiveUploadProgressState.BackupPhase.Message, frameExportCount = 1_000_000, frameTotalCount = 100_000 - ) + ), + isPaidTier = true ) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( @@ -1994,7 +2033,19 @@ private fun InProgressRowPreview() { backupFileTotalBytes = 50.mebiBytes.inWholeBytes, mediaUploadedBytes = 0, mediaTotalBytes = 0 - ) + ), + isPaidTier = true + ) + InProgressBackupRow( + archiveUploadProgressState = ArchiveUploadProgressState( + state = ArchiveUploadProgressState.State.UploadBackupFile, + backupPhase = ArchiveUploadProgressState.BackupPhase.BackupPhaseNone, + backupFileUploadedBytes = 10.mebiBytes.inWholeBytes, + backupFileTotalBytes = 50.mebiBytes.inWholeBytes, + mediaUploadedBytes = 0, + mediaTotalBytes = 0 + ), + isPaidTier = false ) InProgressBackupRow( archiveUploadProgressState = ArchiveUploadProgressState( @@ -2004,7 +2055,8 @@ private fun InProgressRowPreview() { backupFileTotalBytes = 50.mebiBytes.inWholeBytes, mediaUploadedBytes = 100.mebiBytes.inWholeBytes, mediaTotalBytes = 1.gibiBytes.inWholeBytes - ) + ), + isPaidTier = true ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsState.kt index 4a43259188..43da7bc18a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsState.kt @@ -31,7 +31,8 @@ data class RemoteBackupsSettingsState( val canBackupMessagesJobRun: Boolean = false, val backupMediaDetails: BackupMediaDetails? = null, val showBackupCreateFailedError: Boolean = false, - val showBackupCreateCouldNotCompleteError: Boolean = false + val showBackupCreateCouldNotCompleteError: Boolean = false, + val freeTierMediaRetentionDays: Int = -1 ) { data class BackupMediaDetails( @@ -50,7 +51,8 @@ data class RemoteBackupsSettingsState( SUBSCRIPTION_NOT_FOUND, SKIP_MEDIA_RESTORE_PROTECTION, CANCEL_MEDIA_RESTORE_PROTECTION, - RESTORE_OVER_CELLULAR_PROTECTION + RESTORE_OVER_CELLULAR_PROTECTION, + FREE_TIER_MEDIA_EXPLAINER } enum class Snackbar { 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 c9a8f663ef..df6405075c 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 @@ -33,6 +33,9 @@ import org.thoughtcrime.securesms.backup.DeletionState import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgressState.RestoreStatus import org.thoughtcrime.securesms.backup.v2.BackupRepository +import org.thoughtcrime.securesms.backup.v2.MessageBackupTier +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType +import org.thoughtcrime.securesms.components.settings.app.backups.BackupState import org.thoughtcrime.securesms.components.settings.app.backups.BackupStateObserver import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository import org.thoughtcrime.securesms.database.InAppPaymentTable @@ -47,6 +50,8 @@ import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.api.NetworkResult +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds /** @@ -163,11 +168,12 @@ class RemoteBackupsSettingsViewModel : ViewModel() { } } - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { BackupStateObserver(viewModelScope).backupState.collect { state -> _state.update { it.copy(backupState = state) } + refreshState(null) } } @@ -258,8 +264,10 @@ class RemoteBackupsSettingsViewModel : ViewModel() { private fun refreshBackupMediaSizeState() { _state.update { + val (mediaSize, mediaRetentionDays) = getBackupMediaSize(it.tier, (it.backupState as? BackupState.WithTypeAndRenewalTime)?.messageBackupsType) it.copy( - backupMediaSize = getBackupMediaSize(), + backupMediaSize = mediaSize, + freeTierMediaRetentionDays = mediaRetentionDays, backupMediaDetails = if (RemoteConfig.internalUser || Environment.IS_STAGING) { RemoteBackupsSettingsState.BackupMediaDetails( awaitingRestore = SignalDatabase.attachments.getRemainingRestorableAttachmentSize().bytes, @@ -287,7 +295,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() { if (paidType is NetworkResult.Success) { val remoteStorageAllowance = paidType.result.storageAllowanceBytes.bytes - val estimatedSize = SignalDatabase.attachments.getEstimatedArchiveMediaSize().bytes + val estimatedSize = getBackupMediaSize(paidType.result.tier, paidType.result).first.bytes if (estimatedSize + 300.mebiBytes <= remoteStorageAllowance) { BackupRepository.clearOutOfRemoteStorageSpaceError() @@ -303,13 +311,16 @@ class RemoteBackupsSettingsViewModel : ViewModel() { } } + val (mediaSize, mediaRetentionDays) = getBackupMediaSize(_state.value.tier, (_state.value.backupState as? BackupState.WithTypeAndRenewalTime)?.messageBackupsType) + _state.update { it.copy( tier = SignalStore.backup.backupTier, backupsEnabled = SignalStore.backup.areBackupsEnabled, lastBackupTimestamp = SignalStore.backup.lastBackupTime, canBackupMessagesJobRun = BackupMessagesConstraint.isMet(AppDependencies.application), - backupMediaSize = getBackupMediaSize(), + backupMediaSize = mediaSize, + freeTierMediaRetentionDays = mediaRetentionDays, canBackUpUsingCellular = SignalStore.backup.backupWithCellular, canRestoreUsingCellular = SignalStore.backup.restoreWithCellular, isOutOfStorageSpace = BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx(), @@ -320,11 +331,39 @@ class RemoteBackupsSettingsViewModel : ViewModel() { } } - private fun getBackupMediaSize(): Long { - return if (SignalStore.backup.hasBackupBeenUploaded || SignalStore.backup.lastBackupTime > 0L) { - SignalDatabase.attachments.getEstimatedArchiveMediaSize() + private fun getBackupMediaSize(tier: MessageBackupTier?, messageBackupsType: MessageBackupsType?): Pair { + if (tier == null) { + return -1L to 0 + } + + val mediaRetentionDays = if (messageBackupsType is MessageBackupsType.Free) { + messageBackupsType.mediaRetentionDays } else { - 0L + when (tier) { + MessageBackupTier.FREE -> { + when (val result = BackupRepository.getFreeType()) { + is NetworkResult.Success -> result.result.mediaRetentionDays + else -> RemoteConfig.messageQueueTime.milliseconds.inWholeDays.toInt() + } + } + + MessageBackupTier.PAID -> 0 + } + } + + return if (SignalStore.backup.hasBackupBeenUploaded || SignalStore.backup.lastBackupTime > 0L) { + when (tier) { + MessageBackupTier.PAID -> SignalDatabase.attachments.getPaidEstimatedArchiveMediaSize() to -1 + MessageBackupTier.FREE -> { + if (mediaRetentionDays > 0) { + SignalDatabase.attachments.getFreeEstimatedArchiveMediaSize(System.currentTimeMillis() - mediaRetentionDays.days.inWholeMilliseconds) to mediaRetentionDays + } else { + -1L to -1 + } + } + } + } else { + 0L to mediaRetentionDays } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt index 871f6eee11..6fa6102998 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt @@ -3009,28 +3009,40 @@ class AttachmentTable( .readToList { AttachmentId(it.requireLong(ID)) } } - fun getEstimatedArchiveMediaSize(): Long { - val estimatedThumbnailCount = readableDatabase - .select("COUNT(*)") - .from( - """ - ( - SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY - FROM $TABLE_NAME INNER JOIN ${MessageTable.TABLE_NAME} AS m ON $TABLE_NAME.$MESSAGE_ID = m.${MessageTable.ID} - WHERE - $DATA_FILE NOT NULL AND - $DATA_HASH_END NOT NULL AND - $REMOTE_KEY NOT NULL AND - $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND - $ARCHIVE_TRANSFER_STATE != ${ArchiveTransferState.PERMANENT_FAILURE.value} AND - ($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%') AND - $CONTENT_TYPE != 'image/svg+xml' AND - ${getMessageDoesNotExpireWithinTimeoutClause(tablePrefix = "m")} + fun getPaidEstimatedArchiveMediaSize(): Long { + return getEstimatedArchiveMediaSize() + } + + fun getFreeEstimatedArchiveMediaSize(afterTimestamp: Long): Long { + return getEstimatedArchiveMediaSize(afterTimestamp) + } + + private fun getEstimatedArchiveMediaSize(afterTimestamp: Long = 0L): Long { + val estimatedThumbnailCount = if (afterTimestamp == 0L) { + readableDatabase + .select("COUNT(*)") + .from( + """ + ( + SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY + FROM $TABLE_NAME INNER JOIN ${MessageTable.TABLE_NAME} AS m ON $TABLE_NAME.$MESSAGE_ID = m.${MessageTable.ID} + WHERE + $DATA_FILE NOT NULL AND + $DATA_HASH_END NOT NULL AND + $REMOTE_KEY NOT NULL AND + $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND + $ARCHIVE_TRANSFER_STATE != ${ArchiveTransferState.PERMANENT_FAILURE.value} AND + ($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%') AND + $CONTENT_TYPE != 'image/svg+xml' AND + ${getMessageDoesNotExpireWithinTimeoutClause(tablePrefix = "m")} + ) + """ ) - """ - ) - .run() - .readToSingleLong(0L) + .run() + .readToSingleLong(0L) + } else { + 0 + } val uploadedAttachmentBytes = readableDatabase .rawQuery( @@ -3045,6 +3057,7 @@ class AttachmentTable( $REMOTE_KEY NOT NULL AND $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND $ARCHIVE_TRANSFER_STATE != ${ArchiveTransferState.PERMANENT_FAILURE.value} AND + ${if (afterTimestamp > 0) "m.${MessageTable.DATE_RECEIVED} >= $afterTimestamp AND" else ""} ${getMessageDoesNotExpireWithinTimeoutClause(tablePrefix = "m")} ) """ diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt index 2127b3dabf..f0bc624677 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt @@ -166,7 +166,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A Log.w(TAG, "[$attachmentId] Insufficient storage space! Can't upload!") val remoteStorageQuota = getServerQuota() ?: return Result.retry(defaultBackoff()).logW(TAG, "[$attachmentId] Failed to fetch server quota! Retrying.") - if (SignalDatabase.attachments.getEstimatedArchiveMediaSize() > remoteStorageQuota.inWholeBytes) { + if (SignalDatabase.attachments.getPaidEstimatedArchiveMediaSize() > remoteStorageQuota.inWholeBytes) { BackupRepository.markOutOfRemoteStorageSpaceError() return Result.failure() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/data/QuickRegistrationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/data/QuickRegistrationRepository.kt index f9d1226cb2..4bfa8d4cc9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/data/QuickRegistrationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/data/QuickRegistrationRepository.kt @@ -87,7 +87,7 @@ object QuickRegistrationRepository { MessageBackupTier.FREE -> RegistrationProvisionMessage.Tier.FREE null -> null }, - backupSizeBytes = SignalDatabase.attachments.getEstimatedArchiveMediaSize().takeIf { it > 0 }, + backupSizeBytes = if (SignalStore.backup.backupTier == MessageBackupTier.PAID) SignalDatabase.attachments.getPaidEstimatedArchiveMediaSize().takeIf { it > 0 } else null, restoreMethodToken = restoreMethodToken, aciIdentityKeyPublic = SignalStore.account.aciIdentityKey.publicKey.serialize().toByteString(), aciIdentityKeyPrivate = SignalStore.account.aciIdentityKey.privateKey.serialize().toByteString(), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e961e07b04..c6638b5126 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -862,8 +862,10 @@ You\'re all set. Start your backup now. - + Depending on the size of your backup, this could take a long time. You can use your phone as you normally do while the backup takes place. + + Media is added to your backup as you send and receive messages. Can\'t restore media @@ -8188,6 +8190,13 @@ To view your key, confirm it\'s you Turn off and delete backup + + Backup size + + + Your backup includes all of your text messages and your last %1$d day of media. The size will change as new media is received and old media expires. + Your backup includes all of your text messages and your last %1$d days of media. The size will change as new media is received and old media expires. + Backup deleted and turned off. @@ -8331,6 +8340,8 @@ A network error occurred. Please check your internet connection and try again. Uploading: %1$s of %2$s (%3$d%%) + + Uploading: %1$d%% Details