mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 00:44:27 +00:00
SIMPLEX theme: sunrise gradient on chat bubbles, avatars and text
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.
This commit is contained in:
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+240
@@ -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<Color?> { null }
|
||||
|
||||
class ChatViewportInfo(
|
||||
val sizePx: State<IntSize>,
|
||||
val originInWindow: State<Offset>,
|
||||
)
|
||||
|
||||
val LocalChatViewportInfo = compositionLocalOf<ChatViewportInfo?> { null }
|
||||
|
||||
@Composable
|
||||
fun rememberChatViewportInfo(): Pair<ChatViewportInfo, Modifier> {
|
||||
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<Offset, Offset> {
|
||||
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<Pair<Float, Color>>, 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<Pair<Float, Color>>? {
|
||||
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<Color, Modifier> = simplexAxisSampledColor(LINK_STOPS, MaterialTheme.colors.primary)
|
||||
|
||||
@Composable
|
||||
private fun simplexAxisSampledColor(stops: List<Pair<Float, Color>>?, fallback: Color): Pair<Color, Modifier> {
|
||||
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
|
||||
}
|
||||
+23
-11
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -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 -> {}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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
|
||||
)
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+3
-1
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-7
@@ -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
|
||||
)
|
||||
|
||||
+32
-10
@@ -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/*(
|
||||
|
||||
+3
-1
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-16
@@ -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))
|
||||
|
||||
+10
-14
@@ -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),
|
||||
|
||||
+4
-2
@@ -227,12 +227,13 @@ private fun VoiceLayout(
|
||||
@Composable
|
||||
private fun DurationText(text: State<String>, 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -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)
|
||||
|
||||
+10
-17
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -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),
|
||||
|
||||
+7
-7
@@ -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<Boolean>, 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),
|
||||
|
||||
+17
-70
@@ -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<Pair<Float, Color>>,
|
||||
val sent: Array<Pair<Float, Color>>,
|
||||
val sentQuote: Array<Pair<Float, Color>>,
|
||||
val received: Array<Pair<Float, Color>>,
|
||||
val receivedQuote: Array<Pair<Float, Color>>,
|
||||
val secondary: List<Pair<Float, Color>>,
|
||||
val author: List<Pair<Float, Color>>,
|
||||
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),
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user