From 862fd8449db3c3bbae6c7704bc9a77391ed68aee Mon Sep 17 00:00:00 2001 From: heyaim <223061694+heyaim@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:05:23 -0500 Subject: [PATCH] Hide the command and mention popups after a message is sent When a slash command is sent without tapping the autocomplete list, the command popup stays on screen with its old suggestions. The send path clears the input field in code, and the popup is hidden only from the field's text-change handler, which a code-driven clear does not run, so the show flag is never turned off. The mention popup has the same cause. CommandProcessor gains clearSuggestions(), which hides both popups and empties their lists, the same resets the two select functions already do inline. ChatScreen's send handler calls it where it already resets the field. The private chat sheet called updateMentionSuggestions on every keystroke while rendering its own popups as hidden, so the only effect was on the main composer behind it: typing @ in the sheet and then sending or dismissing left a stale mention popup on the main screen. That call is removed. Tests in CommandProcessorTest open each popup and assert clearSuggestions hides it. The unit tests do not drive Compose, so they cover the function the send handler calls rather than the handler itself. Full Android unit suite green; lint and the debug build pass. Fixes #250. --- .../java/com/bitchat/android/ui/ChatScreen.kt | 3 +++ .../com/bitchat/android/ui/ChatViewModel.kt | 6 ++++- .../bitchat/android/ui/CommandProcessor.kt | 12 +++++++++ .../bitchat/android/ui/MeshPeerListSheet.kt | 4 ++- .../android/ui/CommandProcessorTest.kt | 27 +++++++++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) 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 301be917..d462f353 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt @@ -386,6 +386,9 @@ fun ChatScreen(viewModel: ChatViewModel) { if (accepted) { messageText = TextFieldValue("") viewModel.setConversationDraft(selectedPrivatePeer, "") + // Clearing the field in code does not run onMessageTextChange, + // so the popups have to be dismissed here. + viewModel.clearSuggestions() forceScrollToBottom = !forceScrollToBottom } } 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 16933537..eac81cde 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt @@ -1341,7 +1341,11 @@ class ChatViewModel( fun updateCommandSuggestions(input: String) { commandProcessor.updateCommandSuggestions(input) } - + + fun clearSuggestions() { + commandProcessor.clearSuggestions() + } + fun selectCommandSuggestion(suggestion: CommandSuggestion): String { return commandProcessor.selectCommandSuggestion(suggestion) } 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 b10ac9e1..1cccfb19 100644 --- a/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt +++ b/app/src/main/java/com/bitchat/android/ui/CommandProcessor.kt @@ -465,6 +465,18 @@ class CommandProcessor( messageManager.addMessage(systemMessage) } + /** + * Dismiss the command and mention popups. The composer clears its field in code + * after a send, which does not run onValueChange, so the popups need an explicit + * clear. + */ + fun clearSuggestions() { + state.setShowCommandSuggestions(false) + state.setCommandSuggestions(emptyList()) + state.setShowMentionSuggestions(false) + state.setMentionSuggestions(emptyList()) + } + // MARK: - Command Autocomplete fun updateCommandSuggestions(input: String) { 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 93b364e2..b4dc2680 100644 --- a/app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt @@ -1849,7 +1849,9 @@ fun PrivateChatSheet( onMessageTextChange = { newText -> messageText = newText viewModel.setConversationDraft(peerID, newText.text) - viewModel.updateMentionSuggestions(newText.text) + // Do not update the shared suggestion state here: this sheet + // renders its own popups as hidden, so an update only leaves + // a stale popup behind for the main composer. }, onSend = { if (messageText.text.trim().isNotEmpty()) { diff --git a/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt b/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt index 011f80a2..36c68429 100644 --- a/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt +++ b/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt @@ -8,6 +8,7 @@ import com.bitchat.android.geohash.GeohashChannelLevel import com.bitchat.android.mesh.MeshService import com.bitchat.android.model.BitchatMessage import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -161,4 +162,30 @@ class CommandProcessorTest() { ) assertEquals(0, chatState.getMessagesValue().size) } + + @Test + fun `clearSuggestions hides the command suggestion popup`() { + // Typing "/" opens the command popup. + commandProcessor.updateCommandSuggestions("/") + assertTrue(chatState.getShowCommandSuggestionsValue()) + + // The send handlers call this after clearing the field in code, which does + // not run the text-change handler that normally hides the popup. + commandProcessor.clearSuggestions() + assertFalse(chatState.getShowCommandSuggestionsValue()) + assertTrue(chatState.getCommandSuggestionsValue().isEmpty()) + } + + @Test + fun `clearSuggestions hides the mention suggestion popup`() { + whenever(meshService.getPeerNicknames()).thenReturn(mapOf("peer-1" to "alice")) + + // Typing "@a" opens the mention popup. + commandProcessor.updateMentionSuggestions("@a", meshService, viewModel = null) + assertTrue(chatState.getShowMentionSuggestionsValue()) + + commandProcessor.clearSuggestions() + assertFalse(chatState.getShowMentionSuggestionsValue()) + assertTrue(chatState.getMentionSuggestionsValue().isEmpty()) + } }