diff --git a/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt b/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt index 485fb9b0..14c7879d 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt @@ -193,42 +193,6 @@ private fun appendMutedTimestamp( builder.pop() } -/** - * Per-check colours for the inline delivery marker trailing the timestamp inside a bubble. - * - * Both checks always render — grey until an acknowledgement turns them on — so a status change - * recolours in place and can never reflow the message text. - */ -data class MessageStatusGlyph( - val firstColor: Color, - val secondColor: Color, -) - -private fun appendStatusGlyph( - builder: AnnotatedString.Builder, - glyph: MessageStatusGlyph, -) { - builder.append(" ") - builder.pushStyle( - SpanStyle( - color = glyph.firstColor, - fontSize = ChatVisualTokens.SystemTimeFontSize, - fontWeight = FontWeight.Normal, - ) - ) - builder.append("✓") - builder.pop() - builder.pushStyle( - SpanStyle( - color = glyph.secondColor, - fontSize = ChatVisualTokens.SystemTimeFontSize, - fontWeight = FontWeight.Normal, - ) - ) - builder.append("✓") - builder.pop() -} - /** * Build the message body: neutral text with mention/URL/geohash accents, followed by an inline * trailing timestamp. @@ -244,8 +208,7 @@ fun formatTextMessageBody( linkColor: Color, mentionPeerIdentities: Map = emptyMap(), timeFormatter: SimpleDateFormat = SimpleDateFormat(CHAT_TIMESTAMP_PATTERN, Locale.getDefault()), - includeTimestamp: Boolean = true, - statusGlyph: MessageStatusGlyph? = null + includeTimestamp: Boolean = true ): AnnotatedString { val builder = AnnotatedString.Builder() @@ -262,9 +225,6 @@ fun formatTextMessageBody( if (includeTimestamp) { appendBodyTimestamp(builder, message, palette, timeFormatter) } - if (statusGlyph != null) { - appendStatusGlyph(builder, statusGlyph) - } return builder.toAnnotatedString() } diff --git a/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt b/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt index 6b3b52af..bb5d4987 100644 --- a/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt @@ -760,30 +760,6 @@ internal fun TextMessageLayout( onMessageLongPress?.invoke(message) } - // Bubble mode pulls the delivery marker into the bubble, trailing the timestamp. Both - // checks render from the start — grey until an acknowledgement turns them green — so a - // status change recolours in place and never reflows the text. The colour transition is - // animated, which reads as the checks lighting up rather than popping in. - val checkTargets = deliveryCheckColors( - status = if (bubbles && isSelf && message.isPrivate) message.deliveryStatus else null, - colorScheme = colorScheme, - ) - val firstCheck by animateColorAsState( - targetValue = checkTargets.first, - animationSpec = tween(BitchatMotion.QUICK_MS), - label = "firstCheckColor", - ) - val secondCheck by animateColorAsState( - targetValue = checkTargets.second, - animationSpec = tween(BitchatMotion.QUICK_MS), - label = "secondCheckColor", - ) - val statusGlyph = if (bubbles && isSelf && message.isPrivate && message.deliveryStatus != null) { - MessageStatusGlyph(firstColor = firstCheck, secondColor = secondCheck) - } else { - null - } - // The timestamp trails the body rather than occupying its own column, so a short message // no longer reserves a full-width row for eight grey characters. val bodyText = remember( @@ -793,8 +769,7 @@ internal fun TextMessageLayout( colorScheme.onSurface, colorScheme.secondary, mentionPeerIdentities, - timeFormatter, - statusGlyph + timeFormatter ) { formatTextMessageBody( message = displayMessage, @@ -804,7 +779,6 @@ internal fun TextMessageLayout( linkColor = colorScheme.secondary, mentionPeerIdentities = mentionPeerIdentities, timeFormatter = timeFormatter, - statusGlyph = statusGlyph, ) } @@ -966,32 +940,52 @@ private fun BubbleTextMessageLayout( ) } - AnnotatedClickableText( - text = bodyText, - annotationTags = listOf("geohash_click", "url_click"), - onAnnotationClick = { tag, item -> - when (tag) { - "geohash_click" -> { - navigateToGeohash(context, item) - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - true - } + Box { + AnnotatedClickableText( + text = bodyText, + annotationTags = listOf("geohash_click", "url_click"), + onAnnotationClick = { tag, item -> + when (tag) { + "geohash_click" -> { + navigateToGeohash(context, item) + haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) + true + } - "url_click" -> { - openMessageUrl(context, item) - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - true - } + "url_click" -> { + openMessageUrl(context, item) + haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) + true + } - else -> false + else -> false + } + }, + onLongPress = onLongPress, + // Keep the last line clear of the checks parked at the bubble's corner. + modifier = Modifier.padding( + end = if (isSelf && message.isPrivate && message.deliveryStatus != null) { + ChatVisualTokens.BubbleStatusInset + } else { + 0.dp + } + ), + fontFamily = BitchatFontFamily, + softWrap = true, + overflow = TextOverflow.Visible, + style = MessageBodyTextStyle.copy(color = MaterialTheme.colorScheme.onSurface), + ) + + // Delivery checks anchor the bubble's bottom-end corner, like classic + // messengers, instead of trailing the timestamp mid-line. + if (isSelf && message.isPrivate) { + message.deliveryStatus?.let { status -> + Box(modifier = Modifier.align(Alignment.BottomEnd)) { + DeliveryStatusIcon(status = status) + } } - }, - onLongPress = onLongPress, - fontFamily = BitchatFontFamily, - softWrap = true, - overflow = TextOverflow.Visible, - style = MessageBodyTextStyle.copy(color = MaterialTheme.colorScheme.onSurface), - ) + } + } } } } diff --git a/app/src/main/java/com/bitchat/android/ui/theme/ChatVisualTokens.kt b/app/src/main/java/com/bitchat/android/ui/theme/ChatVisualTokens.kt index 10ab9e9e..1db0d45a 100644 --- a/app/src/main/java/com/bitchat/android/ui/theme/ChatVisualTokens.kt +++ b/app/src/main/java/com/bitchat/android/ui/theme/ChatVisualTokens.kt @@ -61,6 +61,9 @@ internal object ChatVisualTokens { /** Author-colour hairline around a bubble; stronger than the fill so the shape reads. */ const val BubbleBorderAlpha: Float = 0.38f + /** End inset reserving room for the delivery checks parked at a bubble's bottom-end corner. */ + val BubbleStatusInset: Dp = 18.dp + const val SenderSuffixAlpha: Float = 0.60f const val HighlightAlpha: Float = 0.20f const val MutedTextAlpha: Float = 0.50f