From 871978bf314e48c96509d44227f8872422787956 Mon Sep 17 00:00:00 2001 From: another-simple-pixel Date: Thu, 2 Jul 2026 15:02:00 -0700 Subject: [PATCH] SIMPLEX theme: sunrise gradient on chat bubbles, avatars and text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Paints the SIMPLEX chat over one screen-spanning "sunrise" gradient: - bubbles, avatars and links sample the gradient by on-screen position (a shared 20°-tilted axis anchored to the chat viewport); - small text (dates, delivery checks, names, quoted authors, event notices, call/voice/deleted secondary text) uses a per-wallpaper semi-transparent warm tint, so it stays evenly legible over the gradient; - decryption/integrity-error and deleted/marked-deleted bubbles use the received gradient instead of a flat grey; - wallpaper stops and secondary colours resolve from the composition, so per-chat wallpaper overrides and the wallpaper-picker preview render correctly. Side effects for all themes (details in the PR): event/feature text follows the active theme instead of a start-up-frozen colour, and the via-proxy meta icon matches the meta row on media bubbles. --- .../chat/simplex/common/model/ChatModel.kt | 2 +- .../simplex/common/ui/theme/SimplexBrushes.kt | 240 ++++++++++++++++++ .../simplex/common/views/chat/ChatView.kt | 34 ++- .../common/views/chat/item/CICallItemView.kt | 9 +- .../views/chat/item/CIChatFeatureView.kt | 4 +- .../common/views/chat/item/CIEventView.kt | 2 +- .../chat/item/CIFeaturePreferenceView.kt | 4 +- .../common/views/chat/item/CIFileView.kt | 21 +- .../views/chat/item/CIGroupInvitationView.kt | 42 ++- .../chat/item/CIMemberCreatedContactView.kt | 4 +- .../common/views/chat/item/CIMetaView.kt | 40 +-- .../views/chat/item/CIRcvDecryptionError.kt | 24 +- .../common/views/chat/item/CIVoiceView.kt | 6 +- .../common/views/chat/item/ChatItemView.kt | 11 +- .../common/views/chat/item/DeletedItemView.kt | 14 +- .../common/views/chat/item/FramedItemView.kt | 27 +- .../views/chat/item/IntegrityErrorItemView.kt | 12 +- .../views/chat/item/MarkedDeletedItemView.kt | 14 +- .../common/views/helpers/ChatWallpaper.kt | 87 ++----- 19 files changed, 419 insertions(+), 178 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/SimplexBrushes.kt diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 44361baa73..6bf510b05d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -4850,7 +4850,7 @@ sealed class Format { val isSimplexLink = this is SimplexLink companion object { - val linkStyle @Composable get() = SpanStyle(color = MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline) + val linkStyle @Composable get() = SpanStyle(color = LocalSimplexLinkColor.current ?: MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/SimplexBrushes.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/SimplexBrushes.kt new file mode 100644 index 0000000000..381ab42ed8 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/SimplexBrushes.kt @@ -0,0 +1,240 @@ +package chat.simplex.common.ui.theme + +import androidx.compose.foundation.background +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +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.graphicsLayer +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.unit.IntSize +import chat.simplex.common.views.helpers.PresetWallpaper +import chat.simplex.common.views.helpers.SimplexStops +import chat.simplex.common.views.helpers.WallpaperType +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +// SIMPLEX 20°-tilted "sunrise" axis spans the whole chat surface (top app bar to compose bar). +// Bubble backgrounds and secondary text sample stops along this same axis so colour rises +// from bottom-left dark to top-right warm in one coordinated motion across the screen. + +private const val SIMPLEX_TILT_DEG = 20f + +// Per-wallpaper gradient stops live in PresetWallpaper._simplexStops (ChatWallpaper.kt). +// Only LINK_STOPS is global — aligned with primary accent, not wallpaper-dependent. +private val LINK_STOPS = listOf( + 0.55f to oklch(0.7993f, 0.1442f, 220.36f), // hue aligned with primary (cyan) — sRGB #00D3F9 + 0.80f to oklch(0.8600f, 0.1000f, 220.36f), // lighter, same hue +) + +val LocalSimplexLinkColor = compositionLocalOf { null } + +class ChatViewportInfo( + val sizePx: State, + val originInWindow: State, +) + +val LocalChatViewportInfo = compositionLocalOf { null } + +@Composable +fun rememberChatViewportInfo(): Pair { + val sizePx = remember { mutableStateOf(IntSize.Zero) } + val originInWindow = remember { mutableStateOf(Offset.Zero) } + val info = remember { ChatViewportInfo(sizePx, originInWindow) } + val mod = Modifier + .onSizeChanged { sizePx.value = it } + .onGloballyPositioned { originInWindow.value = it.positionInWindow() } + return info to mod +} + +private fun axisEndpoints(winW: Float, winH: Float): Pair { + val theta = SIMPLEX_TILT_DEG * PI.toFloat() / 180f + val dx = sin(theta); val dy = -cos(theta) + val cx = winW / 2f; val cy = winH / 2f + val projs = floatArrayOf( + (-cx) * dx + (-cy) * dy, + (winW - cx) * dx + (-cy) * dy, + (-cx) * dx + (winH - cy) * dy, + (winW - cx) * dx + (winH - cy) * dy, + ) + var minP = projs[0]; var maxP = projs[0] + for (p in projs) { if (p < minP) minP = p; if (p > maxP) maxP = p } + val start = Offset(cx + dx * minP, cy + dy * minP) + val end = Offset(cx + dx * maxP, cy + dy * maxP) + return start to end +} + +private fun sampleStops(stops: List>, t: Float): Color { + if (t <= stops.first().first) return stops.first().second + if (t >= stops.last().first) return stops.last().second + for (i in 0 until stops.size - 1) { + val a = stops[i]; val b = stops[i + 1] + if (t in a.first..b.first) { + val f = (t - a.first) / (b.first - a.first) + return Color( + red = a.second.red + f * (b.second.red - a.second.red), + green = a.second.green + f * (b.second.green - a.second.green), + blue = a.second.blue + f * (b.second.blue - a.second.blue), + ) + } + } + return stops.last().second +} + +enum class SimplexBubbleSlot { Sent, SentQuote, Received, ReceivedQuote } + +/** SimplexStops for a wallpaper, or null when it has none. */ +private fun stopsForWallpaper(wallpaper: AppWallpaper): SimplexStops? { + val type = wallpaper.type as? WallpaperType.Preset ?: return null + return PresetWallpaper.from(type.filename)?.simplexStops +} + +// The active theme's base is always the global one — ThemeManager.currentColors never overrides +// the base per chat; only wallpaper and colours vary per chat/preview. So the SIMPLEX check stays +// global, while the stops/tints read wallpaper + colours from composition (per-chat/preview aware). +private fun isSimplexActive(): Boolean = CurrentColors.value.base == DefaultTheme.SIMPLEX +@Composable private fun activeSimplexStops(): SimplexStops? = stopsForWallpaper(MaterialTheme.wallpaper) + +@Composable private fun stopsFor(slot: SimplexBubbleSlot): Array>? { + val stops = activeSimplexStops() ?: return null + return when (slot) { + SimplexBubbleSlot.Sent -> stops.sent + SimplexBubbleSlot.SentQuote -> stops.sentQuote + SimplexBubbleSlot.Received -> stops.received + SimplexBubbleSlot.ReceivedQuote -> stops.receivedQuote + } +} + +// Paint a bubble's background with the tilted gradient anchored to the chat viewport. +// Brush start/end live in viewport coordinates; we translate them into bubble-local +// coordinates so the gradient continues across bubbles as a single coherent axis. +fun Modifier.simplexBubbleBackground(slot: SimplexBubbleSlot): Modifier = composed { + val viewport = LocalChatViewportInfo.current ?: return@composed this + val bubblePos = remember { mutableStateOf(Offset.Zero) } + val stops = stopsFor(slot) ?: return@composed this + this + .onGloballyPositioned { bubblePos.value = it.positionInWindow() } + .drawBehind { + val sz = viewport.sizePx.value + if (sz.width == 0 || sz.height == 0) return@drawBehind + val (axisStartLocal, axisEndLocal) = axisEndpoints(sz.width.toFloat(), sz.height.toFloat()) + val viewportOrigin = viewport.originInWindow.value + val offset = viewportOrigin - bubblePos.value + val brush = Brush.linearGradient( + colorStops = stops, + start = axisStartLocal + offset, + end = axisEndLocal + offset, + ) + drawRect(brush) + } +} + +// Single entry point for chat-bubble backgrounds: routes all four slots to SIMPLEX +// gradients when SIMPLEX is active and viewport info is available, else flat AppColors. +fun Modifier.chatBubbleBackground( + sent: Boolean, + isQuote: Boolean, + transparent: Boolean = false, +): Modifier = composed { + if (transparent) return@composed this.background(Color.Transparent) + val simplexActive = isSimplexActive() + val viewportAvailable = LocalChatViewportInfo.current != null + val stops = activeSimplexStops() + if (simplexActive && viewportAvailable && stops != null) { + val slot = when { + sent && isQuote -> SimplexBubbleSlot.SentQuote + sent -> SimplexBubbleSlot.Sent + isQuote -> SimplexBubbleSlot.ReceivedQuote + else -> SimplexBubbleSlot.Received + } + return@composed this.simplexBubbleBackground(slot) + } + this.background( + when { + sent && isQuote -> MaterialTheme.appColors.sentQuote + sent -> MaterialTheme.appColors.sentMessage + isQuote -> MaterialTheme.appColors.receivedQuote + else -> MaterialTheme.appColors.receivedMessage + } + ) +} + +// Tint a single-colour icon (e.g. default ProfileImage placeholder) with the +// SIMPLEX axis brush. Drawn as an overlay using BlendMode.SrcAtop so the brush +// is masked by the icon's existing alpha — the icon's shape is preserved and +// only its colour is replaced by the gradient. +fun Modifier.simplexAvatarBrushOverlay(slot: SimplexBubbleSlot): Modifier = composed { + if (!isSimplexActive()) return@composed this + val viewport = LocalChatViewportInfo.current ?: return@composed this + val avatarPos = remember { mutableStateOf(Offset.Zero) } + val stops = stopsFor(slot) ?: return@composed this + this + .onGloballyPositioned { avatarPos.value = it.positionInWindow() } + .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .drawWithContent { + drawContent() + val sz = viewport.sizePx.value + if (sz.width == 0 || sz.height == 0) return@drawWithContent + val (axisStartLocal, axisEndLocal) = axisEndpoints(sz.width.toFloat(), sz.height.toFloat()) + val viewportOrigin = viewport.originInWindow.value + val offset = viewportOrigin - avatarPos.value + val brush = Brush.linearGradient( + colorStops = stops, + start = axisStartLocal + offset, + end = axisEndLocal + offset, + ) + drawRect(brush, blendMode = BlendMode.SrcAtop) + } +} + +// Static tinted-transparency for small text on the SIMPLEX gradient. A semi-transparent +// warm colour blends with the gradient at every height, so text stays uniformly legible +// without sampling the axis by screen position (bubbles and avatars still sample it). +// Composition-scoped: honours the per-chat override / wallpaper preview, and its fallback is +// the composition secondary, so non-SIMPLEX chats keep their own (override-aware) colour. +@Composable +fun simplexSecondaryTint(): Color { + if (!isSimplexActive()) return MaterialTheme.colors.secondary + return activeSimplexStops()?.secondaryTint ?: MaterialTheme.colors.secondary +} + +@Composable +fun simplexAuthorTint(): Color { + if (!isSimplexActive()) return MaterialTheme.colors.onBackground + return activeSimplexStops()?.authorTint ?: MaterialTheme.colors.onBackground +} + +// Link colour still samples the axis — accent cyan aligned across the whole screen. +@Composable +fun simplexLinkColor(): Pair = simplexAxisSampledColor(LINK_STOPS, MaterialTheme.colors.primary) + +@Composable +private fun simplexAxisSampledColor(stops: List>?, fallback: Color): Pair { + val isSimplex = isSimplexActive() + val viewport = LocalChatViewportInfo.current + if (!isSimplex || viewport == null || stops == null) return fallback to Modifier + val color = remember(stops) { mutableStateOf(stops.first().second) } + val mod = Modifier.onGloballyPositioned { coords -> + val sz = viewport.sizePx.value + if (sz.width == 0 || sz.height == 0) return@onGloballyPositioned + val pos = coords.positionInWindow() - viewport.originInWindow.value + val cx = pos.x + coords.size.width / 2f + val cy = pos.y + coords.size.height / 2f + val (axisStart, axisEnd) = axisEndpoints(sz.width.toFloat(), sz.height.toFloat()) + val span = (axisEnd - axisStart) + val spanLen2 = span.x * span.x + span.y * span.y + val t = ((cx - axisStart.x) * span.x + (cy - axisStart.y) * span.y) / spanLen2 + color.value = sampleStops(stops, t.coerceIn(0f, 1f)) + } + return color.value to mod +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index fc9d067f0d..13543be1f0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -979,7 +979,9 @@ fun ChatLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { val composeViewHeight = remember { mutableStateOf(0.dp) } - Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.secondaryContextFilter == null)) { + val (chatViewport, chatViewportModifier) = rememberChatViewportInfo() + Box(Modifier.fillMaxSize().then(chatViewportModifier).chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.secondaryContextFilter == null)) { + CompositionLocalProvider(LocalChatViewportInfo provides chatViewport) { val remoteHostId = remember { remoteHostId }.value val chat = remember { chat }.value val chatInfo = chat?.chatInfo @@ -1164,6 +1166,7 @@ fun ChatLayout( } } } + } } } } @@ -2023,6 +2026,7 @@ fun BoxScope.ChatItemsList( null to 1 } // the name and the badge are one element, so SpaceBetween separates them from the role, not from each other + val memberNameColor = simplexSecondaryTint() NameWithBadge( memberNames(member, prevMember, memCount), if (prevMember == null && memCount == 1) member.nameBadge else null, @@ -2030,7 +2034,7 @@ fun BoxScope.ChatItemsList( .padding(start = (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + DEFAULT_PADDING_HALF) .weight(1f, false), fontSize = 13.5.sp, - color = MaterialTheme.colors.secondary, + color = memberNameColor, overflow = TextOverflow.Ellipsis, maxLines = 1 ) @@ -2038,13 +2042,14 @@ fun BoxScope.ChatItemsList( val chatItemTail = remember { appPreferences.chatItemTail.state } val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true) val tailRendered = style is ShapeStyle.Bubble && style.tailVisible + val roleColor = simplexSecondaryTint() Text( member.memberRole.text(isChannel = chatInfo.isChannel), Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF + if (tailRendered) msgTailWidthDp else 0.dp), fontSize = 13.5.sp, fontWeight = FontWeight.Medium, - color = MaterialTheme.colors.secondary, + color = roleColor, maxLines = 1 ) } @@ -2059,7 +2064,9 @@ fun BoxScope.ChatItemsList( } Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }) { val member = cItem.chatDir.groupMember - Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { + val memberAvatarOverlay = if (CurrentColors.value.base == DefaultTheme.SIMPLEX && member.image == null) + Modifier.simplexAvatarBrushOverlay(SimplexBubbleSlot.ReceivedQuote) else Modifier + Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }.then(memberAvatarOverlay)) { MemberImage(member) } Box(modifier = Modifier.padding(top = 2.dp, start = 4.dp).chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)) { @@ -2105,6 +2112,7 @@ fun BoxScope.ChatItemsList( ) { @Composable fun ChannelNameAndRole() { + val channelColor = simplexSecondaryTint() Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) { Text( chatInfo.groupInfo.chatViewName, @@ -2112,7 +2120,7 @@ fun BoxScope.ChatItemsList( .padding(start = (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + DEFAULT_PADDING_HALF) .weight(1f, false), fontSize = 13.5.sp, - color = MaterialTheme.colors.secondary, + color = channelColor, overflow = TextOverflow.Ellipsis, maxLines = 1 ) @@ -2124,7 +2132,7 @@ fun BoxScope.ChatItemsList( Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF + if (tailRendered) msgTailWidthDp else 0.dp), fontSize = 13.5.sp, fontWeight = FontWeight.Medium, - color = MaterialTheme.colors.secondary, + color = channelColor, maxLines = 1 ) } @@ -2278,7 +2286,7 @@ fun BoxScope.ChatItemsList( Box( Modifier .clipChatItem() - .background(MaterialTheme.appColors.receivedMessage) + .chatBubbleBackground(sent = false, isQuote = false) ) { val bannerModifier = if (appPlatform.isDesktop) Modifier.width(400.dp) else Modifier.fillMaxWidth() Column( @@ -2289,9 +2297,11 @@ fun BoxScope.ChatItemsList( // ChatInfoImage has its own padding somewhere, // also not doing verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF) because of it .padding(top = DEFAULT_PADDING_HALF) - .background(MaterialTheme.appColors.receivedMessage) + .let { if (CurrentColors.value.base == DefaultTheme.SIMPLEX) it else it.background(MaterialTheme.appColors.receivedMessage) } ) { - ChatInfoImage(chatInfo, size = alertProfileImageSize, iconColor = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) + Box(if (CurrentColors.value.base == DefaultTheme.SIMPLEX && chatInfo.image == null) Modifier.simplexAvatarBrushOverlay(SimplexBubbleSlot.ReceivedQuote) else Modifier) { + ChatInfoImage(chatInfo, size = alertProfileImageSize, iconColor = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) + } val bannerBadge = chatInfo.nameBadge val uriHandler = LocalUriHandler.current Text( @@ -2345,11 +2355,12 @@ fun BoxScope.ChatItemsList( val contextStr = chatContext() if (contextStr != null) { + val subtitleColor = simplexSecondaryTint() Text( contextStr, style = MaterialTheme.typography.body2, textAlign = TextAlign.Center, - color = MaterialTheme.colors.secondary, + color = subtitleColor, modifier = Modifier.padding(top = DEFAULT_PADDING) ) } @@ -3036,13 +3047,14 @@ private fun ButtonRow(horizontalArrangement: Arrangement.Horizontal, content: @C @Composable private fun DateSeparator(date: Instant) { + val secColor = simplexSecondaryTint() Text( text = getTimestampDateText(date), Modifier.padding(vertical = DEFAULT_PADDING_HALF + 4.dp, horizontal = DEFAULT_PADDING_HALF).fillMaxWidth(), fontSize = 14.sp, fontWeight = FontWeight.Medium, textAlign = TextAlign.Center, - color = MaterialTheme.colors.secondary + color = secColor ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt index 744bcf7b66..053e5402a1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt @@ -41,9 +41,12 @@ fun CICallItemView( CICallStatus.Accepted -> ConnectingCallIcon() CICallStatus.Negotiated -> ConnectingCallIcon() CICallStatus.Progress -> Icon(painterResource(MR.images.ic_phone_in_talk_filled), stringResource(MR.strings.icon_descr_call_progress), tint = SimplexGreen) - CICallStatus.Ended -> Row { - Icon(painterResource(MR.images.ic_call_end), stringResource(MR.strings.icon_descr_call_ended), tint = MaterialTheme.colors.secondary, modifier = Modifier.padding(end = 4.dp)) - Text(durationText(duration), color = MaterialTheme.colors.secondary) + CICallStatus.Ended -> { + val callColor = simplexSecondaryTint() + Row { + Icon(painterResource(MR.images.ic_call_end), stringResource(MR.strings.icon_descr_call_ended), tint = callColor, modifier = Modifier.padding(end = 4.dp)) + Text(durationText(duration), color = callColor) + } } CICallStatus.Error -> {} } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt index c743d78d1c..34e4ae8f7b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull import chat.simplex.common.platform.onRightClick +import chat.simplex.common.ui.theme.simplexSecondaryTint @Composable fun CIChatFeatureView( @@ -106,7 +107,7 @@ private fun FeatureIconView(f: FeatureInfo) { if (f.param != null) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { icon() - Text(chatEventText(f.param, ""), maxLines = 1) + Text(chatEventText(f.param, ""), color = simplexSecondaryTint(), maxLines = 1) } } else { icon() @@ -129,6 +130,7 @@ private fun FullFeatureView( Text( chatEventText(chatItem), Modifier, + color = simplexSecondaryTint(), // this is important. Otherwise, aligning will be bad because annotated string has a Span with size 12.sp fontSize = 12.sp ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt index 529a0e458b..5a32113f6e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt @@ -12,7 +12,7 @@ import chat.simplex.common.ui.theme.* @Composable fun CIEventView(text: AnnotatedString) { - Text(text, Modifier.padding(horizontal = 6.dp, vertical = 6.dp), style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp), maxLines = 4) + Text(text, Modifier.padding(horizontal = 6.dp, vertical = 6.dp), color = simplexSecondaryTint(), style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp), maxLines = 4) } @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt index 1a23dfc49a..8759d9a696 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.simplexSecondaryTint import chat.simplex.res.MR @Composable @@ -42,12 +43,13 @@ fun CIFeaturePreferenceView( fun accept(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Accept", start = offset, end = offset).isNotEmpty() ClickableText( annotatedText, + style = TextStyle(color = simplexSecondaryTint()), onClick = { if (accept(it)) { acceptFeature(contact, feature, param) } }, shouldConsumeEvent = ::accept ) } else { Text(chatItem.content.text + " " + chatItem.timestampText, - fontSize = 12.sp, fontWeight = FontWeight.Light, color = MaterialTheme.colors.secondary) + fontSize = 12.sp, fontWeight = FontWeight.Light, color = simplexSecondaryTint()) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index b2b89ca041..737a5158cf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -53,12 +53,18 @@ fun CIFileView( Box( contentAlignment = Alignment.Center ) { - Icon( - painterResource(MR.images.ic_draft_filled), - stringResource(MR.strings.icon_descr_file), - Modifier.fillMaxSize(), - tint = color - ) + val isDefaultTint = color == (if (isInDarkTheme()) FileDark else FileLight) + val isSimplex = CurrentColors.value.base == DefaultTheme.SIMPLEX + val outerOverlay = if (isSimplex && isDefaultTint) + Modifier.simplexAvatarBrushOverlay(SimplexBubbleSlot.ReceivedQuote) else Modifier + Box(outerOverlay.fillMaxSize()) { + Icon( + painterResource(MR.images.ic_draft_filled), + stringResource(MR.strings.icon_descr_file), + Modifier.fillMaxSize(), + tint = color + ) + } if (innerIcon != null) { Icon( innerIcon, @@ -220,12 +226,13 @@ fun CIFileView( file.fileName, maxLines = 1 ) + val fileSizeColor = simplexSecondaryTint() Text( buildAnnotatedString { append(formatBytes(file.fileSize)) append(metaReserve) }, - color = secondaryColor, + color = fileSizeColor, fontSize = 14.sp, maxLines = 1 ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt index 9b8393f66a..6f1f569717 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt @@ -4,6 +4,7 @@ import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.draw.clip import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -51,11 +52,16 @@ fun CIGroupInvitationView( val iconColor = if (action && !inProgress.value) if (chatIncognito) Indigo else MaterialTheme.colors.primary else if (isInDarkTheme()) FileDark else FileLight + val isSimplex = CurrentColors.value.base == DefaultTheme.SIMPLEX + val avatarOverlayMod = if (isSimplex && groupInvitation.groupProfile.image == null) + Modifier.simplexAvatarBrushOverlay(SimplexBubbleSlot.ReceivedQuote) else Modifier Row( Modifier.defaultMinSize(minWidth = 220.dp) ) { - ProfileImage(size = 54.dp, image = groupInvitation.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled, color = iconColor) + Box(avatarOverlayMod) { + ProfileImage(size = 54.dp, image = groupInvitation.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled, color = iconColor) + } Spacer(Modifier.width(8.dp)) Column( Modifier.defaultMinSize(minHeight = 54.dp), @@ -84,15 +90,12 @@ fun CIGroupInvitationView( val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage - Surface( - modifier = if (action && !inProgress.value) Modifier.clickable(onClick = { - inProgress.value = true - joinGroup(groupInvitation.groupId) { inProgress.value = false } - }) else Modifier, - shape = RoundedCornerShape(18.dp), - color = if (sent) sentColor else receivedColor, - contentColor = LocalContentColor.current - ) { + val isSimplex = CurrentColors.value.base == DefaultTheme.SIMPLEX + val containerClickable = if (action && !inProgress.value) Modifier.clickable(onClick = { + inProgress.value = true + joinGroup(groupInvitation.groupId) { inProgress.value = false } + }) else Modifier + @Composable fun BubbleContent() { Box( Modifier .width(IntrinsicSize.Min) @@ -148,6 +151,25 @@ fun CIGroupInvitationView( CIMetaView(ci, timedMessagesTTL, showStatus = false, showEdited = false, showViaProxy = false, showTimestamp = showTimestamp) } } + + if (isSimplex) { + Box( + containerClickable + .clip(RoundedCornerShape(18.dp)) + .chatBubbleBackground(sent = sent, isQuote = false) + ) { + BubbleContent() + } + } else { + Surface( + modifier = containerClickable, + shape = RoundedCornerShape(18.dp), + color = if (sent) sentColor else receivedColor, + contentColor = LocalContentColor.current + ) { + BubbleContent() + } + } } @Preview/*( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMemberCreatedContactView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMemberCreatedContactView.kt index 2ade49b3fc..ee5b704c0d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMemberCreatedContactView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMemberCreatedContactView.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.simplexSecondaryTint import chat.simplex.res.MR @Composable @@ -51,6 +52,7 @@ fun CIMemberCreatedContactView( fun open(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Open", start = offset, end = offset).isNotEmpty() ClickableText( annotatedText, + style = TextStyle(color = simplexSecondaryTint()), onClick = { if (open(it)) { openDirectChat(chatItem.chatDir.groupMember.memberContactId) @@ -64,7 +66,7 @@ fun CIMemberCreatedContactView( append(" ") withStyle(chatEventStyle) { append(chatItem.timestampText) } } - Text(annotatedText) + Text(annotatedText, color = simplexSecondaryTint()) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt index 4ec2a885e7..5a1843f234 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt @@ -13,36 +13,44 @@ import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.ui.theme.DefaultTheme import chat.simplex.common.ui.theme.isInDarkTheme +import chat.simplex.common.ui.theme.simplexSecondaryTint import chat.simplex.res.MR import kotlinx.datetime.Clock +private fun palette(metaColor: Color, dark: Boolean): Color = + if (dark) metaColor.copy( + red = metaColor.red * 0.67F, + green = metaColor.green * 0.67F, + blue = metaColor.red * 0.67F, + ) else metaColor.copy( + red = minOf(metaColor.red * 1.33F, 1F), + green = minOf(metaColor.green * 1.33F, 1F), + blue = minOf(metaColor.red * 1.33F, 1F), + ) + @Composable fun CIMetaView( chatItem: ChatItem, timedMessagesTTL: Int?, - metaColor: Color = MaterialTheme.colors.secondary, - paleMetaColor: Color = if (isInDarkTheme()) { - metaColor.copy( - red = metaColor.red * 0.67F, - green = metaColor.green * 0.67F, - blue = metaColor.red * 0.67F) - } else { - metaColor.copy( - red = minOf(metaColor.red * 1.33F, 1F), - green = minOf(metaColor.green * 1.33F, 1F), - blue = minOf(metaColor.red * 1.33F, 1F)) - }, + metaColor: Color? = null, + paleMetaColor: Color? = null, showStatus: Boolean = true, showEdited: Boolean = true, showTimestamp: Boolean, showViaProxy: Boolean, ) { + val effectiveMeta = metaColor ?: simplexSecondaryTint() + // Partial delivery keeps the same warm tint but at lower alpha, so it reads a touch + // dimmer than full delivery while staying legible on the gradient. + val effectivePale = paleMetaColor ?: if (CurrentColors.value.base == DefaultTheme.SIMPLEX) effectiveMeta.copy(alpha = 0.4f) else palette(effectiveMeta, isInDarkTheme()) Row(Modifier.padding(start = 3.dp), verticalAlignment = Alignment.CenterVertically) { if (chatItem.isDeletedContent) { Text( chatItem.timestampText, - color = metaColor, + color = effectiveMeta, fontSize = 12.sp, modifier = Modifier.padding(start = 3.dp) ) @@ -51,8 +59,8 @@ fun CIMetaView( chatItem.meta, timedMessagesTTL, encrypted = chatItem.encryptedFile, - metaColor, - paleMetaColor, + effectiveMeta, + effectivePale, showStatus = showStatus, showEdited = showEdited, showViaProxy = showViaProxy, @@ -87,7 +95,7 @@ private fun CIMetaText( } if (showViaProxy && meta.sentViaProxy == true) { Spacer(Modifier.width(4.dp)) - Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = color) } if (showStatus) { Spacer(Modifier.width(4.dp)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt index d58fd7553f..e97d4965f2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt @@ -7,14 +7,14 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.ui.theme.CurrentColors -import chat.simplex.common.ui.theme.appColors +import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.AlertManager import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @@ -138,12 +138,10 @@ fun DecryptionErrorItemFixButton( onClick: () -> Unit, syncSupported: Boolean ) { - val receivedColor = MaterialTheme.appColors.receivedMessage - Surface( - Modifier.clickable(onClick = onClick), - shape = RoundedCornerShape(18.dp), - color = receivedColor, - contentColor = LocalContentColor.current + Box( + Modifier.clickable(onClick = onClick) + .clip(RoundedCornerShape(18.dp)) + .chatBubbleBackground(sent = false, isQuote = false) ) { Box( Modifier.padding(vertical = 6.dp, horizontal = 12.dp), @@ -186,12 +184,10 @@ fun DecryptionErrorItem( ci: ChatItem, onClick: () -> Unit ) { - val receivedColor = MaterialTheme.appColors.receivedMessage - Surface( - Modifier.clickable(onClick = onClick), - shape = RoundedCornerShape(18.dp), - color = receivedColor, - contentColor = LocalContentColor.current + Box( + Modifier.clickable(onClick = onClick) + .clip(RoundedCornerShape(18.dp)) + .chatBubbleBackground(sent = false, isQuote = false) ) { Box( Modifier.padding(vertical = 6.dp, horizontal = 12.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt index 136300e4ed..38b92d0f20 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt @@ -227,12 +227,13 @@ private fun VoiceLayout( @Composable private fun DurationText(text: State, padding: PaddingValues, smallView: Boolean = false) { val minWidth = with(LocalDensity.current) { 45.sp.toDp() } + val durationColor = simplexSecondaryTint() Text( text.value, Modifier .padding(padding) .widthIn(min = minWidth), - color = MaterialTheme.colors.secondary, + color = durationColor, fontSize = if (smallView) 15.sp else 16.sp, maxLines = 1 ) @@ -340,6 +341,7 @@ private fun FileStatusIcon( ) { val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage + val statusIconColor = simplexSecondaryTint() Surface( color = if (sent) sentColor else receivedColor, shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)), @@ -359,7 +361,7 @@ private fun FileStatusIcon( painterResource(icon), contentDescription = null, Modifier.size(36.sp.toDp() * sizeMultiplier), - tint = MaterialTheme.colors.secondary + tint = statusIconColor ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 4f6cde9a4f..c501305393 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -48,7 +48,10 @@ val msgTailWidthDp = 9.dp private val msgTailMinHeightDp = msgTailWidthDp * 1.254f // ~56deg private val msgTailMaxHeightDp = msgTailWidthDp * 1.732f // 60deg -val chatEventStyle = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = CurrentColors.value.colors.secondary) +// Event-text style; the colour is applied at the render site (event views use +// simplexSecondaryTint), so it follows the composition theme. Unspecified here defers the +// colour to the Text/ClickableText that draws the string. +val chatEventStyle = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = Color.Unspecified) fun chatEventText(ci: ChatItem, isChannel: Boolean = false): AnnotatedString = chatEventText(ci.content.text(isChannel), ci.timestampText) @@ -647,7 +650,8 @@ fun ChatItemView( buildAnnotatedString { withStyle(chatEventStyle.copy(fontWeight = FontWeight.Bold)) { append(cItem.content.text(cInfo.isChannel)) } }, - Modifier.padding(horizontal = 6.dp, vertical = 6.dp) + Modifier.padding(horizontal = 6.dp, vertical = 6.dp), + color = simplexSecondaryTint() ) } @@ -677,7 +681,8 @@ fun ChatItemView( buildAnnotatedString { withStyle(chatEventStyle) { append(annotatedStringResource(sId)) } }, - Modifier.padding(horizontal = 6.dp, vertical = 6.dp) + Modifier.padding(horizontal = 6.dp, vertical = 6.dp), + color = simplexSecondaryTint() ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt index 17245c4e75..7456d5f8cd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.desktop.ui.tooling.preview.Preview @@ -18,20 +19,19 @@ import chat.simplex.common.ui.theme.* @Composable fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showViaProxy: Boolean, showTimestamp: Boolean) { val sent = ci.chatDir.sent - val sentColor = MaterialTheme.appColors.sentMessage - val receivedColor = MaterialTheme.appColors.receivedMessage - Surface( - shape = RoundedCornerShape(18.dp), - color = if (sent) sentColor else receivedColor, - contentColor = LocalContentColor.current + Box( + Modifier + .clip(RoundedCornerShape(18.dp)) + .chatBubbleBackground(sent = sent, isQuote = false) ) { Row( Modifier.padding(horizontal = 12.dp, vertical = 6.dp), verticalAlignment = Alignment.Bottom ) { + val deletedColor = simplexSecondaryTint() Text( buildAnnotatedString { - withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = MaterialTheme.colors.secondary)) { append(ci.content.text) } + withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = deletedColor)) { append(ci.content.text) } }, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp), modifier = Modifier.padding(end = 8.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 2e1db8928e..4751ef27ec 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -84,12 +84,13 @@ fun FramedItemView( ) { val sender = qi.sender(membership()) if (sender != null) { + val authorColor = simplexAuthorTint() Column( horizontalAlignment = Alignment.Start ) { Text( sender, - style = TextStyle(fontSize = 13.5.sp, color = if (qi.chatDir is CIDirection.GroupSnd) CurrentColors.value.colors.primary else CurrentColors.value.colors.secondary), + style = TextStyle(fontSize = 13.5.sp, color = if (CurrentColors.value.base == DefaultTheme.SIMPLEX) authorColor else if (qi.chatDir is CIDirection.GroupSnd) CurrentColors.value.colors.primary else CurrentColors.value.colors.secondary), maxLines = 1 ) ciQuotedMsgTextView(qi, lines = 2, showTimestamp = showTimestamp, stripLink = stripLink, prefix = prefix) @@ -102,11 +103,9 @@ fun FramedItemView( @Composable fun FramedItemHeader(caption: String, italic: Boolean, icon: Painter? = null, pad: Boolean = false, iconColor: Color? = null) { - val sentColor = MaterialTheme.appColors.sentQuote - val receivedColor = MaterialTheme.appColors.receivedQuote Row( Modifier - .background(if (sent) sentColor else receivedColor) + .chatBubbleBackground(sent = sent, isQuote = true) .fillMaxWidth() .padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (pad || (ci.quotedItem == null && ci.meta.itemForwarded == null)) 6.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -135,11 +134,9 @@ fun FramedItemView( @Composable fun ciQuoteView(qi: CIQuote) { - val sentColor = MaterialTheme.appColors.sentQuote - val receivedColor = MaterialTheme.appColors.receivedQuote Row( Modifier - .background(if (sent) sentColor else receivedColor) + .chatBubbleBackground(sent = sent, isQuote = true) .fillMaxWidth() ) { when (qi.content) { @@ -210,18 +207,13 @@ fun FramedItemView( val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) && !ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null && ci.meta.itemForwarded == null - val sentColor = MaterialTheme.appColors.sentMessage - val receivedColor = MaterialTheme.appColors.receivedMessage + val (linkColor, linkModifier) = simplexLinkColor() Box(Modifier .clipChatItem(ci, tailVisible, revealed = true) - .background( - when { - transparentBackground -> Color.Transparent - sent -> sentColor - else -> receivedColor - } - )) { - var metaColor = MaterialTheme.colors.secondary + .chatBubbleBackground(sent = sent, isQuote = false, transparent = transparentBackground) + .then(linkModifier)) { + var metaColor: Color? = null + CompositionLocalProvider(LocalSimplexLinkColor provides linkColor) { Box(contentAlignment = Alignment.BottomEnd) { val chatItemTail = remember { appPreferences.chatItemTail.state } val style = shapeStyle(ci, chatItemTail.value, tailVisible, true) @@ -385,6 +377,7 @@ fun FramedItemView( CIMetaView(ci, chatTTL, metaColor, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt index 08b6520dfa..2530b7c74a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt @@ -1,6 +1,7 @@ package chat.simplex.common.views.chat.item import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -9,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle @@ -51,12 +53,10 @@ fun IntegrityErrorItemView(msgError: MsgErrorType, ci: ChatItem, showTimestamp: @Composable fun CIMsgError(ci: ChatItem, showTimestamp: Boolean, timedMessagesTTL: Int?, onClick: () -> Unit) { - val receivedColor = MaterialTheme.appColors.receivedMessage - Surface( - Modifier.clickable(onClick = onClick), - shape = RoundedCornerShape(18.dp), - color = receivedColor, - contentColor = LocalContentColor.current + Box( + Modifier.clickable(onClick = onClick) + .clip(RoundedCornerShape(18.dp)) + .chatBubbleBackground(sent = false, isQuote = false) ) { Row( Modifier.padding(horizontal = 12.dp, vertical = 6.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt index 84bc14fee3..7a20aab24d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextOverflow @@ -21,12 +22,10 @@ import kotlinx.datetime.Clock @Composable fun MarkedDeletedItemView(chatsCtx: ChatModel.ChatsContext, ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: Int?, revealed: State, showViaProxy: Boolean, showTimestamp: Boolean) { - val sentColor = MaterialTheme.appColors.sentMessage - val receivedColor = MaterialTheme.appColors.receivedMessage - Surface( - shape = RoundedCornerShape(18.dp), - color = if (ci.chatDir.sent) sentColor else receivedColor, - contentColor = LocalContentColor.current + Box( + Modifier + .clip(RoundedCornerShape(18.dp)) + .chatBubbleBackground(sent = ci.chatDir.sent, isQuote = false) ) { Row( Modifier.padding(horizontal = 12.dp, vertical = 6.dp), @@ -80,9 +79,10 @@ private fun MergedMarkedDeletedText(chatsCtx: ChatModel.ChatsContext, chatItem: markedDeletedText(chatItem, chatInfo) } + val deletedColor = simplexSecondaryTint() Text( buildAnnotatedString { - withStyle(SpanStyle(fontSize = 12.sp, fontStyle = FontStyle.Italic, color = MaterialTheme.colors.secondary)) { append(text) } + withStyle(SpanStyle(fontSize = 12.sp, fontStyle = FontStyle.Italic, color = deletedColor)) { append(text) } }, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp), modifier = Modifier.padding(end = 8.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt index 50d9038e6f..04e18213d9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt @@ -22,16 +22,17 @@ import kotlinx.serialization.Serializable import java.io.File import kotlin.math.* -// Per-wallpaper SIMPLEX gradient stops: chat background + the four bubble slots + secondary/author text. -// The background stops are drawn by simplexGradient(); bubble/text stops are consumed by SimplexBrushes. +// Per-wallpaper SIMPLEX gradient stops: chat background + the four bubble slots, plus the +// secondary/author text tints. Background stops are drawn by simplexGradient(); bubble stops +// and the semi-transparent text tints are consumed by SimplexBrushes. data class SimplexStops( val bg: Array>, val sent: Array>, val sentQuote: Array>, val received: Array>, val receivedQuote: Array>, - val secondary: List>, - val author: List>, + val secondaryTint: Color, + val authorTint: Color, ) enum class PresetWallpaper( @@ -108,17 +109,8 @@ enum class PresetWallpaper( 0.84f to oklch(0.3700f, 0.0742f, 88f), 1.00f to oklch(0.4350f, 0.1049f, 105f), ), - secondary = listOf( - 0.20f to oklch(0.5050f, 0.1072f, 88f), - 0.55f to oklch(0.5350f, 0.1135f, 88f), - 0.90f to oklch(0.7050f, 0.1200f, 88f), - ), - author = listOf( - 0.20f to oklch(0.6300f, 0.1367f, 88f), - 0.55f to oklch(0.6450f, 0.1399f, 88f), - 0.70f to oklch(0.7250f, 0.1100f, 88f), - 0.90f to oklch(0.9923f, 0.0170f, 100f), - ), + secondaryTint = oklch(0.8874f, 0.1270f, 86.2f, 0.6f), + authorTint = oklch(0.8518f, 0.0736f, 87.2f, 0.6f), ) ), FLOWERS(MR.images.wallpaper_flowers, "flowers", 0.53f, @@ -186,17 +178,8 @@ enum class PresetWallpaper( 0.84f to oklch(0.4130f, 0.1171f, 130f), 1.00f to oklch(0.5040f, 0.1217f, 113f), ), - secondary = listOf( - 0.20f to oklch(0.5200f, 0.1320f, 130f), - 0.55f to oklch(0.5500f, 0.1421f, 130f), - 0.90f to oklch(0.7200f, 0.1218f, 130f), - ), - author = listOf( - 0.20f to oklch(0.6450f, 0.1523f, 130f), - 0.55f to oklch(0.6600f, 0.1523f, 130f), - 0.70f to oklch(0.7400f, 0.1117f, 130f), - 0.90f to oklch(0.9923f, 0.0170f, 100f), - ), + secondaryTint = oklch(0.8874f, 0.1270f, 130.0f, 0.6f), + authorTint = oklch(0.8518f, 0.0736f, 130.0f, 0.6f), ) ), HEARTS(MR.images.wallpaper_hearts, "hearts", 0.59f, @@ -264,17 +247,8 @@ enum class PresetWallpaper( 0.84f to oklch(0.4230f, 0.1319f, 5f), 1.00f to oklch(0.5140f, 0.1234f, 22f), ), - secondary = listOf( - 0.20f to oklch(0.5300f, 0.1339f, 5f), - 0.55f to oklch(0.5600f, 0.1442f, 5f), - 0.90f to oklch(0.7300f, 0.1236f, 5f), - ), - author = listOf( - 0.20f to oklch(0.6550f, 0.1545f, 5f), - 0.55f to oklch(0.6700f, 0.1545f, 5f), - 0.70f to oklch(0.7500f, 0.1133f, 5f), - 0.90f to oklch(0.9923f, 0.0170f, 100f), - ), + secondaryTint = oklch(0.8874f, 0.1270f, 5.0f, 0.6f), + authorTint = oklch(0.8518f, 0.0736f, 5.0f, 0.6f), ) ), KIDS(MR.images.wallpaper_kids, "kids", 0.53f, @@ -342,17 +316,8 @@ enum class PresetWallpaper( 0.84f to oklch(0.4230f, 0.0819f, 200f), 1.00f to oklch(0.5140f, 0.1195f, 217f), ), - secondary = listOf( - 0.20f to oklch(0.5300f, 0.1087f, 200f), - 0.55f to oklch(0.5600f, 0.1149f, 200f), - 0.90f to oklch(0.7300f, 0.1200f, 200f), - ), - author = listOf( - 0.20f to oklch(0.6550f, 0.1373f, 200f), - 0.55f to oklch(0.6700f, 0.1405f, 200f), - 0.70f to oklch(0.7500f, 0.1100f, 200f), - 0.90f to oklch(0.9923f, 0.0170f, 100f), - ), + secondaryTint = oklch(0.8874f, 0.1270f, 200.0f, 0.6f), + authorTint = oklch(0.8518f, 0.0736f, 200.0f, 0.6f), ) ), SCHOOL(MR.images.wallpaper_school, "school", 0.53f, @@ -420,17 +385,8 @@ enum class PresetWallpaper( 0.84f to oklch(0.4950f, 0.1346f, 271f), 1.00f to oklch(0.6250f, 0.1286f, 254f), ), - secondary = listOf( - 0.20f to oklch(0.5600f, 0.1397f, 271f), - 0.55f to oklch(0.5900f, 0.1505f, 271f), - 0.90f to oklch(0.7600f, 0.1172f, 271f), - ), - author = listOf( - 0.20f to oklch(0.6850f, 0.1612f, 271f), - 0.55f to oklch(0.7000f, 0.1562f, 271f), - 0.70f to oklch(0.7800f, 0.1116f, 271f), - 0.90f to oklch(0.9923f, 0.0170f, 100f), - ), + secondaryTint = oklch(0.8874f, 0.1270f, 271.0f, 0.6f), + authorTint = oklch(0.8518f, 0.0736f, 271.0f, 0.6f), ) ), TRAVEL(MR.images.wallpaper_travel, "travel", 0.68f, @@ -498,17 +454,8 @@ enum class PresetWallpaper( 0.84f to oklch(0.4470f, 0.1328f, 315f), 1.00f to oklch(0.5510f, 0.1251f, 298f), ), - secondary = listOf( - 0.20f to oklch(0.5400f, 0.1358f, 315f), - 0.55f to oklch(0.5700f, 0.1463f, 315f), - 0.90f to oklch(0.7400f, 0.1254f, 315f), - ), - author = listOf( - 0.20f to oklch(0.6650f, 0.1567f, 315f), - 0.55f to oklch(0.6800f, 0.1567f, 315f), - 0.70f to oklch(0.7600f, 0.1149f, 315f), - 0.90f to oklch(0.9923f, 0.0170f, 100f), - ), + secondaryTint = oklch(0.8874f, 0.1270f, 315.0f, 0.6f), + authorTint = oklch(0.8518f, 0.0736f, 315.0f, 0.6f), ) );