From 861366091cf8879a535eb21b7b2f0eee8a083617 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Tue, 4 Aug 2026 17:03:46 -0300 Subject: [PATCH] Apply chat color to media select screen in media-send flow. --- .../screens/select/MediaSelectScreen.kt | 61 ++++++++++++++++--- .../screens/select/MediaSelectState.kt | 9 +++ .../screens/select/MediaSelectViewModel.kt | 6 +- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectScreen.kt index 7afb5d99f3..e98036e0ee 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectScreen.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode @@ -79,6 +80,7 @@ import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.DayNightPreviews import org.signal.core.ui.compose.DropdownMenus +import org.signal.core.ui.compose.LocalChatColorProvider import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Scaffolds import org.signal.core.ui.compose.SignalIcons @@ -91,6 +93,7 @@ import org.signal.core.ui.compose.list.rememberDragToSelectState import org.signal.core.ui.compose.list.rememberReorderBuffer import org.signal.core.ui.compose.list.rememberReorderableListState import org.signal.core.ui.compose.list.reorderableList +import org.signal.core.ui.compose.theme.SignalTheme import org.signal.glide.compose.GlideImage import org.signal.mediasend.R import org.signal.mediasend.screens.MediaSendMetrics @@ -117,6 +120,7 @@ internal fun MediaSelectScreen( val gridConfiguration = rememberGridConfiguration(state is MediaSelectState.Folders && !showPlaceholders) val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + val recipientChatColor: Color? = state.recipientId?.let { LocalChatColorProvider.current(it.id).value } val gridState = rememberLazyGridState() val dragToSelectState = rememberDragToSelectMediaState(state, onEvent, gridState) @@ -196,7 +200,12 @@ internal fun MediaSelectScreen( is MediaSelectState.Files -> { items(state.selectedMediaFolderItems, key = { it.uri }) { media -> - MediaTile(media = media, state.selectedMedia.indexOfFirst { it.uri == media.uri }, onEvent = onEvent) + MediaTile( + media = media, + selectionIndex = state.selectedMedia.indexOfFirst { it.uri == media.uri }, + recipientChatColor = recipientChatColor, + onEvent = onEvent + ) } } } @@ -238,7 +247,10 @@ internal fun MediaSelectScreen( .padding(end = 16.dp) ) - NextButton(state.selectedMedia.size) { + NextButton( + mediaSelectionCount = state.selectedMedia.size, + recipientChatColor = recipientChatColor + ) { onEvent(MediaSelectScreenEvents.NavigateToEdit) } } @@ -537,11 +549,16 @@ private fun MediaFolderTile( } } +/** + * @param recipientChatColor The chat color of the single recipient this media is headed to, or null when the + * destination is still to be chosen and the selection badge falls back to the theme. + */ @Composable private fun MediaTile( media: Media, selectionIndex: Int, - onEvent: (MediaSelectScreenEvents) -> Unit + onEvent: (MediaSelectScreenEvents) -> Unit, + recipientChatColor: Color? = null ) { val scale by animateFloatAsState( targetValue = if (selectionIndex >= 0) { @@ -600,30 +617,33 @@ private fun MediaTile( modifier = Modifier .border(width = 3.dp, color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(percent = 50)) .padding(1.dp) - .background(color = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(percent = 50)) + .background(color = recipientChatColor ?: MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(percent = 50)) .ensureWidthIsAtLeastHeight() .padding(horizontal = 5.5.dp, vertical = 2.dp) ) { - Text(text = "${selectionIndex + 1}", color = MaterialTheme.colorScheme.onPrimary) + Text( + text = "${selectionIndex + 1}", + color = if (recipientChatColor != null) SignalTheme.colors.colorOnCustom else MaterialTheme.colorScheme.onPrimary + ) } } } } @Composable -private fun NextButton(mediaSelectionCount: Int, onClick: () -> Unit) { +private fun NextButton(mediaSelectionCount: Int, recipientChatColor: Color? = null, onClick: () -> Unit) { Buttons.MediumTonal( onClick = onClick, contentPadding = PaddingValues(horizontal = 12.dp, vertical = 10.dp) ) { Box( modifier = Modifier - .background(color = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(percent = 50)) + .background(color = recipientChatColor ?: MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(percent = 50)) .ensureWidthIsAtLeastHeight() ) { Text( text = "$mediaSelectionCount", - color = MaterialTheme.colorScheme.onPrimary, + color = if (recipientChatColor != null) SignalTheme.colors.colorOnCustom else MaterialTheme.colorScheme.onPrimary, modifier = Modifier ) } @@ -827,6 +847,19 @@ private fun MediaTileSelectedPreview() { } } +@DayNightPreviews +@Composable +private fun MediaTileSelectedChatColorPreview() { + Previews.Preview { + MediaTile( + media = rememberPreviewMedia(1).first(), + selectionIndex = 0, + onEvent = {}, + recipientChatColor = Color(0xFF3B7845) + ) + } +} + @DayNightPreviews @Composable private fun NextButtonPreview() { @@ -838,6 +871,18 @@ private fun NextButtonPreview() { } } +@DayNightPreviews +@Composable +private fun NextButtonChatColorPreview() { + Previews.Preview { + NextButton( + mediaSelectionCount = 3, + recipientChatColor = Color(0xFF3B7845), + onClick = {} + ) + } +} + @Composable private fun rememberPreviewMediaFolders(count: Int): List { return remember(count) { diff --git a/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectState.kt b/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectState.kt index 622ecedcc3..13619e024e 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectState.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectState.kt @@ -7,12 +7,19 @@ package org.signal.mediasend.screens.select import org.signal.core.models.media.Media import org.signal.core.models.media.MediaFolder +import org.signal.mediasend.MediaRecipientId sealed interface MediaSelectState { /** The flow's current selection, as last reported by the parent. */ val selectedMedia: List + /** + * The single recipient this media is headed to, or null when the destination is still to be chosen. Fixed for the + * life of the flow, so it is read once at construction rather than mirrored from the parent. + */ + val recipientId: MediaRecipientId? + /** How much of the device's media we are allowed to read. */ val mediaPermissions: MediaPermissions @@ -25,6 +32,7 @@ sealed interface MediaSelectState { data class Folders( val mediaFolders: List, override val selectedMedia: List, + override val recipientId: MediaRecipientId? = null, override val mediaPermissions: MediaPermissions = MediaPermissions.FULL, override val isSelectionRejected: Boolean = false ) : MediaSelectState { @@ -36,6 +44,7 @@ sealed interface MediaSelectState { val selectedMediaFolder: MediaFolder, val selectedMediaFolderItems: List, override val selectedMedia: List, + override val recipientId: MediaRecipientId? = null, override val mediaPermissions: MediaPermissions = MediaPermissions.FULL, override val isSelectionRejected: Boolean = false ) : MediaSelectState { diff --git a/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectViewModel.kt b/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectViewModel.kt index 8110acb6fa..7b8d8b78d0 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectViewModel.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/screens/select/MediaSelectViewModel.kt @@ -54,12 +54,14 @@ internal class MediaSelectViewModel( MediaSelectState.Files( selectedMediaFolder = mediaFolder, selectedMediaFolderItems = emptyList(), - selectedMedia = emptyList() + selectedMedia = emptyList(), + recipientId = parentState.value.recipientId ) } else { MediaSelectState.Folders( mediaFolders = emptyList(), - selectedMedia = emptyList() + selectedMedia = emptyList(), + recipientId = parentState.value.recipientId ) } )