From 8294887398978f98534b2f76be3064e130ed53c5 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 4 Sep 2026 22:11:09 +0530 Subject: [PATCH 1/3] Don't suggest blocked peers in @-mention autocomplete Blocking someone and still seeing them in the mention list felt wrong. Skip mesh peers whose fingerprint is blocked and geohash people whose Nostr pubkey is blocked, matching iOS ChatComposerCoordinator. --- .../com/bitchat/android/ui/ChatViewModel.kt | 5 ++ .../bitchat/android/ui/CommandProcessor.kt | 49 ++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) 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 f40549b6..20e6544f 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt @@ -1675,6 +1675,11 @@ class ChatViewModel( geohashViewModel.blockUserInGeohash(targetNickname) } + /** True when this Nostr pubkey is blocked for geohash/location chats. */ + fun isGeohashPubkeyBlocked(pubkeyHex: String): Boolean { + return dataManager.isGeohashUserBlocked(pubkeyHex.lowercase()) + } + // MARK: - Navigation Management fun showAppInfo() { 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 1cccfb19..ab0b33a0 100644 --- a/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt +++ b/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt @@ -562,14 +562,20 @@ class CommandProcessor( when (val selectedChannel = viewModel.selectedLocationChannel.value) { is com.bitchat.android.geohash.ChannelID.Mesh, null -> { - // Mesh channel: use Bluetooth mesh peer nicknames - val peerNicknames = meshService.getPeerNicknames() - peerNicknames.values.filter { it != peerNicknames[meshService.myPeerID] } + meshMentionCandidates( + peerNicknames = meshService.getPeerNicknames(), + myPeerID = meshService.myPeerID, + isPeerBlocked = privateChatManager::isPeerBlocked + ) } is com.bitchat.android.geohash.ChannelID.Location -> { // Location channel: use geohash participants with collision-resistant suffixes - val geohashPeople = viewModel.geohashPeople.value + val geohashPeople = excludeBlockedGeohashPeople( + people = viewModel.geohashPeople.value, + pubkeyOf = { it.id }, + isBlocked = viewModel::isGeohashPubkeyBlocked + ) val currentNickname = state.getNicknameValue() val duplicateNames = duplicateGeohashBaseNames(geohashPeople) @@ -593,9 +599,11 @@ class CommandProcessor( } } } else { - // Fallback to mesh peers if no viewModel available - val peerNicknames = meshService.getPeerNicknames() - peerNicknames.values.filter { it != peerNicknames[meshService.myPeerID] } + meshMentionCandidates( + peerNicknames = meshService.getPeerNicknames(), + myPeerID = meshService.myPeerID, + isPeerBlocked = privateChatManager::isPeerBlocked + ) } val filteredNicknames = filterMentionCandidates(peerCandidates, textAfterAt) @@ -719,3 +727,30 @@ internal fun filterMentionCandidates( .sortedWith(String.CASE_INSENSITIVE_ORDER) .toList() } + +/** + * Mesh @-mention candidates excluding self and blocked peers (iOS ChatComposerCoordinator parity). + */ +internal fun meshMentionCandidates( + peerNicknames: Map, + myPeerID: String, + isPeerBlocked: (String) -> Boolean +): List { + val myNickname = peerNicknames[myPeerID] + return peerNicknames + .filter { (peerID, nick) -> + nick != myNickname && !isPeerBlocked(peerID) + } + .values + .toList() +} + +/** + * Drop geohash people whose Nostr pubkey is blocked before building mention tokens. + */ +internal fun excludeBlockedGeohashPeople( + people: List, + pubkeyOf: (T) -> String, + isBlocked: (String) -> Boolean +): List = people.filterNot { isBlocked(pubkeyOf(it)) } + From 67dd9dcafc1226e95447715f0130cc786916fcb0 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 4 Sep 2026 22:11:50 +0530 Subject: [PATCH 2/3] Cover blocked-peer exclusion in mention candidate helpers --- .../android/ui/MentionSuggestionsTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/src/test/java/com/bitchat/android/ui/MentionSuggestionsTest.kt b/app/src/test/java/com/bitchat/android/ui/MentionSuggestionsTest.kt index 607aaf1e..d89aaaea 100644 --- a/app/src/test/java/com/bitchat/android/ui/MentionSuggestionsTest.kt +++ b/app/src/test/java/com/bitchat/android/ui/MentionSuggestionsTest.kt @@ -51,4 +51,34 @@ class MentionSuggestionsTest { fun `mention popup viewport is capped at five rows`() { assertEquals(5, MaxVisibleMentionSuggestions) } + + @Test + fun `mesh mention candidates exclude self and blocked peers`() { + val nicknames = mapOf( + "aaaa" to "me", + "bbbb" to "alice", + "cccc" to "eve" + ) + val candidates = meshMentionCandidates( + peerNicknames = nicknames, + myPeerID = "aaaa", + isPeerBlocked = { it == "cccc" } + ) + assertEquals(listOf("alice"), candidates) + } + + @Test + fun `geohash mention candidates exclude blocked pubkeys`() { + data class Person(val id: String, val name: String) + val people = listOf( + Person("aaaabbbb", "carol"), + Person("bbbbcccc", "blocked") + ) + val kept = excludeBlockedGeohashPeople( + people = people, + pubkeyOf = { it.id }, + isBlocked = { it == "bbbbcccc" } + ) + assertEquals(listOf("carol"), kept.map { it.name }) + } } From 77646fbae916473523e75d97707d7eb4b29aa6cf Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 4 Sep 2026 22:27:04 +0530 Subject: [PATCH 3/3] Tolerate a null mesh myPeerID when building mention candidates CommandProcessorTest mocks MeshService without stubbing myPeerID. Pass orEmpty() so the helper does not NPE on that path. --- app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ab0b33a0..b811c9c8 100644 --- a/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt +++ b/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt @@ -564,7 +564,7 @@ class CommandProcessor( null -> { meshMentionCandidates( peerNicknames = meshService.getPeerNicknames(), - myPeerID = meshService.myPeerID, + myPeerID = meshService.myPeerID.orEmpty(), isPeerBlocked = privateChatManager::isPeerBlocked ) } @@ -601,7 +601,7 @@ class CommandProcessor( } else { meshMentionCandidates( peerNicknames = meshService.getPeerNicknames(), - myPeerID = meshService.myPeerID, + myPeerID = meshService.myPeerID.orEmpty(), isPeerBlocked = privateChatManager::isPeerBlocked ) }