From 758e6031d156dfe6348101ba7aaa66edf82505d4 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 f40549b6f..20e6544fe 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 1cccfb193..ab0b33a05 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 170e7339a1d39c844b0406638dd808fecac0b8be 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 607aaf1e3..d89aaaead 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 f3bf50dc39ba127186291649ec41b7c9d689fcac 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 ab0b33a05..b811c9c87 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 ) }