From a81bf4489d8301a6ab21c0bfe3a461bc62abfb07 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Jan 2026 01:19:48 +0700 Subject: [PATCH] wip: nickname disambiguation --- .../android/mesh/BluetoothMeshService.kt | 17 ++ .../com/bitchat/android/mesh/PeerManager.kt | 25 +-- .../android/nostr/GeohashRepository.kt | 23 ++- .../java/com/bitchat/android/ui/ChatScreen.kt | 10 +- .../com/bitchat/android/ui/ChatUIUtils.kt | 38 +++- .../com/bitchat/android/ui/ChatViewModel.kt | 8 +- .../bitchat/android/ui/CommandProcessor.kt | 171 ++++++++++++++---- .../bitchat/android/ui/MeshDelegateHandler.kt | 9 +- .../bitchat/android/ui/MeshPeerListSheet.kt | 7 +- .../bitchat/android/ui/PrivateChatManager.kt | 56 +----- 10 files changed, 231 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index 474c04a5..55ee7089 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -636,6 +636,10 @@ class BluetoothMeshService(private val context: Context) { if (connectionManager.startServices()) { isActive = true + // Add ourselves to peer manager for collision detection + val myNickname = try { com.bitchat.android.services.NicknameProvider.getNickname(context, myPeerID) } catch (_: Exception) { myPeerID } + peerManager.addOrUpdatePeer(myPeerID, myNickname) + // Start periodic announcements for peer discovery and connectivity sendPeriodicBroadcastAnnounce() Log.d(TAG, "Started periodic broadcast announcements (every 30 seconds)") @@ -697,6 +701,14 @@ class BluetoothMeshService(private val context: Context) { return reusable } + /** + * Update our own nickname in peer manager and announce it + */ + fun updateSelfNickname(newNickname: String) { + peerManager.addOrUpdatePeer(myPeerID, newNickname) + sendBroadcastAnnounce() + } + /** * Send public message */ @@ -1175,6 +1187,11 @@ class BluetoothMeshService(private val context: Context) { * Get peer nicknames */ fun getPeerNicknames(): Map = peerManager.getAllPeerNicknames() + + /** + * Get disambiguated peer nickname (nickname#suffix if collisions exist) + */ + fun getDisambiguatedNickname(peerID: String): String = peerManager.getDisambiguatedNickname(peerID) /** * Get peer RSSI values diff --git a/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt b/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt index 536a9b2a..a1962622 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt @@ -207,22 +207,7 @@ class PeerManager { fun addOrUpdatePeer(peerID: String, nickname: String): Boolean { if (peerID == "unknown") return false - // Clean up stale peer IDs with the same nickname (exact same logic as iOS) val now = System.currentTimeMillis() - val stalePeerIDs = mutableListOf() - peers.forEach { (existingPeerID, info) -> - if (info.nickname == nickname && existingPeerID != peerID) { - val wasRecentlySeen = (now - info.lastSeen) < 10000 - if (!wasRecentlySeen) { - stalePeerIDs.add(existingPeerID) - } - } - } - - // Remove stale peer IDs - stalePeerIDs.forEach { stalePeerID -> - removePeer(stalePeerID, notifyDelegate = false) - } // Check if this is a new peer announcement val isFirstAnnounce = !announcedPeers.contains(peerID) @@ -313,6 +298,16 @@ class PeerManager { return peers[peerID]?.nickname } + /** + * Get disambiguated peer nickname (nickname#suffix if collisions exist) + */ + fun getDisambiguatedNickname(peerID: String): String { + val info = peers[peerID] ?: return peerID + val nick = info.nickname.trim() + val isAmbiguous = peers.values.count { it.nickname.trim().equals(nick, ignoreCase = true) } > 1 + return if (isAmbiguous) "$nick#${peerID.takeLast(4)}" else nick + } + /** * Get all peer nicknames */ diff --git a/app/src/main/java/com/bitchat/android/nostr/GeohashRepository.kt b/app/src/main/java/com/bitchat/android/nostr/GeohashRepository.kt index 822606c2..e3753668 100644 --- a/app/src/main/java/com/bitchat/android/nostr/GeohashRepository.kt +++ b/app/src/main/java/com/bitchat/android/nostr/GeohashRepository.kt @@ -37,10 +37,25 @@ class GeohashRepository( fun getConversationGeohash(convKey: String): String? = conversationGeohash[convKey] fun findPubkeyByNickname(targetNickname: String): String? { - return geoNicknames.entries.firstOrNull { (_, nickname) -> - val base = nickname.split("#").firstOrNull() ?: nickname - base == targetNickname - }?.key + val parts = targetNickname.split("#") + val baseName = parts[0] + val suffix = if (parts.size > 1) parts[1] else null + + if (suffix != null) { + // Exact match attempt: nickname + pubkey suffix + return geoNicknames.entries.find { (id, nick) -> + val base = nick.split("#").firstOrNull() ?: nick + base.equals(baseName, ignoreCase = true) && id.endsWith(suffix, ignoreCase = true) + }?.key + } else { + // No suffix provided. Check for matches. + val matches = geoNicknames.entries.filter { (_, nick) -> + val base = nick.split("#").firstOrNull() ?: nick + base.equals(baseName, ignoreCase = true) + } + // If only one match, return it. If multiple, it's ambiguous (return null or handle) + return if (matches.size == 1) matches.first().key else null + } } // peerID alias -> nostr pubkey mapping for geohash DMs and temp aliases diff --git a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt index 0d22a0d5..a5a02541 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt @@ -167,9 +167,13 @@ fun ChatScreen(viewModel: ChatViewModel) { }, onMessageLongPress = { message -> // Message long press - open user action sheet with message context - // Extract base nickname from message sender (contains all necessary info) - val (baseName, _) = splitSuffix(message.sender) - selectedUserForSheet = baseName + // Use disambiguated name so actions like /block work with suffixes if collisions exist + val disambiguated = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) { + viewModel.meshService.getDisambiguatedNickname(message.senderPeerID) + } else { + message.sender + } + selectedUserForSheet = disambiguated selectedMessageForSheet = message showUserSheet = true }, 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 82db6b64..5e12a83f 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt @@ -53,8 +53,21 @@ fun formatMessageAsAnnotatedString( val isSelf = message.senderPeerID == meshService.myPeerID || message.sender == currentUserNickname || message.sender.startsWith("$currentUserNickname#") - if (message.sender != "system") { + // Resolve disambiguated sender name if it's a mesh message + val senderDisplayName = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) { + 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 (disambiguated == message.senderPeerID && message.sender != message.senderPeerID) { + message.sender + } else { + disambiguated + } + } else { + message.sender + } + + // Get base color for this peer (iOS-style color assignment) val baseColor = if (isSelf) { Color(0xFFFF9500) // Orange for self (iOS orange) @@ -63,7 +76,7 @@ fun formatMessageAsAnnotatedString( } // Split sender into base name and hashtag suffix - val (baseName, suffix) = splitSuffix(message.sender) + val (baseName, suffix) = splitSuffix(senderDisplayName) // Sender prefix "<@" builder.pushStyle(SpanStyle( @@ -85,11 +98,11 @@ fun formatMessageAsAnnotatedString( builder.append(truncatedBase) val nicknameEnd = builder.length - // Add click annotation for nickname (store canonical sender name with hash if available) + // Add click annotation for nickname (store disambiguated name for mentions) if (!isSelf) { builder.addStringAnnotation( tag = "nickname_click", - annotation = (message.originalSender ?: message.sender), + annotation = senderDisplayName, start = nicknameStart, end = nicknameEnd ) @@ -174,8 +187,21 @@ fun formatMessageHeaderAnnotatedString( message.sender.startsWith("$currentUserNickname#") if (message.sender != "system") { + // Resolve disambiguated sender name if it's a mesh message + val senderDisplayName = if (message.senderPeerID != null && !message.senderPeerID.startsWith("nostr_")) { + 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 (disambiguated == message.senderPeerID && message.sender != message.senderPeerID) { + message.sender + } else { + disambiguated + } + } else { + message.sender + } + val baseColor = if (isSelf) Color(0xFFFF9500) else getPeerColor(message, isDark) - val (baseName, suffix) = splitSuffix(message.sender) + val (baseName, suffix) = splitSuffix(senderDisplayName) // "<@" builder.pushStyle(SpanStyle( @@ -198,7 +224,7 @@ fun formatMessageHeaderAnnotatedString( if (!isSelf) { builder.addStringAnnotation( tag = "nickname_click", - annotation = (message.originalSender ?: message.sender), + annotation = senderDisplayName, start = nicknameStart, end = nicknameEnd ) diff --git a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt index 5456ca4d..733dbdd1 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt @@ -317,7 +317,7 @@ class ChatViewModel( fun setNickname(newNickname: String) { state.setNickname(newNickname) dataManager.saveNickname(newNickname) - meshService.sendBroadcastAnnounce() + meshService.updateSelfNickname(newNickname) } /** @@ -519,7 +519,7 @@ class ChatViewModel( } } // Send private message - val recipientNickname = meshService.getPeerNicknames()[selectedPeer] + val recipientNickname = meshService.getDisambiguatedNickname(selectedPeer) privateChatManager.sendPrivateMessage( content, selectedPeer, @@ -581,10 +581,6 @@ class ChatViewModel( // MARK: - Utility Functions - fun getPeerIDForNickname(nickname: String): String? { - return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key - } - fun toggleFavorite(peerID: String) { Log.d("ChatViewModel", "toggleFavorite called for peerID: $peerID") privateChatManager.toggleFavorite(peerID) diff --git a/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt b/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt index 499b3926..b540f182 100644 --- a/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt +++ b/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt @@ -80,43 +80,56 @@ class CommandProcessor( private fun handleMessageCommand(parts: List, meshService: BluetoothMeshService) { if (parts.size > 1) { val targetName = parts[1].removePrefix("@") - val peerID = getPeerIDForNickname(targetName, meshService) + val resolution = resolvePeerIDForNickname(targetName, meshService) - if (peerID != null) { - val success = privateChatManager.startPrivateChat(peerID, meshService) - - if (success) { - if (parts.size > 2) { - val messageContent = parts.drop(2).joinToString(" ") - val recipientNickname = getPeerNickname(peerID, meshService) - privateChatManager.sendPrivateMessage( - messageContent, - peerID, - recipientNickname, - state.getNicknameValue(), - getMyPeerID(meshService) - ) { content, peerIdParam, recipientNicknameParam, messageId -> - // This would trigger the actual mesh service send - sendPrivateMessageVia(meshService, content, peerIdParam, recipientNicknameParam, messageId) + when (resolution) { + is PeerResolutionResult.Found -> { + val peerID = resolution.peerID + val success = privateChatManager.startPrivateChat(peerID, meshService) + + if (success) { + if (parts.size > 2) { + val messageContent = parts.drop(2).joinToString(" ") + val recipientNickname = getPeerNickname(peerID, meshService) + privateChatManager.sendPrivateMessage( + messageContent, + peerID, + recipientNickname, + state.getNicknameValue(), + getMyPeerID(meshService) + ) { content, peerIdParam, recipientNicknameParam, messageId -> + // This would trigger the actual mesh service send + sendPrivateMessageVia(meshService, content, peerIdParam, recipientNicknameParam, messageId) + } + } else { + val systemMessage = BitchatMessage( + sender = "system", + content = "started private chat with $targetName", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) } - } else { - val systemMessage = BitchatMessage( - sender = "system", - content = "started private chat with $targetName", - timestamp = Date(), - isRelay = false - ) - messageManager.addMessage(systemMessage) } } - } else { - val systemMessage = BitchatMessage( - sender = "system", - content = "user '$targetName' not found. they may be offline or using a different nickname.", - timestamp = Date(), - isRelay = false - ) - messageManager.addMessage(systemMessage) + is PeerResolutionResult.Ambiguous -> { + val systemMessage = BitchatMessage( + sender = "system", + content = "multiple users found with nickname '$targetName'. please use one of: ${resolution.candidates.joinToString(", ")}", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } + is PeerResolutionResult.NotFound -> { + val systemMessage = BitchatMessage( + sender = "system", + content = "user '$targetName' not found. they may be offline or using a different nickname.", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } } } else { val systemMessage = BitchatMessage( @@ -251,7 +264,31 @@ class CommandProcessor( private fun handleBlockCommand(parts: List, meshService: BluetoothMeshService) { if (parts.size > 1) { val targetName = parts[1].removePrefix("@") - privateChatManager.blockPeerByNickname(targetName, meshService) + val resolution = resolvePeerIDForNickname(targetName, meshService) + + when (resolution) { + is PeerResolutionResult.Found -> { + privateChatManager.blockPeer(resolution.peerID, meshService) + } + is PeerResolutionResult.Ambiguous -> { + val systemMessage = BitchatMessage( + sender = "system", + content = "multiple users found with nickname '$targetName'. please use one of: ${resolution.candidates.joinToString(", ")}", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } + is PeerResolutionResult.NotFound -> { + val systemMessage = BitchatMessage( + sender = "system", + content = "user '$targetName' not found.", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } + } } else { // List blocked users val blockedInfo = privateChatManager.listBlockedUsers() @@ -268,7 +305,31 @@ class CommandProcessor( private fun handleUnblockCommand(parts: List, meshService: BluetoothMeshService) { if (parts.size > 1) { val targetName = parts[1].removePrefix("@") - privateChatManager.unblockPeerByNickname(targetName, meshService) + val resolution = resolvePeerIDForNickname(targetName, meshService) + + when (resolution) { + is PeerResolutionResult.Found -> { + privateChatManager.unblockPeer(resolution.peerID, meshService) + } + is PeerResolutionResult.Ambiguous -> { + val systemMessage = BitchatMessage( + sender = "system", + content = "multiple users found with nickname '$targetName'. please use one of: ${resolution.candidates.joinToString(", ")}", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } + is PeerResolutionResult.NotFound -> { + val systemMessage = BitchatMessage( + sender = "system", + content = "user '$targetName' not found.", + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } + } } else { val systemMessage = BitchatMessage( sender = "system", @@ -448,7 +509,12 @@ class CommandProcessor( is com.bitchat.android.geohash.ChannelID.Mesh, null -> { // Mesh channel: use Bluetooth mesh peer nicknames - meshService.getPeerNicknames().values.filter { it != meshService.getPeerNicknames()[meshService.myPeerID] } + val nicknames = meshService.getPeerNicknames().toMap() // Take a stable snapshot + val myPeerID = meshService.myPeerID + nicknames.filter { it.key != myPeerID }.map { (id, nick) -> + val isAmbiguous = nicknames.values.count { it == nick } > 1 + if (isAmbiguous) "$nick#${id.takeLast(4)}" else nick + } } is com.bitchat.android.geohash.ChannelID.Location -> { @@ -503,12 +569,39 @@ class CommandProcessor( // MARK: - Utility Functions - private fun getPeerIDForNickname(nickname: String, meshService: BluetoothMeshService): String? { - return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key + private sealed class PeerResolutionResult { + data class Found(val peerID: String) : PeerResolutionResult() + object NotFound : PeerResolutionResult() + data class Ambiguous(val candidates: List) : PeerResolutionResult() + } + + private fun resolvePeerIDForNickname(targetName: String, meshService: BluetoothMeshService): PeerResolutionResult { + val nicknames = meshService.getPeerNicknames() + + val parts = targetName.split("#") + val baseName = parts[0] + val suffix = if (parts.size > 1) parts[1] else null + + if (suffix != null) { + val peerID = nicknames.entries.find { (id, nick) -> + nick.equals(baseName, ignoreCase = true) && id.takeLast(4).equals(suffix, ignoreCase = true) + }?.key + return if (peerID != null) PeerResolutionResult.Found(peerID) else PeerResolutionResult.NotFound + } else { + val matches = nicknames.entries.filter { it.value.equals(baseName, ignoreCase = true) } + return when { + matches.isEmpty() -> PeerResolutionResult.NotFound + matches.size == 1 -> PeerResolutionResult.Found(matches.first().key) + else -> { + val candidates = matches.map { "${it.value}#${it.key.takeLast(4)}" } + PeerResolutionResult.Ambiguous(candidates) + } + } + } } private fun getPeerNickname(peerID: String, meshService: BluetoothMeshService): String { - return meshService.getPeerNicknames()[peerID] ?: peerID + return meshService.getDisambiguatedNickname(peerID) } private fun getMyPeerID(meshService: BluetoothMeshService): String { diff --git a/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt b/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt index d27719c7..6162a999 100644 --- a/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt +++ b/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt @@ -54,8 +54,8 @@ class MeshDelegateHandler( // Show notification with enhanced information - now includes senderPeerID message.senderPeerID?.let { senderPeerID -> - // Use nickname if available, fall back to sender or senderPeerID - val senderNickname = message.sender.takeIf { it != senderPeerID } ?: senderPeerID + // Use disambiguated nickname for notifications + val senderNickname = getMeshService().getDisambiguatedNickname(senderPeerID) val preview = NotificationTextUtils.buildPrivateMessagePreview(message) notificationManager.showPrivateMessageNotification( senderPeerID = senderPeerID, @@ -250,10 +250,11 @@ class MeshDelegateHandler( val isMention = checkForMeshMention(message.content, currentNickname) if (isMention) { - android.util.Log.d("MeshDelegateHandler", "🔔 Triggering mesh mention notification from ${message.sender}") + val senderNickname = message.senderPeerID?.let { getMeshService().getDisambiguatedNickname(it) } ?: message.sender + android.util.Log.d("MeshDelegateHandler", "🔔 Triggering mesh mention notification from $senderNickname") notificationManager.showMeshMentionNotification( - senderNickname = message.sender, + senderNickname = senderNickname, messageContent = message.content, senderPeerID = message.senderPeerID ) diff --git a/app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt b/app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt index e5688a4e..055a0b26 100644 --- a/app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt @@ -34,6 +34,7 @@ import com.bitchat.android.geohash.ChannelID import com.bitchat.android.ui.theme.BASE_FONT_SIZE import com.bitchat.android.nostr.GeohashAliasRegistry import com.bitchat.android.nostr.GeohashConversationRegistry +import com.bitchat.android.util.toHexString /** @@ -367,7 +368,7 @@ fun PeopleSection( // 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 favPeerID = fav.peerNoisePublicKey.toHexString() val isMappedToConnected = noiseHexByPeerID.values.any { it.equals(favPeerID, ignoreCase = true) } if (!isMappedToConnected) { val dn = peerNicknames[favPeerID] ?: fav.peerNickname @@ -435,7 +436,7 @@ fun PeopleSection( // Append offline favorites we actively favorite (and not currently connected) offlineFavorites.forEach { fav -> - val favPeerID = fav.peerNoisePublicKey.joinToString("") { b -> "%02x".format(b) } + val favPeerID = fav.peerNoisePublicKey.toHexString() // If any connected peer maps to this noise key, skip showing the offline entry val isMappedToConnected = noiseHexByPeerID.values.any { it.equals(favPeerID, ignoreCase = true) } if (isMappedToConnected) return@forEach @@ -560,7 +561,7 @@ private fun PeerItem( // Split display name for hashtag suffix support (iOS-compatible) val (baseNameRaw, suffixRaw) = splitSuffix(displayName) val baseName = truncateNickname(baseNameRaw) - val suffix = if (showHashSuffix) suffixRaw else "" + val suffix = if (showHashSuffix && suffixRaw.isEmpty()) "#${peerID.takeLast(4)}" else if (showHashSuffix) suffixRaw else "" val isMe = displayName == "You" || peerID == currentNickname // Get consistent peer color (iOS-compatible) diff --git a/app/src/main/java/com/bitchat/android/ui/PrivateChatManager.kt b/app/src/main/java/com/bitchat/android/ui/PrivateChatManager.kt index af2882bb..b5ffbbb9 100644 --- a/app/src/main/java/com/bitchat/android/ui/PrivateChatManager.kt +++ b/app/src/main/java/com/bitchat/android/ui/PrivateChatManager.kt @@ -193,7 +193,7 @@ class PrivateChatManager( if (fingerprint != null) { dataManager.addBlockedUser(fingerprint) - val peerNickname = getPeerNickname(peerID, meshService) + val peerNickname = meshService.getDisambiguatedNickname(peerID) val systemMessage = BitchatMessage( sender = "system", content = "blocked user $peerNickname", @@ -217,7 +217,7 @@ class PrivateChatManager( if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) { dataManager.removeBlockedUser(fingerprint) - val peerNickname = getPeerNickname(peerID, meshService) + val peerNickname = meshService.getDisambiguatedNickname(peerID) val systemMessage = BitchatMessage( sender = "system", content = "unblocked user $peerNickname", @@ -230,52 +230,6 @@ class PrivateChatManager( return false } - fun blockPeerByNickname(targetName: String, meshService: BluetoothMeshService): Boolean { - val peerID = getPeerIDForNickname(targetName, meshService) - - if (peerID != null) { - return blockPeer(peerID, meshService) - } else { - val systemMessage = BitchatMessage( - sender = "system", - content = "user '$targetName' not found", - timestamp = Date(), - isRelay = false - ) - messageManager.addMessage(systemMessage) - return false - } - } - - fun unblockPeerByNickname(targetName: String, meshService: BluetoothMeshService): Boolean { - val peerID = getPeerIDForNickname(targetName, meshService) - - if (peerID != null) { - val fingerprint = fingerprintManager.getFingerprintForPeer(peerID) - if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) { - return unblockPeer(peerID, meshService) - } else { - val systemMessage = BitchatMessage( - sender = "system", - content = "user '$targetName' is not blocked", - timestamp = Date(), - isRelay = false - ) - messageManager.addMessage(systemMessage) - return false - } - } else { - val systemMessage = BitchatMessage( - sender = "system", - content = "user '$targetName' not found", - timestamp = Date(), - isRelay = false - ) - messageManager.addMessage(systemMessage) - return false - } - } - fun listBlockedUsers(): String { val blockedCount = dataManager.blockedUsers.size return if (blockedCount == 0) { @@ -413,12 +367,8 @@ class PrivateChatManager( // MARK: - Utility Functions - private fun getPeerIDForNickname(nickname: String, meshService: BluetoothMeshService): String? { - return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key - } - private fun getPeerNickname(peerID: String, meshService: BluetoothMeshService): String { - return meshService.getPeerNicknames()[peerID] ?: peerID + return meshService.getDisambiguatedNickname(peerID) } // MARK: - Consolidation