mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Add TransferProgressIndicator composable.
Adds a composable version of `TransferProgressView`.
This commit is contained in:
committed by
Cody Henthorne
parent
3d1895500c
commit
48d26beb77
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.transfercontrols
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.clickableContainer
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
/**
|
||||
* A button that can be used to start, cancel, show progress, and show completion of a data transfer.
|
||||
*/
|
||||
@Composable
|
||||
fun TransferProgressIndicator(
|
||||
state: TransferProgressState,
|
||||
modifier: Modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
when (state) {
|
||||
is TransferProgressState.Ready -> StartTransferButton(state, modifier)
|
||||
is TransferProgressState.InProgress -> ProgressIndicator(state, modifier)
|
||||
is TransferProgressState.Complete -> CompleteIcon(state, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StartTransferButton(
|
||||
state: TransferProgressState.Ready,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clickableContainer(
|
||||
contentDescription = state.startButtonContentDesc,
|
||||
onClickLabel = state.startButtonOnClickLabel,
|
||||
onClick = state.onStartClick
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
painter = state.iconPainter,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.padding(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProgressIndicator(
|
||||
state: TransferProgressState.InProgress,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clickableContainer(
|
||||
contentDescription = null,
|
||||
onClickLabel = state.cancelButtonOnClickLabel,
|
||||
onClick = state.onCancelClick
|
||||
)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_stop_24),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.padding(6.dp)
|
||||
)
|
||||
|
||||
CircularProgressIndicator(
|
||||
progress = { state.progress },
|
||||
strokeWidth = 2.dp,
|
||||
strokeCap = StrokeCap.Round,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.clearAndSetSemantics {
|
||||
contentDescription = state.cancelButtonContentDesc
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CompleteIcon(
|
||||
state: TransferProgressState.Complete,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Icon(
|
||||
painter = state.iconPainter,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = state.iconContentDesc,
|
||||
modifier = modifier.padding(12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface TransferProgressState {
|
||||
data class Ready(
|
||||
val iconPainter: Painter,
|
||||
val startButtonContentDesc: String,
|
||||
val startButtonOnClickLabel: String,
|
||||
val onStartClick: () -> Unit
|
||||
) : TransferProgressState
|
||||
|
||||
data class InProgress(
|
||||
val progress: Float,
|
||||
val cancelButtonContentDesc: String,
|
||||
val cancelButtonOnClickLabel: String,
|
||||
val onCancelClick: () -> Unit = {}
|
||||
) : TransferProgressState
|
||||
|
||||
data class Complete(
|
||||
val iconPainter: Painter,
|
||||
val iconContentDesc: String
|
||||
) : TransferProgressState
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.Discouraged
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.withTranslation
|
||||
import org.signal.core.util.logging.Log
|
||||
@@ -24,6 +25,7 @@ import kotlin.math.roundToInt
|
||||
/**
|
||||
* This displays a circular progress around an icon. The icon is either an upload arrow, a download arrow, or a rectangular stop button.
|
||||
*/
|
||||
@Discouraged("Use TransferProgressIndicator instead.")
|
||||
class TransferProgressView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
|
||||
@@ -279,7 +279,7 @@ private fun AvailableStickersContentPreview() {
|
||||
title = "Bandit the Cat",
|
||||
author = "Agnes Lee",
|
||||
isBlessed = false,
|
||||
downloadStatus = DownloadStatus.InProgress(progressPercent = 22.0)
|
||||
downloadStatus = DownloadStatus.InProgress(progress = 0.37f)
|
||||
),
|
||||
StickerPreviewDataFactory.availablePack(
|
||||
title = "Day by Day",
|
||||
|
||||
@@ -52,7 +52,7 @@ data class AvailableStickerPack(
|
||||
) {
|
||||
sealed class DownloadStatus {
|
||||
data object NotDownloaded : DownloadStatus()
|
||||
data class InProgress(val progressPercent: Double) : DownloadStatus()
|
||||
data class InProgress(val progress: Float) : DownloadStatus()
|
||||
data object Downloaded : DownloadStatus()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,17 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.IconButtons
|
||||
import org.signal.core.ui.compose.SignalPreview
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.core.util.nullIfBlank
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressIndicator
|
||||
import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressState
|
||||
import org.thoughtcrime.securesms.compose.GlideImage
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
||||
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
|
||||
@@ -53,6 +54,7 @@ fun StickerPackSectionHeader(
|
||||
fun AvailableStickerPackRow(
|
||||
pack: AvailableStickerPack,
|
||||
onStartDownloadClick: () -> Unit = {},
|
||||
onCancelDownloadClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
@@ -69,15 +71,25 @@ fun AvailableStickerPackRow(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
// TODO show TransferProgressIndicator based on download state
|
||||
IconButtons.IconButton(
|
||||
size = 48.dp,
|
||||
onClick = onStartDownloadClick,
|
||||
content = {
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_arrow_circle_down_24),
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = stringResource(R.string.StickerManagement_accessibility_download_pack, pack.record.title)
|
||||
TransferProgressIndicator(
|
||||
state = when (pack.downloadStatus) {
|
||||
DownloadStatus.NotDownloaded -> TransferProgressState.Ready(
|
||||
iconPainter = painterResource(id = R.drawable.symbol_arrow_circle_down_24),
|
||||
startButtonContentDesc = stringResource(R.string.StickerManagement_accessibility_download),
|
||||
startButtonOnClickLabel = stringResource(R.string.StickerManagement_accessibility_download_pack, pack.record.title),
|
||||
onStartClick = onStartDownloadClick
|
||||
)
|
||||
|
||||
is DownloadStatus.InProgress -> TransferProgressState.InProgress(
|
||||
progress = pack.downloadStatus.progress,
|
||||
cancelButtonContentDesc = stringResource(R.string.StickerManagement_accessibility_cancel),
|
||||
cancelButtonOnClickLabel = stringResource(R.string.StickerManagement_accessibility_cancel_downloading_pack, pack.record.title),
|
||||
onCancelClick = onCancelDownloadClick
|
||||
)
|
||||
|
||||
DownloadStatus.Downloaded -> TransferProgressState.Complete(
|
||||
iconPainter = painterResource(id = R.drawable.symbol_check_24),
|
||||
iconContentDesc = stringResource(R.string.StickerManagement_accessibility_downloaded_checkmark, pack.record.title)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -215,7 +227,7 @@ private fun AvailableStickerPackRowPreviewDownloading() = SignalTheme {
|
||||
title = "Bandit the Cat",
|
||||
author = "Agnes Lee",
|
||||
isBlessed = false,
|
||||
downloadStatus = DownloadStatus.InProgress(progressPercent = 22.0)
|
||||
downloadStatus = DownloadStatus.InProgress(progress = 0.37f)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
9
app/src/main/res/drawable/symbol_stop_24.xml
Normal file
9
app/src/main/res/drawable/symbol_stop_24.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6.5,4.5C5.395,4.5 4.5,5.395 4.5,6.5V17.5C4.5,18.605 5.395,19.5 6.5,19.5H17.5C18.605,19.5 19.5,18.605 19.5,17.5V6.5C19.5,5.395 18.605,4.5 17.5,4.5H6.5Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -2800,8 +2800,16 @@
|
||||
<string name="StickerManagement_signal_artist_series_header">Signal artist series</string>
|
||||
<!-- Accessibility label for the button to download a sticker pack. The placeholder is the sticker pack name. -->
|
||||
<string name="StickerManagement_accessibility_download_pack">Download %s sticker pack</string>
|
||||
<!-- Accessibility label for the button to cancel a sticker pack download. The placeholder is the sticker pack name. -->
|
||||
<string name="StickerManagement_accessibility_cancel_downloading_pack">Cancel downloading %s sticker pack</string>
|
||||
<!-- Accessibility label for the icon that is displayed after a sticker pack has been downloaded. The placeholder is the sticker pack name. -->
|
||||
<string name="StickerManagement_accessibility_downloaded_checkmark">Downloaded checkmark</string>
|
||||
<!-- Accessibility label for the sticker pack drag and drop handle used to reorder the list. -->
|
||||
<string name="StickerManagement_accessibility_drag_handle">Drag and drop handle</string>
|
||||
<!-- Accessibility label for the button to download a sticker pack. -->
|
||||
<string name="StickerManagement_accessibility_download">Download</string>
|
||||
<!-- Accessibility label for the button to cancel downloading a sticker pack. -->
|
||||
<string name="StickerManagement_accessibility_cancel">Cancel</string>
|
||||
|
||||
<!-- StickerManagementAdapter -->
|
||||
<string name="StickerManagementAdapter_no_stickers_installed">No stickers installed</string>
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
|
||||
package org.signal.core.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Indication
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import org.signal.core.ui.R
|
||||
|
||||
@@ -21,3 +29,34 @@ fun Modifier.horizontalGutters(
|
||||
): Modifier {
|
||||
return padding(horizontal = gutterSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a component to be clickable within its bounds and show a default indication when pressed.
|
||||
*
|
||||
* This modifier is designed for use on container components, making it easier to create a clickable container with proper accessibility configuration.
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.clickableContainer(
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
indication: Indication = ripple(bounded = false),
|
||||
enabled: Boolean = true,
|
||||
contentDescription: String?,
|
||||
onClickLabel: String,
|
||||
role: Role? = null,
|
||||
onClick: () -> Unit
|
||||
): Modifier = clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = indication,
|
||||
enabled = enabled,
|
||||
onClickLabel = onClickLabel,
|
||||
role = role,
|
||||
onClick = onClick
|
||||
).then(
|
||||
if (contentDescription != null) {
|
||||
Modifier.semantics(mergeDescendants = true) {
|
||||
this.contentDescription = contentDescription
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user