mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Implement skeleton for backup sheets.
This commit is contained in:
committed by
Greyson Parrelli
parent
a82b9ee25f
commit
947ab7d48b
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.ui
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Icons
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
|
||||
/**
|
||||
* Notifies the user of an issue with their backup.
|
||||
*/
|
||||
class BackupAlertBottomSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
private const val ARG_ALERT = "alert"
|
||||
|
||||
fun create(backupAlert: BackupAlert): BackupAlertBottomSheet {
|
||||
return BackupAlertBottomSheet().apply {
|
||||
arguments = bundleOf(ARG_ALERT to backupAlert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val backupAlert: BackupAlert by lazy(LazyThreadSafetyMode.NONE) {
|
||||
BundleCompat.getParcelable(requireArguments(), ARG_ALERT, BackupAlert::class.java)!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
BackupAlertSheetContent(
|
||||
backupAlert = backupAlert,
|
||||
onPrimaryActionClick = this::performPrimaryAction,
|
||||
onSecondaryActionClick = this::performSecondaryAction
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
private fun performPrimaryAction() {
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC -> {
|
||||
// TODO [message-backups] -- Back up now
|
||||
}
|
||||
BackupAlert.PAYMENT_PROCESSING -> {
|
||||
// TODO [message-backups] -- Silence
|
||||
}
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF -> {
|
||||
// TODO [message-backups] -- Download media now
|
||||
}
|
||||
BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> {
|
||||
// TODO [message-backups] -- Download media now
|
||||
}
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
@Stable
|
||||
private fun performSecondaryAction() {
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC -> {
|
||||
// TODO [message-backups] - Dismiss and notify later
|
||||
}
|
||||
BackupAlert.PAYMENT_PROCESSING -> error("PAYMENT_PROCESSING state does not support a secondary action.")
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF -> {
|
||||
// TODO [message-backups] - Silence and remind on last day
|
||||
}
|
||||
BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> {
|
||||
// TODO [message-backups] - Silence forever
|
||||
}
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackupAlertSheetContent(
|
||||
backupAlert: BackupAlert,
|
||||
onPrimaryActionClick: () -> Unit,
|
||||
onSecondaryActionClick: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
) {
|
||||
BottomSheets.Handle()
|
||||
|
||||
Spacer(modifier = Modifier.size(26.dp))
|
||||
|
||||
val iconColors = rememberBackupsIconColors(backupAlert = backupAlert)
|
||||
Icons.BrushedForeground(
|
||||
painter = painterResource(id = R.drawable.symbol_backup_light), // TODO [message-backups] final asset
|
||||
contentDescription = null,
|
||||
foregroundBrush = iconColors.foreground,
|
||||
modifier = Modifier
|
||||
.size(88.dp)
|
||||
.background(color = iconColors.background, shape = CircleShape)
|
||||
.padding(20.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = rememberTitleResource(backupAlert = backupAlert)),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
|
||||
)
|
||||
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC -> GenericBody()
|
||||
BackupAlert.PAYMENT_PROCESSING -> PaymentProcessingBody()
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF -> MediaBackupsAreOffBody()
|
||||
BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> MediaWillBeDeletedTodayBody()
|
||||
}
|
||||
|
||||
val secondaryActionResource = rememberSecondaryActionResource(backupAlert = backupAlert)
|
||||
val padBottom = if (secondaryActionResource > 0) 16.dp else 56.dp
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onPrimaryActionClick,
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minWidth = 220.dp)
|
||||
.padding(top = 60.dp, bottom = padBottom)
|
||||
) {
|
||||
Text(text = stringResource(id = rememberPrimaryActionResource(backupAlert = backupAlert)))
|
||||
}
|
||||
|
||||
if (secondaryActionResource > 0) {
|
||||
TextButton(onClick = onSecondaryActionClick, modifier = Modifier.padding(bottom = 32.dp)) {
|
||||
Text(text = stringResource(id = secondaryActionResource))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GenericBody() {
|
||||
Text(text = "TODO")
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PaymentProcessingBody() {
|
||||
Text(text = "TODO")
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediaBackupsAreOffBody() {
|
||||
Text(text = "TODO")
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediaWillBeDeletedTodayBody() {
|
||||
Text(text = "TODO")
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberBackupsIconColors(backupAlert: BackupAlert): BackupsIconColors {
|
||||
return remember(backupAlert) {
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC, BackupAlert.PAYMENT_PROCESSING -> BackupsIconColors.Warning
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF, BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> BackupsIconColors.Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@StringRes
|
||||
private fun rememberTitleResource(backupAlert: BackupAlert): Int {
|
||||
return remember(backupAlert) {
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC -> R.string.default_error_msg // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.PAYMENT_PROCESSING -> R.string.default_error_msg // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF -> R.string.default_error_msg // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> R.string.default_error_msg // TODO [message-backups] -- Finalized copy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberPrimaryActionResource(backupAlert: BackupAlert): Int {
|
||||
return remember(backupAlert) {
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC -> android.R.string.ok // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.PAYMENT_PROCESSING -> android.R.string.ok // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF -> android.R.string.ok // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> android.R.string.ok // TODO [message-backups] -- Finalized copy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberSecondaryActionResource(backupAlert: BackupAlert): Int {
|
||||
return remember(backupAlert) {
|
||||
when (backupAlert) {
|
||||
BackupAlert.GENERIC -> android.R.string.cancel // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.PAYMENT_PROCESSING -> -1
|
||||
BackupAlert.MEDIA_BACKUPS_ARE_OFF -> android.R.string.cancel // TODO [message-backups] -- Finalized copy
|
||||
BackupAlert.MEDIA_WILL_BE_DELETED_TODAY -> android.R.string.cancel // TODO [message-backups] -- Finalized copy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun BackupAlertSheetContentPreviewGeneric() {
|
||||
Previews.BottomSheetPreview {
|
||||
BackupAlertSheetContent(
|
||||
backupAlert = BackupAlert.GENERIC,
|
||||
onPrimaryActionClick = {},
|
||||
onSecondaryActionClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun BackupAlertSheetContentPreviewPayment() {
|
||||
Previews.BottomSheetPreview {
|
||||
BackupAlertSheetContent(
|
||||
backupAlert = BackupAlert.PAYMENT_PROCESSING,
|
||||
onPrimaryActionClick = {},
|
||||
onSecondaryActionClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun BackupAlertSheetContentPreviewMedia() {
|
||||
Previews.BottomSheetPreview {
|
||||
BackupAlertSheetContent(
|
||||
backupAlert = BackupAlert.MEDIA_BACKUPS_ARE_OFF,
|
||||
onPrimaryActionClick = {},
|
||||
onSecondaryActionClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun BackupAlertSheetContentPreviewDelete() {
|
||||
Previews.BottomSheetPreview {
|
||||
BackupAlertSheetContent(
|
||||
backupAlert = BackupAlert.MEDIA_WILL_BE_DELETED_TODAY,
|
||||
onPrimaryActionClick = {},
|
||||
onSecondaryActionClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
enum class BackupAlert : Parcelable {
|
||||
GENERIC,
|
||||
PAYMENT_PROCESSING,
|
||||
MEDIA_BACKUPS_ARE_OFF,
|
||||
MEDIA_WILL_BE_DELETED_TODAY
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.ui
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
|
||||
sealed interface BackupsIconColors {
|
||||
@get:Composable
|
||||
val foreground: Brush
|
||||
|
||||
@get:Composable
|
||||
val background: Color
|
||||
|
||||
object Normal : BackupsIconColors {
|
||||
override val foreground: Brush
|
||||
@Composable get() = remember {
|
||||
Brush.linearGradient(
|
||||
colors = listOf(Color(0xFF316ED0), Color(0xFF558BE2)),
|
||||
start = Offset(x = 0f, y = Float.POSITIVE_INFINITY),
|
||||
end = Offset(x = Float.POSITIVE_INFINITY, y = 0f)
|
||||
)
|
||||
}
|
||||
|
||||
override val background: Color @Composable get() = MaterialTheme.colorScheme.primaryContainer
|
||||
}
|
||||
|
||||
object Warning : BackupsIconColors {
|
||||
override val foreground: Brush @Composable get() = SolidColor(Color(0xFFC86600))
|
||||
override val background: Color @Composable get() = Color(0xFFF9E4B6)
|
||||
}
|
||||
|
||||
object Error : BackupsIconColors {
|
||||
override val foreground: Brush @Composable get() = SolidColor(MaterialTheme.colorScheme.error)
|
||||
override val background: Color @Composable get() = Color(0xFFFFD9D9)
|
||||
}
|
||||
}
|
||||
@@ -17,30 +17,23 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
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.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Icons
|
||||
import org.signal.core.ui.Previews
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.BackupsIconColors
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -63,19 +56,13 @@ fun BackupStatus(
|
||||
.padding(14.dp)
|
||||
) {
|
||||
val foreground: Brush = data.iconColors.foreground
|
||||
Icon(
|
||||
Icons.BrushedForeground(
|
||||
painter = painterResource(id = data.iconRes),
|
||||
contentDescription = null,
|
||||
foregroundBrush = foreground,
|
||||
modifier = Modifier
|
||||
.background(color = data.iconColors.background, shape = CircleShape)
|
||||
.padding(8.dp)
|
||||
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
|
||||
.drawWithCache {
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
drawRect(foreground, blendMode = BlendMode.SrcAtop)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Column(
|
||||
@@ -92,7 +79,9 @@ fun BackupStatus(
|
||||
LinearProgressIndicator(
|
||||
progress = data.progress,
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,7 +141,7 @@ sealed interface BackupStatusData {
|
||||
@get:StringRes
|
||||
val titleRes: Int
|
||||
|
||||
val iconColors: IconColors
|
||||
val iconColors: BackupsIconColors
|
||||
|
||||
@get:StringRes
|
||||
val actionRes: Int get() = NONE
|
||||
@@ -168,7 +157,7 @@ sealed interface BackupStatusData {
|
||||
object CouldNotCompleteBackup : BackupStatusData {
|
||||
override val iconRes: Int = R.drawable.symbol_backup_light
|
||||
override val titleRes: Int = R.string.default_error_msg
|
||||
override val iconColors: IconColors = IconColors.Warning
|
||||
override val iconColors: BackupsIconColors = BackupsIconColors.Warning
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,7 +166,7 @@ sealed interface BackupStatusData {
|
||||
object NotEnoughFreeSpace : BackupStatusData {
|
||||
override val iconRes: Int = R.drawable.symbol_backup_light
|
||||
override val titleRes: Int = R.string.default_error_msg
|
||||
override val iconColors: IconColors = IconColors.Warning
|
||||
override val iconColors: BackupsIconColors = BackupsIconColors.Warning
|
||||
override val actionRes: Int = R.string.registration_activity__skip
|
||||
}
|
||||
|
||||
@@ -190,7 +179,7 @@ sealed interface BackupStatusData {
|
||||
val status: Status = Status.NONE
|
||||
) : BackupStatusData {
|
||||
override val iconRes: Int = R.drawable.symbol_backup_light
|
||||
override val iconColors: IconColors = IconColors.Normal
|
||||
override val iconColors: BackupsIconColors = BackupsIconColors.Normal
|
||||
|
||||
override val titleRes: Int = when (status) {
|
||||
Status.NONE -> R.string.default_error_msg
|
||||
@@ -215,31 +204,6 @@ sealed interface BackupStatusData {
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface IconColors {
|
||||
@get:Composable
|
||||
val foreground: Brush
|
||||
|
||||
@get:Composable
|
||||
val background: Color
|
||||
|
||||
object Normal : IconColors {
|
||||
override val foreground: Brush @Composable get() = remember {
|
||||
Brush.linearGradient(
|
||||
colors = listOf(Color(0xFF316ED0), Color(0xFF558BE2)),
|
||||
start = Offset(x = 0f, y = Float.POSITIVE_INFINITY),
|
||||
end = Offset(x = Float.POSITIVE_INFINITY, y = 0f)
|
||||
)
|
||||
}
|
||||
|
||||
override val background: Color @Composable get() = MaterialTheme.colorScheme.primaryContainer
|
||||
}
|
||||
|
||||
object Warning : IconColors {
|
||||
override val foreground: Brush @Composable get() = SolidColor(Color(0xFFC86600))
|
||||
override val background: Color @Composable get() = Color(0xFFF9E4B6)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the status of an in-progress media download session.
|
||||
*/
|
||||
|
||||
57
core-ui/src/main/java/org/signal/core/ui/Icons.kt
Normal file
57
core-ui/src/main/java/org/signal/core/ui/Icons.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.ui
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
object Icons {
|
||||
/**
|
||||
* Icon that takes a Brush instead of a Color for its foreground
|
||||
*/
|
||||
@Composable
|
||||
fun BrushedForeground(
|
||||
painter: Painter,
|
||||
contentDescription: String?,
|
||||
foregroundBrush: Brush,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Icon(
|
||||
painter = painter,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier
|
||||
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
|
||||
.drawWithCache {
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
drawRect(foregroundBrush, blendMode = BlendMode.SrcAtop)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BrushedForegroundPreview() {
|
||||
Previews.Preview {
|
||||
Icons.BrushedForeground(
|
||||
painter = painterResource(id = android.R.drawable.ic_menu_camera),
|
||||
contentDescription = null,
|
||||
foregroundBrush = Brush.linearGradient(listOf(Color.Red, Color.Blue))
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user