mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Unify backup creation progress model for local backups.
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.backups.local
|
||||
|
||||
/**
|
||||
* Progress indicator state for the on-device backups creation/verification workflow.
|
||||
*/
|
||||
sealed class BackupProgressState {
|
||||
data object Idle : BackupProgressState()
|
||||
|
||||
/**
|
||||
* Represents either backup creation or verification progress.
|
||||
*
|
||||
* @param summary High-level status label (e.g. "In progress…", "Verifying backup…")
|
||||
* @param percentLabel Secondary progress label (either a percent string or a count-based string)
|
||||
* @param progressFraction Optional progress fraction in \\([0, 1]\\). Null indicates indeterminate progress.
|
||||
*/
|
||||
data class InProgress(
|
||||
val summary: String,
|
||||
val percentLabel: String,
|
||||
val progressFraction: Float?
|
||||
) : BackupProgressState()
|
||||
}
|
||||
@@ -5,10 +5,8 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.backups.local
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
@@ -38,6 +36,8 @@ import org.signal.core.ui.compose.Rows
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.Snackbars
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.BackupCreationProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.status.BackupCreationProgressRow
|
||||
import org.thoughtcrime.securesms.components.compose.rememberBiometricsAuthentication
|
||||
import org.thoughtcrime.securesms.util.BackupUtil
|
||||
import org.signal.core.ui.R as CoreUiR
|
||||
@@ -120,51 +120,26 @@ internal fun LocalBackupsSettingsScreen(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val isCreating = state.progress is BackupProgressState.InProgress
|
||||
val isCreating = state.progress !is BackupCreationProgress.Idle
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.BackupsPreferenceFragment__create_backup),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
if (state.progress is BackupProgressState.InProgress) {
|
||||
if (isCreating) {
|
||||
item {
|
||||
BackupCreationProgressRow(
|
||||
progress = state.progress,
|
||||
isRemote = false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = state.progress.summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
text = stringResource(id = R.string.BackupsPreferenceFragment__create_backup),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
if (state.progress.progressFraction == null) {
|
||||
LinearProgressIndicator(
|
||||
trackColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
)
|
||||
} else {
|
||||
LinearProgressIndicator(
|
||||
trackColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
progress = { state.progress.progressFraction },
|
||||
drawStopIndicator = {},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = state.progress.percentLabel,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = state.lastBackupLabel.orEmpty(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
@@ -172,11 +147,10 @@ internal fun LocalBackupsSettingsScreen(
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !isCreating,
|
||||
onClick = callback::onCreateBackupClick
|
||||
)
|
||||
},
|
||||
onClick = callback::onCreateBackupClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
@@ -224,7 +198,7 @@ internal fun LocalBackupsSettingsScreen(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = dimensionResource(id = org.signal.core.ui.R.dimen.gutter),
|
||||
horizontal = dimensionResource(id = CoreUiR.dimen.gutter),
|
||||
vertical = 16.dp
|
||||
)
|
||||
)
|
||||
@@ -294,7 +268,7 @@ private fun LocalBackupsSettingsEnabledIdlePreview() {
|
||||
lastBackupLabel = "Last backup: 1 hour ago",
|
||||
folderDisplayName = "/storage/emulated/0/Signal/Backups",
|
||||
scheduleTimeLabel = "1:00 AM",
|
||||
progress = BackupProgressState.Idle
|
||||
progress = BackupCreationProgress.Idle
|
||||
),
|
||||
callback = LocalBackupsSettingsCallback.Empty
|
||||
)
|
||||
@@ -303,7 +277,7 @@ private fun LocalBackupsSettingsEnabledIdlePreview() {
|
||||
|
||||
@DayNightPreview
|
||||
@Composable
|
||||
private fun LocalBackupsSettingsEnabledInProgressIndeterminatePreview() {
|
||||
private fun LocalBackupsSettingsEnabledExportingIndeterminatePreview() {
|
||||
Previews.Preview {
|
||||
LocalBackupsSettingsScreen(
|
||||
state = LocalBackupsSettingsState(
|
||||
@@ -311,10 +285,8 @@ private fun LocalBackupsSettingsEnabledInProgressIndeterminatePreview() {
|
||||
lastBackupLabel = "Last backup: 1 hour ago",
|
||||
folderDisplayName = "/storage/emulated/0/Signal/Backups",
|
||||
scheduleTimeLabel = "1:00 AM",
|
||||
progress = BackupProgressState.InProgress(
|
||||
summary = "In progress…",
|
||||
percentLabel = "123 so far…",
|
||||
progressFraction = null
|
||||
progress = BackupCreationProgress.Exporting(
|
||||
phase = BackupCreationProgress.ExportPhase.ACCOUNT
|
||||
)
|
||||
),
|
||||
callback = LocalBackupsSettingsCallback.Empty
|
||||
@@ -324,7 +296,7 @@ private fun LocalBackupsSettingsEnabledInProgressIndeterminatePreview() {
|
||||
|
||||
@DayNightPreview
|
||||
@Composable
|
||||
private fun LocalBackupsSettingsEnabledInProgressPercentPreview() {
|
||||
private fun LocalBackupsSettingsEnabledExportingMessagesPreview() {
|
||||
Previews.Preview {
|
||||
LocalBackupsSettingsScreen(
|
||||
state = LocalBackupsSettingsState(
|
||||
@@ -332,10 +304,31 @@ private fun LocalBackupsSettingsEnabledInProgressPercentPreview() {
|
||||
lastBackupLabel = "Last backup: 1 hour ago",
|
||||
folderDisplayName = "/storage/emulated/0/Signal/Backups",
|
||||
scheduleTimeLabel = "1:00 AM",
|
||||
progress = BackupProgressState.InProgress(
|
||||
summary = "In progress…",
|
||||
percentLabel = "42.0% so far…",
|
||||
progressFraction = 0.42f
|
||||
progress = BackupCreationProgress.Exporting(
|
||||
phase = BackupCreationProgress.ExportPhase.MESSAGE,
|
||||
frameExportCount = 42000,
|
||||
frameTotalCount = 100000
|
||||
)
|
||||
),
|
||||
callback = LocalBackupsSettingsCallback.Empty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreview
|
||||
@Composable
|
||||
private fun LocalBackupsSettingsEnabledTransferringPreview() {
|
||||
Previews.Preview {
|
||||
LocalBackupsSettingsScreen(
|
||||
state = LocalBackupsSettingsState(
|
||||
backupsEnabled = true,
|
||||
lastBackupLabel = "Last backup: 1 hour ago",
|
||||
folderDisplayName = "/storage/emulated/0/Signal/Backups",
|
||||
scheduleTimeLabel = "1:00 AM",
|
||||
progress = BackupCreationProgress.Transferring(
|
||||
completed = 50,
|
||||
total = 200,
|
||||
mediaPhase = true
|
||||
)
|
||||
),
|
||||
callback = LocalBackupsSettingsCallback.Empty
|
||||
@@ -353,7 +346,7 @@ private fun LocalBackupsSettingsEnabledNonLegacyPreview() {
|
||||
lastBackupLabel = "Last backup: 1 hour ago",
|
||||
folderDisplayName = "Signal Backups",
|
||||
scheduleTimeLabel = "1:00 AM",
|
||||
progress = BackupProgressState.Idle
|
||||
progress = BackupCreationProgress.Idle
|
||||
),
|
||||
callback = LocalBackupsSettingsCallback.Empty
|
||||
)
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.components.settings.app.backups.local
|
||||
|
||||
import org.thoughtcrime.securesms.backup.BackupCreationProgress
|
||||
|
||||
/**
|
||||
* Immutable state for the on-device (legacy) backups settings screen.
|
||||
* Immutable state for the on-device backups settings screen.
|
||||
*
|
||||
* This is intended to be the single source of truth for UI rendering (i.e. a single `StateFlow`
|
||||
* emission fully describes what the screen should display).
|
||||
@@ -16,5 +18,5 @@ data class LocalBackupsSettingsState(
|
||||
val lastBackupLabel: String? = null,
|
||||
val folderDisplayName: String? = null,
|
||||
val scheduleTimeLabel: String? = null,
|
||||
val progress: BackupProgressState = BackupProgressState.Idle
|
||||
val progress: BackupCreationProgress = BackupCreationProgress.Idle
|
||||
)
|
||||
|
||||
@@ -20,8 +20,9 @@ import org.greenrobot.eventbus.ThreadMode
|
||||
import org.signal.core.ui.util.StorageUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.BackupCreationEvent
|
||||
import org.thoughtcrime.securesms.backup.BackupCreationProgress
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase
|
||||
import org.thoughtcrime.securesms.backup.v2.LocalBackupV2Event
|
||||
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeyCredentialManagerHandler
|
||||
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeySaveState
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -31,7 +32,6 @@ import org.thoughtcrime.securesms.util.BackupUtil
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.formatHours
|
||||
import java.text.NumberFormat
|
||||
import java.time.LocalTime
|
||||
import java.util.Locale
|
||||
|
||||
@@ -44,11 +44,6 @@ class LocalBackupsViewModel : ViewModel(), BackupKeyCredentialManagerHandler {
|
||||
private val TAG = Log.tag(LocalBackupsViewModel::class)
|
||||
}
|
||||
|
||||
private val formatter: NumberFormat = NumberFormat.getInstance().apply {
|
||||
minimumFractionDigits = 1
|
||||
maximumFractionDigits = 1
|
||||
}
|
||||
|
||||
private val internalSettingsState = MutableStateFlow(
|
||||
LocalBackupsSettingsState(
|
||||
backupsEnabled = SignalStore.backup.newLocalBackupsEnabled,
|
||||
@@ -117,46 +112,14 @@ class LocalBackupsViewModel : ViewModel(), BackupKeyCredentialManagerHandler {
|
||||
}
|
||||
|
||||
fun onBackupStarted() {
|
||||
val context = AppDependencies.application
|
||||
internalSettingsState.update {
|
||||
it.copy(
|
||||
progress = BackupProgressState.InProgress(
|
||||
summary = context.getString(R.string.BackupsPreferenceFragment__in_progress),
|
||||
percentLabel = context.getString(R.string.BackupsPreferenceFragment__d_so_far, 0),
|
||||
progressFraction = null
|
||||
)
|
||||
)
|
||||
it.copy(progress = BackupCreationProgress.Exporting(phase = BackupCreationProgress.ExportPhase.NONE))
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onBackupEvent(event: LocalBackupV2Event) {
|
||||
val context = AppDependencies.application
|
||||
when (event.type) {
|
||||
LocalBackupV2Event.Type.FINISHED -> {
|
||||
internalSettingsState.update { it.copy(progress = BackupProgressState.Idle) }
|
||||
}
|
||||
|
||||
else -> {
|
||||
val summary = context.getString(R.string.BackupsPreferenceFragment__in_progress)
|
||||
val progressState = if (event.estimatedTotalCount == 0L) {
|
||||
BackupProgressState.InProgress(
|
||||
summary = summary,
|
||||
percentLabel = context.getString(R.string.BackupsPreferenceFragment__d_so_far, event.count),
|
||||
progressFraction = null
|
||||
)
|
||||
} else {
|
||||
val fraction = ((event.count / event.estimatedTotalCount.toDouble()) / 100.0).toFloat().coerceIn(0f, 1f)
|
||||
BackupProgressState.InProgress(
|
||||
summary = summary,
|
||||
percentLabel = context.getString(R.string.BackupsPreferenceFragment__s_so_far, formatter.format((event.count / event.estimatedTotalCount.toDouble()))),
|
||||
progressFraction = fraction
|
||||
)
|
||||
}
|
||||
|
||||
internalSettingsState.update { it.copy(progress = progressState) }
|
||||
}
|
||||
}
|
||||
fun onBackupEvent(event: BackupCreationEvent.LocalEncrypted) {
|
||||
internalSettingsState.update { it.copy(progress = event.progress) }
|
||||
}
|
||||
|
||||
override fun updateBackupKeySaveState(newState: BackupKeySaveState?) {
|
||||
|
||||
Reference in New Issue
Block a user