limit nick length (#330)

This commit is contained in:
callebtc
2025-08-29 00:27:55 +02:00
committed by GitHub
parent 550520795f
commit 9c103180cd
3 changed files with 100 additions and 19 deletions
@@ -78,7 +78,8 @@ fun formatMessageAsAnnotatedString(
fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Medium
))
val nicknameStart = builder.length
builder.append(baseName)
val truncatedBase = truncateNickname(baseName)
builder.append(truncatedBase)
val nicknameEnd = builder.length
// Add click annotation for nickname (store canonical sender name with hash if available)
@@ -332,13 +333,13 @@ private fun appendIOSFormattedContent(
builder.append("@")
builder.pop()
// Base name
// Base name (truncate for rendering)
builder.pushStyle(SpanStyle(
color = mentionColor,
fontSize = BASE_FONT_SIZE.sp,
fontWeight = if (isSelf) FontWeight.Bold else FontWeight.SemiBold
))
builder.append(mBase)
builder.append(truncateNickname(mBase))
builder.pop()
// Hashtag suffix in lighter color
@@ -19,6 +19,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
import java.util.*
import androidx.compose.ui.text.style.TextOverflow
/**
* GeohashPeopleList - iOS-compatible component for displaying geohash participants
@@ -115,6 +116,16 @@ fun GeohashPeopleList(
}
}
}
// Compute base name collisions to decide whether to show hash suffix
val baseNameCounts = remember(geohashPeople) {
val counts = mutableMapOf<String, Int>()
geohashPeople.forEach { person ->
val (b, _) = com.bitchat.android.ui.splitSuffix(person.displayName)
counts[b] = (counts[b] ?: 0) + 1
}
counts
}
val firstID = orderedPeople.firstOrNull()?.id
@@ -129,6 +140,7 @@ fun GeohashPeopleList(
nickname = nickname,
colorScheme = colorScheme,
viewModel = viewModel,
showHashSuffix = (baseNameCounts[com.bitchat.android.ui.splitSuffix(person.displayName).first] ?: 0) > 1,
onTap = {
if (person.id != myHex) {
// TODO: Re-enable when NIP-17 geohash DM issues are fixed
@@ -154,6 +166,7 @@ private fun GeohashPersonItem(
nickname: String,
colorScheme: ColorScheme,
viewModel: ChatViewModel,
showHashSuffix: Boolean,
onTap: () -> Unit
) {
Row(
@@ -198,8 +211,10 @@ private fun GeohashPersonItem(
Spacer(modifier = Modifier.width(8.dp))
// Display name with suffix handling (matches iOS splitSuffix logic)
val (baseName, suffix) = com.bitchat.android.ui.splitSuffix(person.displayName)
// Display name with suffix handling
val (baseNameRaw, suffixRaw) = com.bitchat.android.ui.splitSuffix(person.displayName)
val baseName = truncateNickname(baseNameRaw)
val suffix = if (showHashSuffix) suffixRaw else ""
// Get consistent peer color (matches iOS color assignment exactly)
val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f
@@ -218,7 +233,9 @@ private fun GeohashPersonItem(
fontSize = BASE_FONT_SIZE.sp,
fontWeight = if (isMe) FontWeight.Bold else FontWeight.Normal
),
color = baseColor
color = baseColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Suffix (collision-resistant #abcd) in lighter shade
@@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.text.style.TextOverflow
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
@@ -319,6 +320,52 @@ fun PeopleSection(
.thenBy { (if (it == nickname) "You" else (peerNicknames[it] ?: it)).lowercase() } // Alphabetical
)
// Build a map of base name counts across all people shown in the list (connected + offline + nostr)
val hex64Regex = Regex("^[0-9a-fA-F]{64}$")
// Helper to compute display name used for a given key
fun computeDisplayNameForPeerId(key: String): String {
return if (key == nickname) "You" else (peerNicknames[key] ?: (privateChats[key]?.lastOrNull()?.sender ?: key.take(12)))
}
val baseNameCounts = mutableMapOf<String, Int>()
// Connected peers
sortedPeers.forEach { pid ->
val dn = computeDisplayNameForPeerId(pid)
val (b, _) = com.bitchat.android.ui.splitSuffix(dn)
if (b != "You") baseNameCounts[b] = (baseNameCounts[b] ?: 0) + 1
}
// Offline favorites (exclude ones mapped to connected)
val offlineFavorites = com.bitchat.android.favorites.FavoritesPersistenceService.shared.getOurFavorites()
offlineFavorites.forEach { fav ->
val favPeerID = fav.peerNoisePublicKey.joinToString("") { b -> "%02x".format(b) }
val isMappedToConnected = noiseHexByPeerID.values.any { it.equals(favPeerID, ignoreCase = true) }
if (!isMappedToConnected) {
val dn = peerNicknames[favPeerID] ?: fav.peerNickname
val (b, _) = com.bitchat.android.ui.splitSuffix(dn)
if (b != "You") baseNameCounts[b] = (baseNameCounts[b] ?: 0) + 1
}
}
// Nostr-only conversations
val connectedIds = sortedPeers.toSet()
val appendedOfflineIds = mutableSetOf<String>()
privateChats.keys
.filter { key ->
(key.startsWith("nostr_") || hex64Regex.matches(key)) &&
!connectedIds.contains(key) &&
!noiseHexByPeerID.values.any { it.equals(key, ignoreCase = true) }
}
.forEach { convKey ->
val dn = peerNicknames[convKey] ?: (privateChats[convKey]?.lastOrNull()?.sender ?: convKey.take(12))
val (b, _) = com.bitchat.android.ui.splitSuffix(dn)
if (b != "You") baseNameCounts[b] = (baseNameCounts[b] ?: 0) + 1
}
sortedPeers.forEach { peerID ->
val isFavorite = peerFavoriteStates[peerID] ?: false
// fingerprint and favorite relationship resolution not needed here; UI will show Nostr globe for appended offline favorites below
@@ -333,9 +380,13 @@ fun PeopleSection(
if (noiseHex != null) privateChats[noiseHex]?.count { msg -> msg.sender != nickname && nostrUnread } ?: 0 else 0
)
val displayName = if (peerID == nickname) "You" else (peerNicknames[peerID] ?: (privateChats[peerID]?.lastOrNull()?.sender ?: peerID.take(12)))
val (bName, _) = com.bitchat.android.ui.splitSuffix(displayName)
val showHash = (baseNameCounts[bName] ?: 0) > 1
PeerItem(
peerID = peerID,
displayName = if (peerID == nickname) "You" else (peerNicknames[peerID] ?: (privateChats[peerID]?.lastOrNull()?.sender ?: peerID.take(12))),
displayName = displayName,
signalStrength = convertRSSIToSignalStrength(peerRSSI[peerID]),
isSelected = peerID == selectedPrivatePeer,
isFavorite = isFavorite,
@@ -348,13 +399,12 @@ fun PeopleSection(
viewModel.toggleFavorite(peerID)
},
unreadCount = if (combinedUnreadCount > 0) combinedUnreadCount else if (combinedHasUnread) 1 else 0,
showNostrGlobe = false
showNostrGlobe = false,
showHashSuffix = showHash
)
}
// Append offline favorites we actively favorite (and not currently connected)
val offlineFavorites = com.bitchat.android.favorites.FavoritesPersistenceService.shared.getOurFavorites()
val appendedOfflineIds = mutableSetOf<String>()
offlineFavorites.forEach { fav ->
val favPeerID = fav.peerNoisePublicKey.joinToString("") { b -> "%02x".format(b) }
// If any connected peer maps to this noise key, skip showing the offline entry
@@ -364,9 +414,13 @@ fun PeopleSection(
// If user clicks an offline favorite and the mapped peer is currently connected under a different ID,
// open chat with the connected peerID instead of the noise hex for a seamless window
val mappedConnectedPeerID = noiseHexByPeerID.entries.firstOrNull { it.value.equals(favPeerID, ignoreCase = true) }?.key
val dn = peerNicknames[favPeerID] ?: fav.peerNickname
val (bName, _) = com.bitchat.android.ui.splitSuffix(dn)
val showHash = (baseNameCounts[bName] ?: 0) > 1
PeerItem(
peerID = favPeerID,
displayName = peerNicknames[favPeerID] ?: fav.peerNickname,
displayName = dn,
signalStrength = 0,
isSelected = (mappedConnectedPeerID ?: favPeerID) == selectedPrivatePeer,
isFavorite = true,
@@ -381,16 +435,15 @@ fun PeopleSection(
unreadCount = privateChats[favPeerID]?.count { msg ->
msg.sender != nickname && hasUnreadPrivateMessages.contains(favPeerID)
} ?: if (hasUnreadPrivateMessages.contains(favPeerID)) 1 else 0,
showNostrGlobe = (fav.isMutual && fav.peerNostrPublicKey != null)
showNostrGlobe = (fav.isMutual && fav.peerNostrPublicKey != null),
showHashSuffix = showHash
)
appendedOfflineIds.add(favPeerID)
}
// Also show any incoming Nostr chats that exist locally but are not in connected peers or favorites yet
// This ensures a user can open and read Nostr messages while the sender remains offline
val connectedIds = sortedPeers.toSet()
val alreadyShownIds = connectedIds + appendedOfflineIds
val hex64Regex = Regex("^[0-9a-fA-F]{64}$")
privateChats.keys
.filter { key ->
(key.startsWith("nostr_") || hex64Regex.matches(key)) &&
@@ -401,9 +454,13 @@ fun PeopleSection(
.sortedBy { key -> privateChats[key]?.lastOrNull()?.timestamp }
.forEach { convKey ->
val lastSender = privateChats[convKey]?.lastOrNull()?.sender
val dn = peerNicknames[convKey] ?: (lastSender ?: convKey.take(12))
val (bName, _) = com.bitchat.android.ui.splitSuffix(dn)
val showHash = (baseNameCounts[bName] ?: 0) > 1
PeerItem(
peerID = convKey,
displayName = peerNicknames[convKey] ?: (lastSender ?: convKey.take(12)),
displayName = dn,
signalStrength = 0,
isSelected = convKey == selectedPrivatePeer,
isFavorite = false,
@@ -415,7 +472,8 @@ fun PeopleSection(
unreadCount = privateChats[convKey]?.count { msg ->
msg.sender != nickname && hasUnreadPrivateMessages.contains(convKey)
} ?: if (hasUnreadPrivateMessages.contains(convKey)) 1 else 0,
showNostrGlobe = true
showNostrGlobe = true,
showHashSuffix = showHash
)
}
}
@@ -434,10 +492,13 @@ private fun PeerItem(
onItemClick: () -> Unit,
onToggleFavorite: () -> Unit,
unreadCount: Int = 0,
showNostrGlobe: Boolean = false
showNostrGlobe: Boolean = false,
showHashSuffix: Boolean = true
) {
// Split display name for hashtag suffix support (iOS-compatible)
val (baseName, suffix) = com.bitchat.android.ui.splitSuffix(displayName)
val (baseNameRaw, suffixRaw) = com.bitchat.android.ui.splitSuffix(displayName)
val baseName = truncateNickname(baseNameRaw)
val suffix = if (showHashSuffix) suffixRaw else ""
val isMe = displayName == "You" || peerID == viewModel.nickname.value
// Get consistent peer color (iOS-compatible)
@@ -498,7 +559,9 @@ private fun PeerItem(
fontSize = BASE_FONT_SIZE.sp,
fontWeight = if (isMe) FontWeight.Bold else FontWeight.Normal
),
color = baseColor
color = baseColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Hashtag suffix in lighter shade (iOS-style)