From e8a9e819a89a066cdebf08f6f11bada7435fcaa8 Mon Sep 17 00:00:00 2001 From: heyaim <223061694+heyaim@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:29:30 -0500 Subject: [PATCH] Persist the draft and re-sync popups at every programmatic-edit site Review follow-up. The nickname tap wrote the field in code but never wrote the draft, unlike onMessageTextChange, so switching conversations restored the field without the tapped-in mention. The two suggestion click handlers skipped the draft write the same way, and also set the field without re-syncing the popups. All three sites now run the same block a keyboard edit runs. A new CommandProcessorTest case pins that command suggestions close once the input stops prefixing a command name or alias, which is why the two click sites had no user-visible popup repro: an input that can match the mention popup has already closed the command popup. --- .../java/com/bitchat/android/ui/ChatScreen.kt | 15 +++++++++++++++ .../bitchat/android/ui/CommandProcessorTest.kt | 14 ++++++++++++++ 2 files changed, 29 insertions(+) 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 fc4f4ccf..4b81ed65 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt @@ -348,6 +348,9 @@ fun ChatScreen(viewModel: ChatViewModel) { // Setting the field in code does not run onMessageTextChange, so // the popups have to be re-synced with the new text here. The // inserted mention ends with a space, which hides an open popup. + // The draft write is skipped the same way; without it, switching + // conversations restores the field without the tapped-in mention. + viewModel.setConversationDraft(selectedPrivatePeer, newText) viewModel.updateCommandSuggestions(newText) viewModel.updateMentionSuggestions(newText) }, @@ -439,6 +442,12 @@ fun ChatScreen(viewModel: ChatViewModel) { text = commandText, selection = TextRange(commandText.length) ) + // A code-driven edit skips onMessageTextChange: persist the + // draft and re-sync both popups, same as the other + // programmatic-edit sites. + viewModel.setConversationDraft(selectedPrivatePeer, commandText) + viewModel.updateCommandSuggestions(commandText) + viewModel.updateMentionSuggestions(commandText) }, onMentionSuggestionClick = { mention: String -> val mentionText = viewModel.selectMentionSuggestion(mention, messageText.text) @@ -446,6 +455,12 @@ fun ChatScreen(viewModel: ChatViewModel) { text = mentionText, selection = TextRange(mentionText.length) ) + // A code-driven edit skips onMessageTextChange: persist the + // draft and re-sync both popups, same as the other + // programmatic-edit sites. + viewModel.setConversationDraft(selectedPrivatePeer, mentionText) + viewModel.updateCommandSuggestions(mentionText) + viewModel.updateMentionSuggestions(mentionText) }, selectedPrivatePeer = null, currentChannel = currentChannel, 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 16ca3f16..e46fd927 100644 --- a/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt +++ b/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt @@ -218,6 +218,20 @@ class CommandProcessorTest() { assertTrue(chatState.getMentionSuggestionsValue().isEmpty()) } + @Test + fun `command suggestions close once the input grows past the command`() { + // The command popup only shows while the whole input is a prefix of a + // command name or alias, and none of them contains a space or an @. An + // input that can match the mention popup has already closed the command + // popup. + commandProcessor.updateCommandSuggestions("/m") + assertTrue(chatState.getShowCommandSuggestionsValue()) + + commandProcessor.updateCommandSuggestions("/msg @ali") + assertFalse(chatState.getShowCommandSuggestionsValue()) + assertTrue(chatState.getCommandSuggestionsValue().isEmpty()) + } + @Test fun `a restored command draft reopens the command popup`() { // Switching conversations restores the draft in code; re-syncing with the