diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusRow.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusRow.kt
index 042fc654dc..055856ebae 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusRow.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/status/BackupStatusRow.kt
@@ -43,6 +43,8 @@ import org.thoughtcrime.securesms.R
import kotlin.math.roundToInt
import org.signal.core.ui.R as CoreUiR
+private val YELLOW_DOT = Color(0xFFFFCC00)
+
/**
* Backup status displayable as a row on a settings page.
*/
@@ -106,12 +108,12 @@ fun BackupStatusRow(
BackupStatusData.CouldNotCompleteBackup -> {
val inlineContentMap = mapOf(
"yellow_bullet" to InlineTextContent(
- Placeholder(12.sp, 12.sp, PlaceholderVerticalAlign.TextCenter)
+ Placeholder(20.sp, 12.sp, PlaceholderVerticalAlign.TextCenter)
) {
Box(
modifier = Modifier
.size(12.dp)
- .background(color = backupStatusData.iconColors.foreground, shape = CircleShape)
+ .background(color = YELLOW_DOT, shape = CircleShape)
)
}
)
@@ -129,12 +131,12 @@ fun BackupStatusRow(
BackupStatusData.BackupFailed -> {
val inlineContentMap = mapOf(
"yellow_bullet" to InlineTextContent(
- Placeholder(12.sp, 12.sp, PlaceholderVerticalAlign.TextCenter)
+ Placeholder(20.sp, 12.sp, PlaceholderVerticalAlign.TextCenter)
) {
Box(
modifier = Modifier
.size(12.dp)
- .background(color = backupStatusData.iconColors.foreground, shape = CircleShape)
+ .background(color = YELLOW_DOT, shape = CircleShape)
)
}
)
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 92cb1e246a..7074e2deb8 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
@@ -36,6 +36,7 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@@ -54,6 +55,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
@@ -61,6 +63,7 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
@@ -69,6 +72,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
+import com.google.android.material.progressindicator.LinearProgressIndicator
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.Dialogs
import org.signal.core.ui.compose.Dividers
@@ -101,8 +105,6 @@ import org.thoughtcrime.securesms.components.compose.TextWithBetaLabel
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.components.settings.app.subscription.MessageBackupsCheckoutLauncher.createBackupsCheckoutLauncher
import org.thoughtcrime.securesms.compose.ComposeFragment
-import org.thoughtcrime.securesms.fonts.SignalSymbols
-import org.thoughtcrime.securesms.fonts.SignalSymbols.signalSymbolText
import org.thoughtcrime.securesms.help.HelpFragment
import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
@@ -241,6 +243,10 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
// TODO - [backups] Need process here (cancel first?)
}
+ override fun onCancelUploadClick() {
+ viewModel.cancelUpload()
+ }
+
override fun onContactSupport() {
requireActivity().finish()
requireActivity().startActivity(AppSettingsActivity.help(requireContext(), HelpFragment.REMOTE_BACKUPS_INDEX))
@@ -328,6 +334,7 @@ private interface ContentCallbacks {
fun onBackupTypeActionClick(tier: MessageBackupTier) = Unit
fun onBackUpUsingCellularClick(canUseCellular: Boolean) = Unit
fun onBackupNowClick() = Unit
+ fun onCancelUploadClick() = Unit
fun onTurnOffAndDeleteBackupsClick() = Unit
fun onChangeBackupFrequencyClick() = Unit
fun onDialogDismissed() = Unit
@@ -445,43 +452,9 @@ private fun RemoteBackupsSettingsContent(
}
if (backupsEnabled) {
- if (backupRestoreState !is BackupRestoreState.None) {
- item {
- Dividers.Default()
- }
-
- if (backupRestoreState is BackupRestoreState.FromBackupStatusData) {
- item {
- BackupStatusRow(
- backupStatusData = backupRestoreState.backupStatusData,
- onCancelClick = contentCallbacks::onCancelMediaRestore,
- onSkipClick = contentCallbacks::onSkipMediaRestore,
- onLearnMoreClick = contentCallbacks::onLearnMoreAboutBackupFailure
- )
- }
-
- if (!canRestoreUsingCellular) {
- item {
- Rows.TextRow(
- text = stringResource(R.string.RemoteBackupsSettingsFragment__resume_download),
- icon = painterResource(R.drawable.symbol_arrow_circle_down_24),
- onClick = contentCallbacks::onRestoreUsingCellularConfirm
- )
- }
- }
- } else if (backupRestoreState is BackupRestoreState.Ready) {
- item {
- BackupReadyToDownloadRow(
- ready = backupRestoreState,
- backupState = backupState,
- onDownloadClick = contentCallbacks::onStartMediaRestore
- )
- }
- }
- }
-
appendBackupDetailsItems(
backupState = backupState,
+ backupRestoreState = backupRestoreState,
backupProgress = backupProgress,
lastBackupTimestamp = lastBackupTimestamp,
backupMediaSize = backupMediaSize,
@@ -618,6 +591,7 @@ private fun RemoteBackupsSettingsContent(
private fun LazyListScope.appendBackupDetailsItems(
backupState: RemoteBackupsSettingsState.BackupState,
+ backupRestoreState: BackupRestoreState,
backupProgress: ArchiveUploadProgressState?,
lastBackupTimestamp: Long,
backupMediaSize: Long,
@@ -634,6 +608,37 @@ private fun LazyListScope.appendBackupDetailsItems(
Texts.SectionHeader(text = stringResource(id = R.string.RemoteBackupsSettingsFragment__backup_details))
}
+ if (backupRestoreState !is BackupRestoreState.None) {
+ if (backupRestoreState is BackupRestoreState.FromBackupStatusData) {
+ item {
+ BackupStatusRow(
+ backupStatusData = backupRestoreState.backupStatusData,
+ onCancelClick = contentCallbacks::onCancelMediaRestore,
+ onSkipClick = contentCallbacks::onSkipMediaRestore,
+ onLearnMoreClick = contentCallbacks::onLearnMoreAboutBackupFailure
+ )
+ }
+
+ if (!canRestoreUsingCellular) {
+ item {
+ Rows.TextRow(
+ text = stringResource(R.string.RemoteBackupsSettingsFragment__resume_download),
+ icon = painterResource(R.drawable.symbol_arrow_circle_down_24),
+ onClick = contentCallbacks::onRestoreUsingCellularConfirm
+ )
+ }
+ }
+ } else if (backupRestoreState is BackupRestoreState.Ready) {
+ item {
+ BackupReadyToDownloadRow(
+ ready = backupRestoreState,
+ backupState = backupState,
+ onDownloadClick = contentCallbacks::onStartMediaRestore
+ )
+ }
+ }
+ }
+
if (backupProgress == null || backupProgress.state == ArchiveUploadProgressState.State.None) {
item {
LastBackupRow(
@@ -643,7 +648,10 @@ private fun LazyListScope.appendBackupDetailsItems(
}
} else {
item {
- InProgressBackupRow(archiveUploadProgressState = backupProgress)
+ InProgressBackupRow(
+ archiveUploadProgressState = backupProgress,
+ cancelArchiveUpload = contentCallbacks::onCancelUploadClick
+ )
}
}
@@ -736,10 +744,7 @@ private fun BackupCard(
}
Text(
- text = signalSymbolText(
- text = title,
- glyphStart = if (backupState.isActive()) SignalSymbols.Glyph.CHECK else null
- ),
+ text = title,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium
)
@@ -834,7 +839,7 @@ private fun CallToActionButton(
text: String,
onClick: () -> Unit
) {
- Buttons.LargeTonal(
+ Buttons.MediumTonal(
onClick = onClick,
colors = ButtonDefaults.filledTonalButtonColors().copy(
containerColor = SignalTheme.colors.colorTransparent5,
@@ -1010,7 +1015,7 @@ private fun SubscriptionNotFoundCard(
Row(
horizontalArrangement = spacedBy(16.dp)
) {
- Buttons.LargeTonal(
+ Buttons.MediumTonal(
onClick = onRenewClick,
colors = ButtonDefaults.filledTonalButtonColors().copy(
containerColor = SignalTheme.colors.colorTransparent5,
@@ -1025,7 +1030,7 @@ private fun SubscriptionNotFoundCard(
)
}
- Buttons.LargeTonal(
+ Buttons.MediumTonal(
onClick = onLearnMoreClick,
colors = ButtonDefaults.filledTonalButtonColors().copy(
containerColor = SignalTheme.colors.colorTransparent5,
@@ -1060,7 +1065,8 @@ private fun SubscriptionMismatchMissingGooglePlayCard(
@Composable
private fun InProgressBackupRow(
- archiveUploadProgressState: ArchiveUploadProgressState
+ archiveUploadProgressState: ArchiveUploadProgressState,
+ cancelArchiveUpload: () -> Unit = {}
) {
Row(
modifier = Modifier
@@ -1068,26 +1074,23 @@ private fun InProgressBackupRow(
.padding(top = 16.dp, bottom = 14.dp)
) {
Column(
- modifier = Modifier.weight(1f)
+ modifier = Modifier.weight(1f),
+ verticalArrangement = spacedBy(12.dp)
) {
when (archiveUploadProgressState.state) {
ArchiveUploadProgressState.State.None -> {
- LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
+ ArchiveProgressIndicator()
}
ArchiveUploadProgressState.State.Export -> {
val progressValue by animateFloatAsState(targetValue = archiveUploadProgressState.frameExportProgress(), animationSpec = tween(durationMillis = 250))
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
- progress = { progressValue },
- drawStopIndicator = {}
- )
+ ArchiveProgressIndicator(progress = { progressValue })
}
ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> {
val progressValue by animateFloatAsState(targetValue = archiveUploadProgressState.uploadProgress(), animationSpec = tween(durationMillis = 250))
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
+ ArchiveProgressIndicator(
progress = { progressValue },
- drawStopIndicator = {}
+ isCancelable = true,
+ cancel = cancelArchiveUpload
)
}
}
@@ -1101,6 +1104,31 @@ private fun InProgressBackupRow(
}
}
+@Composable
+private fun ArchiveProgressIndicator(
+ progress: () -> Float = { 0f },
+ isCancelable: Boolean = false,
+ cancel: () -> Unit = {}
+) {
+ Row {
+ LinearProgressIndicator(
+ trackColor = MaterialTheme.colorScheme.secondaryContainer,
+ progress = progress,
+ drawStopIndicator = {},
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ if (isCancelable) {
+ IconButton(onClick = cancel) {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.symbol_x_24),
+ contentDescription = stringResource(R.string.RemoteBackupsSettingsFragment__cancel_upload)
+ )
+ }
+ }
+ }
+}
+
@Composable
private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadProgressState): String {
return when (archiveUploadProgressState.state) {
@@ -1735,13 +1763,6 @@ private fun BackupFrequencyDialogPreview() {
}
}
-private data class BackupProgress(
- val completed: Long,
- val total: Long
-) {
- val progress: Float = if (total > 0) completed / total.toFloat() else 0f
-}
-
private fun ArchiveUploadProgressState.frameExportProgress(): Float {
return if (this.frameTotalCount == 0L) {
0f
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 cef3949d27..53813766d8 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
@@ -213,6 +213,10 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
BackupMessagesJob.enqueue()
}
+ fun cancelUpload() {
+ // TODO [message-backups] -- Perform cancel of media upload.
+ }
+
private suspend fun refreshState(lastPurchase: InAppPaymentTable.InAppPayment?) {
try {
performStateRefresh(lastPurchase)
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 2f8b619dfa..90f9a86b01 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java
@@ -393,7 +393,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
initializeVoiceNotePlayer();
initializeBanners();
maybeScheduleRefreshProfileJob();
- ConversationListFragmentExtensionsKt.listenToEventBusWhileResumed(this, mainNavigationViewModel.getDetailLocation());
+ ConversationListFragmentExtensionsKt.listenToEventBusWhileResumed(this, mainNavigationViewModel .getDetailLocation());
String query = contactSearchMediator.getFilter();
if (query != null) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d823ed251d..fa71092484 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8042,6 +8042,8 @@
Set up
+
+ Cancel upload
Signal Backups
diff --git a/core-ui/src/main/java/org/signal/core/ui/compose/Rows.kt b/core-ui/src/main/java/org/signal/core/ui/compose/Rows.kt
index 31c87c526f..b29e24f460 100644
--- a/core-ui/src/main/java/org/signal/core/ui/compose/Rows.kt
+++ b/core-ui/src/main/java/org/signal/core/ui/compose/Rows.kt
@@ -26,6 +26,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
+import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
@@ -196,7 +197,13 @@ object Rows {
Switch(
checked = state.checked,
enabled = state.enabled,
- onCheckedChange = state.onCheckChanged
+ onCheckedChange = state.onCheckChanged,
+ colors = SwitchDefaults.colors(
+ checkedTrackColor = MaterialTheme.colorScheme.primary,
+ uncheckedBorderColor = MaterialTheme.colorScheme.outline,
+ uncheckedIconColor = MaterialTheme.colorScheme.outline,
+ uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant
+ )
)
}
}