This commit is contained in:
callebtc
2026-01-17 01:34:20 +07:00
parent a81bf4489d
commit ad7034577d
7 changed files with 100 additions and 63 deletions
@@ -130,6 +130,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
) )
// Messages area - takes up available space, will compress when keyboard appears // Messages area - takes up available space, will compress when keyboard appears
val peerNicknames by viewModel.peerNicknames.collectAsStateWithLifecycle()
MessagesList( MessagesList(
messages = displayMessages, messages = displayMessages,
currentUserNickname = nickname, currentUserNickname = nickname,
@@ -137,6 +138,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
forceScrollToBottom = forceScrollToBottom, forceScrollToBottom = forceScrollToBottom,
onScrolledUpChanged = { isUp -> isScrolledUp = isUp }, onScrolledUpChanged = { isUp -> isScrolledUp = isUp },
peerNicknames = peerNicknames,
onNicknameClick = { fullSenderName -> onNicknameClick = { fullSenderName ->
// Single click - mention user in text input // Single click - mention user in text input
val currentText = messageText.text val currentText = messageText.text
@@ -44,7 +44,8 @@ fun formatMessageAsAnnotatedString(
currentUserNickname: String, currentUserNickname: String,
meshService: BluetoothMeshService, meshService: BluetoothMeshService,
colorScheme: ColorScheme, colorScheme: ColorScheme,
timeFormatter: SimpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) timeFormatter: SimpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()),
peerNicknames: Map<String, String> = emptyMap()
): AnnotatedString { ): AnnotatedString {
val builder = AnnotatedString.Builder() val builder = AnnotatedString.Builder()
val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f
@@ -53,26 +54,26 @@ fun formatMessageAsAnnotatedString(
val isSelf = message.senderPeerID == meshService.myPeerID || val isSelf = message.senderPeerID == meshService.myPeerID ||
message.sender == currentUserNickname || message.sender == currentUserNickname ||
message.sender.startsWith("$currentUserNickname#") message.sender.startsWith("$currentUserNickname#")
if (message.sender != "system") { if (message.sender != "system") {
// Resolve disambiguated sender name if it's a mesh message // Resolve disambiguated sender name if it's a mesh message
val senderDisplayName = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) { val senderDisplayName = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) {
val disambiguated = meshService.getDisambiguatedNickname(message.senderPeerID) val disambiguated = meshService.getDisambiguatedNickname(message.senderPeerID)
// If it returned the raw peerID because the peer is offline, fall back to message.sender (nickname at time of send) // If it returned the raw peerID because the peer is offline, fall back to message.sender (nickname at time of send)
if (disambiguated == message.senderPeerID && message.sender != message.senderPeerID) { if (disambiguated == message.senderPeerID && message.sender != message.senderPeerID) {
message.sender message.sender
} else { } else {
disambiguated disambiguated
} }
} else { } else {
message.sender message.sender
} }
// Get base color for this peer (iOS-style color assignment) // Get base color for this peer (iOS-style color assignment)
val baseColor = if (isSelf) { val baseColor = if (isSelf) {
Color(0xFFFF9500) // Orange for self (iOS orange) Color(0xFFFF9500) // Orange for self (iOS orange)
} else { } else {
getPeerColor(message, isDark) colorForPeer(message.senderPeerID, message.sender, isDark)
} }
// Split sender into base name and hashtag suffix // Split sender into base name and hashtag suffix
@@ -130,10 +131,9 @@ fun formatMessageAsAnnotatedString(
builder.pop() builder.pop()
// Message content with iOS-style hashtag and mention highlighting // Message content with iOS-style hashtag and mention highlighting
appendIOSFormattedContent(builder, message.content, message.mentions, currentUserNickname, baseColor, isSelf, isDark) appendIOSFormattedContent(builder, message.content, message.mentions, currentUserNickname, baseColor, isSelf, isDark, meshService, peerNicknames)
// iOS-style timestamp at the END (smaller, grey) // iOS-style timestamp at the END (smaller, grey)
// Timestamp (and optional PoW badge)
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = Color.Gray.copy(alpha = 0.7f), color = Color.Gray.copy(alpha = 0.7f),
fontSize = (BASE_FONT_SIZE - 4).sp fontSize = (BASE_FONT_SIZE - 4).sp
@@ -152,7 +152,7 @@ fun formatMessageAsAnnotatedString(
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = Color.Gray, color = Color.Gray,
fontSize = (BASE_FONT_SIZE - 2).sp, fontSize = (BASE_FONT_SIZE - 2).sp,
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic fontStyle = FontStyle.Italic
)) ))
builder.append("* ${message.content} *") builder.append("* ${message.content} *")
builder.pop() builder.pop()
@@ -177,7 +177,8 @@ fun formatMessageHeaderAnnotatedString(
currentUserNickname: String, currentUserNickname: String,
meshService: BluetoothMeshService, meshService: BluetoothMeshService,
colorScheme: ColorScheme, colorScheme: ColorScheme,
timeFormatter: SimpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) timeFormatter: SimpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()),
peerNicknames: Map<String, String> = emptyMap()
): AnnotatedString { ): AnnotatedString {
val builder = AnnotatedString.Builder() val builder = AnnotatedString.Builder()
val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f
@@ -189,18 +190,12 @@ fun formatMessageHeaderAnnotatedString(
if (message.sender != "system") { if (message.sender != "system") {
// Resolve disambiguated sender name if it's a mesh message // Resolve disambiguated sender name if it's a mesh message
val senderDisplayName = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) { val senderDisplayName = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) {
val disambiguated = meshService.getDisambiguatedNickname(message.senderPeerID) meshService.getDisambiguatedNickname(message.senderPeerID)
// If it returned the raw peerID because the peer is offline, fall back to message.sender (nickname at time of send)
if (disambiguated == message.senderPeerID && message.sender != message.senderPeerID) {
message.sender
} else {
disambiguated
}
} else { } else {
message.sender message.sender
} }
val baseColor = if (isSelf) Color(0xFFFF9500) else getPeerColor(message, isDark) val baseColor = if (isSelf) Color(0xFFFF9500) else colorForPeer(message.senderPeerID, message.sender, isDark)
val (baseName, suffix) = splitSuffix(senderDisplayName) val (baseName, suffix) = splitSuffix(senderDisplayName)
// "<@" // "<@"
@@ -266,7 +261,7 @@ fun formatMessageHeaderAnnotatedString(
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = Color.Gray, color = Color.Gray,
fontSize = (BASE_FONT_SIZE - 2).sp, fontSize = (BASE_FONT_SIZE - 2).sp,
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic fontStyle = FontStyle.Italic
)) ))
builder.append("* ${message.content} *") builder.append("* ${message.content} *")
builder.pop() builder.pop()
@@ -286,23 +281,26 @@ fun formatMessageHeaderAnnotatedString(
* Avoids orange (~30°) reserved for self messages * Avoids orange (~30°) reserved for self messages
*/ */
fun getPeerColor(message: BitchatMessage, isDark: Boolean): Color { fun getPeerColor(message: BitchatMessage, isDark: Boolean): Color {
return colorForPeer(message.senderPeerID, message.sender, isDark)
}
/**
* Generate consistent peer color based on peer ID or nickname
*/
fun colorForPeer(peerID: String?, nickname: String, isDark: Boolean): Color {
// Create seed from peer identifier (prioritizing stable keys) // Create seed from peer identifier (prioritizing stable keys)
val seed = when { val seed = when {
message.senderPeerID?.startsWith("nostr:") == true || message.senderPeerID?.startsWith("nostr_") == true -> { peerID?.startsWith("nostr:") == true || peerID?.startsWith("nostr_") == true -> {
// For Nostr peers, use the full key if available, otherwise the peer ID "nostr:${peerID.lowercase()}"
"nostr:${message.senderPeerID.lowercase()}"
} }
message.senderPeerID?.length == 16 -> { peerID?.length == 16 -> {
// For ephemeral peer IDs, try to get stable Noise key, fallback to peer ID "noise:${peerID.lowercase()}"
"noise:${message.senderPeerID.lowercase()}"
} }
message.senderPeerID?.length == 64 -> { peerID?.length == 64 -> {
// This is already a stable Noise key "noise:${peerID.lowercase()}"
"noise:${message.senderPeerID.lowercase()}"
} }
else -> { else -> {
// Fallback to sender name nickname.lowercase()
message.sender.lowercase()
} }
} }
@@ -354,6 +352,34 @@ fun splitSuffix(name: String): Pair<String, String> {
return Pair(name, "") return Pair(name, "")
} }
/**
* Truncate nickname if it's too long for UI display
*/
fun truncateNickname(nickname: String): String {
return if (nickname.length > 20) {
nickname.take(18) + ".."
} else {
nickname
}
}
/**
* Resolve a display name (with potential #suffix) back to a Peer ID
*/
fun resolvePeerIDFromDisplayName(displayName: String, nicknames: Map<String, String>): String? {
val (base, suffix) = splitSuffix(displayName)
return nicknames.entries.find { (id, nick) ->
if (suffix.isNotEmpty()) {
// Match nickname and Peer ID ending
nick.equals(base, ignoreCase = true) && id.endsWith(suffix.removePrefix("#"), ignoreCase = true)
} else {
// Match nickname only
nick.equals(displayName, ignoreCase = true)
}
}?.key
}
/** /**
* iOS-style content formatting with proper hashtag and mention handling * iOS-style content formatting with proper hashtag and mention handling
*/ */
@@ -364,7 +390,9 @@ private fun appendIOSFormattedContent(
currentUserNickname: String, currentUserNickname: String,
baseColor: Color, baseColor: Color,
isSelf: Boolean, isSelf: Boolean,
isDark: Boolean isDark: Boolean,
meshService: BluetoothMeshService,
peerNicknames: Map<String, String> = emptyMap()
) { ) {
// iOS-style patterns: allow optional '#abcd' suffix in mentions // iOS-style patterns: allow optional '#abcd' suffix in mentions
val hashtagPattern = "#([a-zA-Z0-9_]+)".toRegex() val hashtagPattern = "#([a-zA-Z0-9_]+)".toRegex()
@@ -396,7 +424,6 @@ private fun appendIOSFormattedContent(
} }
// Add standalone geohash matches (e.g., "#9q") that are not part of another word // Add standalone geohash matches (e.g., "#9q") that are not part of another word
// We use MessageSpecialParser to find exact ranges; then merge with existing ranges avoiding overlaps
val geoMatches = MessageSpecialParser.findStandaloneGeohashes(content) val geoMatches = MessageSpecialParser.findStandaloneGeohashes(content)
for (gm in geoMatches) { for (gm in geoMatches) {
val range = gm.start until gm.endExclusive val range = gm.start until gm.endExclusive
@@ -414,7 +441,7 @@ private fun appendIOSFormattedContent(
} }
} }
// Remove generic hashtag matches that overlap with detected geohash ranges to avoid duplicate rendering // Remove generic hashtag matches that overlap with detected geohash ranges
fun rangesOverlap(a: IntRange, b: IntRange): Boolean { fun rangesOverlap(a: IntRange, b: IntRange): Boolean {
return a.first < b.last && a.last > b.first return a.first < b.last && a.last > b.first
} }
@@ -424,7 +451,6 @@ private fun appendIOSFormattedContent(
val iterator = allMatches.listIterator() val iterator = allMatches.listIterator()
while (iterator.hasNext()) { while (iterator.hasNext()) {
val (range, type) = iterator.next() val (range, type) = iterator.next()
// Remove generic hashtags that overlap with geohashes, and geohashes that overlap with URLs
val overlapsGeo = geoRanges.any { rangesOverlap(range, it) } val overlapsGeo = geoRanges.any { rangesOverlap(range, it) }
val overlapsUrl = urlRanges.any { rangesOverlap(range, it) } val overlapsUrl = urlRanges.any { rangesOverlap(range, it) }
if ((type == "hashtag" && overlapsGeo) || (type == "geohash" && overlapsUrl)) iterator.remove() if ((type == "hashtag" && overlapsGeo) || (type == "geohash" && overlapsUrl)) iterator.remove()
@@ -436,6 +462,9 @@ private fun appendIOSFormattedContent(
var lastEnd = 0 var lastEnd = 0
val isMentioned = mentions?.contains(currentUserNickname) == true val isMentioned = mentions?.contains(currentUserNickname) == true
// Use provided map if available, else query service once
val nicknameMap = if (peerNicknames.isNotEmpty()) peerNicknames else meshService.getPeerNicknames()
for ((range, type) in allMatches) { for ((range, type) in allMatches) {
// Add text before match // Add text before match
if (lastEnd < range.first) { if (lastEnd < range.first) {
@@ -447,7 +476,6 @@ private fun appendIOSFormattedContent(
fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Normal fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Normal
)) ))
if (isMentioned) { if (isMentioned) {
// Make entire message bold if user is mentioned
builder.pushStyle(SpanStyle(fontWeight = FontWeight.Bold)) builder.pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
builder.append(beforeText) builder.append(beforeText)
builder.pop() builder.pop()
@@ -462,13 +490,19 @@ private fun appendIOSFormattedContent(
val matchText = content.substring(range.first, range.last + 1) val matchText = content.substring(range.first, range.last + 1)
when (type) { when (type) {
"mention" -> { "mention" -> {
// iOS-style mention with hashtag suffix support
val mentionWithoutAt = matchText.removePrefix("@") val mentionWithoutAt = matchText.removePrefix("@")
val (mBase, mSuffix) = splitSuffix(mentionWithoutAt) val (mBase, mSuffix) = splitSuffix(mentionWithoutAt)
// Check if this mention targets current user // Check if this mention targets current user
val isMentionToMe = mBase == currentUserNickname val isMentionToMe = mBase.equals(currentUserNickname, ignoreCase = true)
val mentionColor = if (isMentionToMe) Color(0xFFFF9500) else baseColor
// Resolve mention color based on mentioned user's actual color
val mentionColor = if (isMentionToMe) {
Color(0xFFFF9500) // Orange for mentions to self
} else {
val resolvedID = resolvePeerIDFromDisplayName(mentionWithoutAt, nicknameMap)
colorForPeer(resolvedID, mentionWithoutAt, isDark)
}
// "@" symbol // "@" symbol
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
@@ -479,7 +513,7 @@ private fun appendIOSFormattedContent(
builder.append("@") builder.append("@")
builder.pop() builder.pop()
// Base name (truncate for rendering) // Base name
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = mentionColor, color = mentionColor,
fontSize = BASE_FONT_SIZE.sp, fontSize = BASE_FONT_SIZE.sp,
@@ -500,7 +534,6 @@ private fun appendIOSFormattedContent(
} }
} }
"hashtag" -> { "hashtag" -> {
// Render general hashtags like normal content
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = baseColor, color = baseColor,
fontSize = BASE_FONT_SIZE.sp, fontSize = BASE_FONT_SIZE.sp,
@@ -517,7 +550,6 @@ private fun appendIOSFormattedContent(
} }
else -> { else -> {
if (type == "geohash") { if (type == "geohash") {
// Style geohash in blue, underlined, and add click annotation
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = Color(0xFF007AFF), color = Color(0xFF007AFF),
fontSize = BASE_FONT_SIZE.sp, fontSize = BASE_FONT_SIZE.sp,
@@ -528,15 +560,9 @@ private fun appendIOSFormattedContent(
builder.append(matchText) builder.append(matchText)
val end = builder.length val end = builder.length
val geohash = matchText.removePrefix("#").lowercase() val geohash = matchText.removePrefix("#").lowercase()
builder.addStringAnnotation( builder.addStringAnnotation(tag = "geohash_click", annotation = geohash, start = start, end = end)
tag = "geohash_click",
annotation = geohash,
start = start,
end = end
)
builder.pop() builder.pop()
} else if (type == "url") { } else if (type == "url") {
// Style URL in blue, underlined, and add click annotation with the raw text
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = Color(0xFF007AFF), color = Color(0xFF007AFF),
fontSize = BASE_FONT_SIZE.sp, fontSize = BASE_FONT_SIZE.sp,
@@ -546,15 +572,9 @@ private fun appendIOSFormattedContent(
val start = builder.length val start = builder.length
builder.append(matchText) builder.append(matchText)
val end = builder.length val end = builder.length
builder.addStringAnnotation( builder.addStringAnnotation(tag = "url_click", annotation = matchText, start = start, end = end)
tag = "url_click",
annotation = matchText,
start = start,
end = end
)
builder.pop() builder.pop()
} else { } else {
// Fallback: treat as normal text
builder.pushStyle(SpanStyle( builder.pushStyle(SpanStyle(
color = baseColor, color = baseColor,
fontSize = BASE_FONT_SIZE.sp, fontSize = BASE_FONT_SIZE.sp,
@@ -251,7 +251,8 @@ private fun formatMessageAsAnnotatedStringWithoutTimestamp(
currentUserNickname = currentUserNickname, currentUserNickname = currentUserNickname,
meshService = meshService, meshService = meshService,
colorScheme = colorScheme, colorScheme = colorScheme,
timeFormatter = timeFormatter timeFormatter = timeFormatter,
peerNicknames = emptyMap() // Can't easily get map here without refactoring AnimatedMessageDisplay
) )
// Find and remove the timestamp and PoW badge at the end // Find and remove the timestamp and PoW badge at the end
@@ -839,7 +839,8 @@ fun PrivateChatSheet(
onNicknameClick = { /* handle mention */ }, onNicknameClick = { /* handle mention */ },
onMessageLongPress = { /* handle long press */ }, onMessageLongPress = { /* handle long press */ },
onCancelTransfer = { msg -> viewModel.cancelMediaSend(msg.id) }, onCancelTransfer = { msg -> viewModel.cancelMediaSend(msg.id) },
onImageClick = { _, _, _ -> /* handle image click */ } onImageClick = { _, _, _ -> /* handle image click */ },
peerNicknames = peerNicknames
) )
HorizontalDivider(color = colorScheme.outline.copy(alpha = 0.3f)) HorizontalDivider(color = colorScheme.outline.copy(alpha = 0.3f))
@@ -65,7 +65,8 @@ fun MessagesList(
onNicknameClick: ((String) -> Unit)? = null, onNicknameClick: ((String) -> Unit)? = null,
onMessageLongPress: ((BitchatMessage) -> Unit)? = null, onMessageLongPress: ((BitchatMessage) -> Unit)? = null,
onCancelTransfer: ((BitchatMessage) -> Unit)? = null, onCancelTransfer: ((BitchatMessage) -> Unit)? = null,
onImageClick: ((String, List<String>, Int) -> Unit)? = null onImageClick: ((String, List<String>, Int) -> Unit)? = null,
peerNicknames: Map<String, String> = emptyMap()
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -126,7 +127,8 @@ fun MessagesList(
onNicknameClick = onNicknameClick, onNicknameClick = onNicknameClick,
onMessageLongPress = onMessageLongPress, onMessageLongPress = onMessageLongPress,
onCancelTransfer = onCancelTransfer, onCancelTransfer = onCancelTransfer,
onImageClick = onImageClick onImageClick = onImageClick,
peerNicknames = peerNicknames
) )
} }
} }
@@ -142,7 +144,8 @@ fun MessageItem(
onNicknameClick: ((String) -> Unit)? = null, onNicknameClick: ((String) -> Unit)? = null,
onMessageLongPress: ((BitchatMessage) -> Unit)? = null, onMessageLongPress: ((BitchatMessage) -> Unit)? = null,
onCancelTransfer: ((BitchatMessage) -> Unit)? = null, onCancelTransfer: ((BitchatMessage) -> Unit)? = null,
onImageClick: ((String, List<String>, Int) -> Unit)? = null onImageClick: ((String, List<String>, Int) -> Unit)? = null,
peerNicknames: Map<String, String> = emptyMap()
) { ) {
val colorScheme = MaterialTheme.colorScheme val colorScheme = MaterialTheme.colorScheme
val timeFormatter = remember { SimpleDateFormat("HH:mm:ss", Locale.getDefault()) } val timeFormatter = remember { SimpleDateFormat("HH:mm:ss", Locale.getDefault()) }
@@ -171,6 +174,7 @@ fun MessageItem(
onMessageLongPress = onMessageLongPress, onMessageLongPress = onMessageLongPress,
onCancelTransfer = onCancelTransfer, onCancelTransfer = onCancelTransfer,
onImageClick = onImageClick, onImageClick = onImageClick,
peerNicknames = peerNicknames,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(end = endPad) .padding(end = endPad)
@@ -208,6 +212,7 @@ fun MessageItem(
onMessageLongPress: ((BitchatMessage) -> Unit)?, onMessageLongPress: ((BitchatMessage) -> Unit)?,
onCancelTransfer: ((BitchatMessage) -> Unit)?, onCancelTransfer: ((BitchatMessage) -> Unit)?,
onImageClick: ((String, List<String>, Int) -> Unit)?, onImageClick: ((String, List<String>, Int) -> Unit)?,
peerNicknames: Map<String, String> = emptyMap(),
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
// Image special rendering // Image special rendering
@@ -223,6 +228,7 @@ fun MessageItem(
onMessageLongPress = onMessageLongPress, onMessageLongPress = onMessageLongPress,
onCancelTransfer = onCancelTransfer, onCancelTransfer = onCancelTransfer,
onImageClick = onImageClick, onImageClick = onImageClick,
peerNicknames = peerNicknames,
modifier = modifier modifier = modifier
) )
return return
@@ -239,6 +245,7 @@ fun MessageItem(
onNicknameClick = onNicknameClick, onNicknameClick = onNicknameClick,
onMessageLongPress = onMessageLongPress, onMessageLongPress = onMessageLongPress,
onCancelTransfer = onCancelTransfer, onCancelTransfer = onCancelTransfer,
peerNicknames = peerNicknames,
modifier = modifier modifier = modifier
) )
return return
@@ -263,7 +270,8 @@ fun MessageItem(
currentUserNickname = currentUserNickname, currentUserNickname = currentUserNickname,
meshService = meshService, meshService = meshService,
colorScheme = colorScheme, colorScheme = colorScheme,
timeFormatter = timeFormatter timeFormatter = timeFormatter,
peerNicknames = peerNicknames
) )
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
var headerLayout by remember { mutableStateOf<TextLayoutResult?>(null) } var headerLayout by remember { mutableStateOf<TextLayoutResult?>(null) }
@@ -371,7 +379,8 @@ fun MessageItem(
currentUserNickname = currentUserNickname, currentUserNickname = currentUserNickname,
meshService = meshService, meshService = meshService,
colorScheme = colorScheme, colorScheme = colorScheme,
timeFormatter = timeFormatter timeFormatter = timeFormatter,
peerNicknames = peerNicknames
) )
// Check if this message was sent by self to avoid click interactions on own nickname // Check if this message was sent by self to avoid click interactions on own nickname
@@ -36,6 +36,7 @@ fun AudioMessageItem(
onNicknameClick: ((String) -> Unit)?, onNicknameClick: ((String) -> Unit)?,
onMessageLongPress: ((BitchatMessage) -> Unit)?, onMessageLongPress: ((BitchatMessage) -> Unit)?,
onCancelTransfer: ((BitchatMessage) -> Unit)?, onCancelTransfer: ((BitchatMessage) -> Unit)?,
peerNicknames: Map<String, String> = emptyMap(),
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val path = message.content.trim() val path = message.content.trim()
@@ -55,7 +56,8 @@ fun AudioMessageItem(
currentUserNickname = currentUserNickname, currentUserNickname = currentUserNickname,
meshService = meshService, meshService = meshService,
colorScheme = colorScheme, colorScheme = colorScheme,
timeFormatter = timeFormatter timeFormatter = timeFormatter,
peerNicknames = peerNicknames
) )
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
var headerLayout by remember { mutableStateOf<TextLayoutResult?>(null) } var headerLayout by remember { mutableStateOf<TextLayoutResult?>(null) }
@@ -46,6 +46,7 @@ fun ImageMessageItem(
onMessageLongPress: ((BitchatMessage) -> Unit)?, onMessageLongPress: ((BitchatMessage) -> Unit)?,
onCancelTransfer: ((BitchatMessage) -> Unit)?, onCancelTransfer: ((BitchatMessage) -> Unit)?,
onImageClick: ((String, List<String>, Int) -> Unit)?, onImageClick: ((String, List<String>, Int) -> Unit)?,
peerNicknames: Map<String, String> = emptyMap(),
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val path = message.content.trim() val path = message.content.trim()
@@ -55,7 +56,8 @@ fun ImageMessageItem(
currentUserNickname = currentUserNickname, currentUserNickname = currentUserNickname,
meshService = meshService, meshService = meshService,
colorScheme = colorScheme, colorScheme = colorScheme,
timeFormatter = timeFormatter timeFormatter = timeFormatter,
peerNicknames = peerNicknames
) )
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
var headerLayout by remember { mutableStateOf<TextLayoutResult?>(null) } var headerLayout by remember { mutableStateOf<TextLayoutResult?>(null) }