From ff3f8b13f190b8193f4bd570d75bc384bfd4e068 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:36:55 -0600 Subject: [PATCH] feat(messaging): Move message input to Scaffold bottomBar (#4530) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../main/java/com/geeksville/mesh/ui/Main.kt | 3 +- .../meshtastic/feature/messaging/Message.kt | 143 +++++++++--------- 2 files changed, 74 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index fa238e1b1..3fbcfb9a1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -33,7 +33,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.recalculateWindowInsets import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.width @@ -464,7 +463,7 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode NavHost( navController = navController, startDestination = NodesRoutes.NodesGraph, - modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding().imePadding(), + modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding(), ) { contactsGraph(navController, uIViewModel.scrollToTopEventFlow) nodesGraph(navController, uIViewModel.scrollToTopEventFlow) diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt index 7e3ae510b..10ad8a954 100644 --- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt +++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt @@ -303,6 +303,17 @@ fun MessageScreen( sharedContact?.let { contact -> SharedContactDialog(contact = contact, onDismiss = { sharedContact = null }) } + val originalMessage by + remember(replyingToPacketId, pagedMessages.itemCount) { + derivedStateOf { + replyingToPacketId?.let { id -> + (0 until pagedMessages.itemCount).firstNotNullOfOrNull { index -> + pagedMessages[index]?.takeIf { it.packetId == id } + } + } + } + } + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { @@ -357,82 +368,74 @@ fun MessageScreen( ) } }, - ) { paddingValues -> - Column(Modifier.fillMaxSize().padding(paddingValues).focusable()) { - Box(modifier = Modifier.weight(1f)) { - MessageListPaged( - modifier = Modifier.fillMaxSize(), - listState = listState, - state = - MessageListPagedState( - nodes = nodes, - ourNode = ourNode, - messages = pagedMessages, - selectedIds = selectedMessageIds, - contactKey = contactKey, - firstUnreadMessageUuid = firstUnreadMessageUuid, - hasUnreadMessages = hasUnreadMessages, - filteredCount = filteredCount, - showFiltered = showFiltered, - filteringDisabled = filteringDisabled, - ), - handlers = - MessageListHandlers( - onUnreadChanged = { messageUuid, timestamp -> - onEvent(MessageScreenEvent.ClearUnreadCount(messageUuid, timestamp)) + bottomBar = { + Column { + AnimatedVisibility(visible = showQuickChat) { + QuickChatRow( + enabled = connectionState.isConnected(), + actions = quickChatActions, + onClick = { action -> + handleQuickChatAction( + action = action, + messageInputState = messageInputState, + onSendMessage = { text -> onEvent(MessageScreenEvent.SendMessage(text)) }, + ) }, - onSendReaction = { emoji, id -> onEvent(MessageScreenEvent.SendReaction(emoji, id)) }, - onClickChip = { onEvent(MessageScreenEvent.NodeDetails(it)) }, - onDeleteMessages = { viewModel.deleteMessages(it) }, - onSendMessage = { text, key -> viewModel.sendMessage(text, key) }, - onReply = { message -> replyingToPacketId = message?.packetId }, - ), - quickEmojis = viewModel.frequentEmojis, - ) - // Show FAB if we can scroll towards the newest messages (index 0). - if (listState.canScrollBackward) { - ScrollToBottomFab(coroutineScope, listState) + ) } - } - AnimatedVisibility(visible = showQuickChat) { - QuickChatRow( - enabled = connectionState.isConnected(), - actions = quickChatActions, - onClick = { action -> - handleQuickChatAction( - action = action, - messageInputState = messageInputState, - onSendMessage = { text -> onEvent(MessageScreenEvent.SendMessage(text)) }, - ) + ReplySnippet( + originalMessage = originalMessage, + onClearReply = { replyingToPacketId = null }, + ourNode = ourNode, + ) + MessageInput( + isEnabled = connectionState.isConnected(), + isHomoglyphEncodingEnabled = homoglyphEncodingEnabled, + textFieldState = messageInputState, + onSendMessage = { + val messageText = messageInputState.text.toString().trim() + if (messageText.isNotEmpty()) { + onEvent(MessageScreenEvent.SendMessage(messageText, replyingToPacketId)) + } }, ) } - val originalMessage by - remember(replyingToPacketId, pagedMessages.itemCount) { - derivedStateOf { - replyingToPacketId?.let { id -> - (0 until pagedMessages.itemCount).firstNotNullOfOrNull { index -> - pagedMessages[index]?.takeIf { it.packetId == id } - } - } - } - } - ReplySnippet( - originalMessage = originalMessage, - onClearReply = { replyingToPacketId = null }, - ourNode = ourNode, - ) - MessageInput( - isEnabled = connectionState.isConnected(), - isHomoglyphEncodingEnabled = homoglyphEncodingEnabled, - textFieldState = messageInputState, - onSendMessage = { - val messageText = messageInputState.text.toString().trim() - if (messageText.isNotEmpty()) { - onEvent(MessageScreenEvent.SendMessage(messageText, replyingToPacketId)) - } - }, + }, + ) { paddingValues -> + Box(Modifier.fillMaxSize().padding(paddingValues).focusable()) { + MessageListPaged( + modifier = Modifier.fillMaxSize(), + listState = listState, + state = + MessageListPagedState( + nodes = nodes, + ourNode = ourNode, + messages = pagedMessages, + selectedIds = selectedMessageIds, + contactKey = contactKey, + firstUnreadMessageUuid = firstUnreadMessageUuid, + hasUnreadMessages = hasUnreadMessages, + filteredCount = filteredCount, + showFiltered = showFiltered, + filteringDisabled = filteringDisabled, + ), + handlers = + MessageListHandlers( + onUnreadChanged = { messageUuid, timestamp -> + onEvent(MessageScreenEvent.ClearUnreadCount(messageUuid, timestamp)) + }, + onSendReaction = { emoji, id -> onEvent(MessageScreenEvent.SendReaction(emoji, id)) }, + onClickChip = { onEvent(MessageScreenEvent.NodeDetails(it)) }, + onDeleteMessages = { viewModel.deleteMessages(it) }, + onSendMessage = { text, key -> viewModel.sendMessage(text, key) }, + onReply = { message -> replyingToPacketId = message?.packetId }, + ), + quickEmojis = viewModel.frequentEmojis, ) + // Show FAB if we can scroll towards the newest messages (index 0). + if (listState.canScrollBackward) { + ScrollToBottomFab(coroutineScope, listState) + } } } }