From c3277fd2b187d43e3639add7cd6af352330e8a10 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 31 Jul 2026 19:00:28 +0000 Subject: [PATCH] Apply chat color tint and send icon to the media-send edit next button. --- .../mediasend/v3/MediaSendV3Activity.kt | 7 +++ .../core/ui/compose/LocalChatColorProvider.kt | 27 +++++++++ .../org/signal/core/ui/compose/SignalIcons.kt | 1 + .../main/res/drawable/symbol_send_fill_24.xml | 9 +++ .../signal/mediasend/edit/AddAMessageRow.kt | 59 +++++++++++++++---- .../signal/mediasend/edit/MediaEditScreen.kt | 6 ++ .../src/main/res/values/strings.xml | 2 + 7 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 core/ui/src/main/java/org/signal/core/ui/compose/LocalChatColorProvider.kt create mode 100644 core/ui/src/main/res/drawable/symbol_send_fill_24.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt index b6251fc982..9ac154f431 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.fragment.compose.AndroidFragment @@ -27,6 +28,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.signal.core.ui.WindowBreakpoint +import org.signal.core.ui.compose.LocalChatColorProvider import org.signal.core.ui.compose.LocalDisplayNameProvider import org.signal.core.ui.getWindowBreakpoint import org.signal.mediasend.HudCommand @@ -145,6 +147,11 @@ class MediaSendV3Activity : getDisplayName(context) } } + }, + LocalChatColorProvider provides { id -> + rememberRecipientField(RecipientId.from(id)) { + Color(chatColors.asSingleColor()) + } } ) { MediaSendScreen( diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/LocalChatColorProvider.kt b/core/ui/src/main/java/org/signal/core/ui/compose/LocalChatColorProvider.kt new file mode 100644 index 0000000000..0553131463 --- /dev/null +++ b/core/ui/src/main/java/org/signal/core/ui/compose/LocalChatColorProvider.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.ui.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color + +/** + * Intended to be bound at the app level to allow for the production of an observable single-color representation of a + * recipient's chat color. Raw types are utilized here since RecipientId and ChatColors are both currently localized to + * the app module and are non-trivial to pick up and move. + * + * Gradient chat colors collapse to a single color, matching what the legacy send button did. + */ +val LocalChatColorProvider = compositionLocalOf<@Composable (Long) -> State> { + { remember(it) { mutableStateOf(UnboundChatColor) } } +} + +/** Stand-in used in previews and anywhere else the provider is left unbound. Matches the default (ultramarine) bubble. */ +private val UnboundChatColor = Color(0xFF315FF4) diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt b/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt index 0380a7de05..bdd8a889f4 100644 --- a/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt +++ b/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt @@ -82,6 +82,7 @@ enum class SignalIcons(private val icon: SignalIcon) : SignalIcon by icon { Redo(icon(R.drawable.symbol_redo_24)), Save(icon(R.drawable.symbol_save_android_24)), Search(icon(R.drawable.symbol_search_24)), + SendFill(icon(R.drawable.symbol_send_fill_24)), Settings(icon(R.drawable.symbol_settings_android_24)), Share(icon(R.drawable.symbol_share_android_24)), SignalBackupsDisplay(icon(R.drawable.symbol_signal_backups_display_48)), diff --git a/core/ui/src/main/res/drawable/symbol_send_fill_24.xml b/core/ui/src/main/res/drawable/symbol_send_fill_24.xml new file mode 100644 index 0000000000..2da721a51e --- /dev/null +++ b/core/ui/src/main/res/drawable/symbol_send_fill_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/AddAMessageRow.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/AddAMessageRow.kt index f17d5529e3..785b3e575c 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/AddAMessageRow.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/AddAMessageRow.kt @@ -12,8 +12,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -23,6 +21,7 @@ import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -33,9 +32,13 @@ import org.signal.core.ui.compose.DropdownMenus import org.signal.core.ui.compose.IconButtons import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.SignalIcons +import org.signal.core.ui.compose.theme.SignalTheme import org.signal.mediasend.R import org.signal.mediasend.test.TestTags +/** Mirrors the legacy send button's disabled tint, which has no equivalent in the core-ui color scheme. */ +private val DisabledNextButtonColor = Color(0xFF777777) + /** * Because we need to be able to support stuff like mentions, styled text, and custom emoji, we need to allow * the users of this feature to inject their own text-field. @@ -51,6 +54,11 @@ val LocalAddAMessageRowTextField = compositionLocalOf<@Composable (CharSequence, } } +/** + * @param recipientChatColor The chat color of the single recipient this media is headed to, or null when the destination + * is still to be chosen. Non-null makes the trailing button a chat-color-tinted send button rather than a themed + * "next" arrow. + */ @Composable fun AddAMessageRow( message: CharSequence?, @@ -60,7 +68,8 @@ fun AddAMessageRow( enabled: Boolean = true, canScheduleSend: Boolean = false, viewOnceAvailable: Boolean = false, - viewOnce: Boolean = false + viewOnce: Boolean = false, + recipientChatColor: Color? = null ) { Row( horizontalArrangement = Arrangement.Center, @@ -132,20 +141,19 @@ fun AddAMessageRow( onClick = onNextClick, onLongClick = if (canScheduleSend) scheduleSendMenuController::show else null, onLongClickLabel = stringResource(R.string.AddAMessageRow__schedule_send), + colors = IconButtons.iconButtonColors( + containerColor = recipientChatColor ?: MaterialTheme.colorScheme.onSecondaryContainer, + contentColor = if (recipientChatColor != null) SignalTheme.colors.colorOnCustom else MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = DisabledNextButtonColor, + disabledContentColor = MaterialTheme.colorScheme.secondaryContainer + ), modifier = Modifier .testTag(TestTags.ADD_A_MESSAGE_NEXT_BUTTON) .padding(start = 12.dp) - .background( - color = MaterialTheme.colorScheme.primaryContainer, - shape = CircleShape - ) ) { Icon( - painter = SignalIcons.ArrowEnd.painter, - contentDescription = stringResource(R.string.AddAMessageRow__next), - modifier = Modifier - .size(40.dp) - .padding(8.dp) + painter = if (recipientChatColor != null) SignalIcons.SendFill.painter else SignalIcons.ArrowEnd.painter, + contentDescription = stringResource(if (recipientChatColor != null) R.string.AddAMessageRow__send else R.string.AddAMessageRow__next) ) } @@ -195,3 +203,30 @@ private fun AddAMessageRowViewOncePreview() { ) } } + +@DayNightPreviews +@Composable +private fun AddAMessageRowKnownRecipientPreview() { + Previews.Preview { + AddAMessageRow( + message = null, + onEvent = {}, + onNextClick = {}, + recipientChatColor = Color(0xFF3B7845) + ) + } +} + +@DayNightPreviews +@Composable +private fun AddAMessageRowDisabledPreview() { + Previews.Preview { + AddAMessageRow( + message = null, + onEvent = {}, + onNextClick = {}, + enabled = false, + recipientChatColor = Color(0xFF3B7845) + ) + } +} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt index 682381f8ef..639ecbb69f 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt @@ -42,6 +42,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.launch import org.signal.core.ui.WindowBreakpoint import org.signal.core.ui.compose.AllDevicePreviews +import org.signal.core.ui.compose.LocalChatColorProvider import org.signal.core.ui.compose.LocalDisplayNameProvider import org.signal.core.ui.compose.Previews import org.signal.core.ui.rememberWindowBreakpoint @@ -231,6 +232,10 @@ internal fun MediaEditScreen( val isTextEditing = imageController?.textEditingElement != null + // Null whenever the destination is still to be chosen, which is what turns the trailing button back into a + // "next" arrow. + val recipientChatColor: Color? = state.recipientId?.let { LocalChatColorProvider.current(it.id).value } + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier @@ -320,6 +325,7 @@ internal fun MediaEditScreen( viewOnceAvailable = state.isViewOnceAvailable, viewOnce = state.isViewOnceEnabled, message = state.message, + recipientChatColor = recipientChatColor, onEvent = onEvent, onNextClick = { onEvent(MediaEditScreenEvent.NextClick) }, modifier = Modifier diff --git a/feature/media-send/src/main/res/values/strings.xml b/feature/media-send/src/main/res/values/strings.xml index 9b22359f90..22d91e11a3 100644 --- a/feature/media-send/src/main/res/values/strings.xml +++ b/feature/media-send/src/main/res/values/strings.xml @@ -24,6 +24,8 @@ Message Next + + Send Schedule send