From 15481dd0047147a1e2347b9f9ffc657ebca1c8e1 Mon Sep 17 00:00:00 2001 From: Mac DeCourcy <49794076+mdecourcy@users.noreply.github.com> Date: Sat, 22 Nov 2025 06:57:39 -0800 Subject: [PATCH] fix: unread count racecondition (#3784) --- .gitignore | 4 ++++ .../feature/messaging/MessageList.kt | 20 +++++++++++++------ .../feature/messaging/UnreadUiDefaults.kt | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 7652f35e6..c3468500e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ keystore.properties /build-logic/convention/build/* /build-logic/build/ + +# Personal build scripts +build-and-install-android.sh +wireless-install.sh diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageList.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageList.kt index a9f35b521..524031efb 100644 --- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageList.kt +++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageList.kt @@ -61,6 +61,7 @@ import org.meshtastic.core.strings.Res import org.meshtastic.core.strings.new_messages_below import org.meshtastic.feature.messaging.component.MessageItem import org.meshtastic.feature.messaging.component.ReactionDialog +import timber.log.Timber import kotlin.collections.buildList internal data class MessageListState( @@ -368,16 +369,23 @@ private fun UpdateUnreadCount( messages: List, onUnreadChanged: (Long, Long) -> Unit, ) { - LaunchedEffect(messages) { + val remoteMessageCount = remember(messages) { messages.count { !it.fromLocal } } + + LaunchedEffect(remoteMessageCount, listState) { + Timber.d("UpdateUnreadCount LaunchedEffect started/restarted, remoteMessageCount=$remoteMessageCount") snapshotFlow { listState.firstVisibleItemIndex } .debounce(timeoutMillis = UnreadUiDefaults.SCROLL_DEBOUNCE_MILLIS) .collectLatest { index -> + Timber.d("Debounce triggered, index=$index, messages.size=${messages.size}") val lastUnreadIndex = messages.indexOfLast { !it.read && !it.fromLocal } - if (lastUnreadIndex != -1 && index <= lastUnreadIndex && index < messages.size) { - val visibleMessage = messages[index] - if (!visibleMessage.read && !visibleMessage.fromLocal) { - onUnreadChanged(visibleMessage.uuid, visibleMessage.receivedTime) - } + Timber.d("lastUnreadIndex=$lastUnreadIndex") + // If user has scrolled past all unread messages, mark the last unread message as read + if (lastUnreadIndex != -1 && index <= lastUnreadIndex) { + val lastUnreadMessage = messages[lastUnreadIndex] + Timber.d("Marking last unread message as read: ${lastUnreadMessage.uuid}") + onUnreadChanged(lastUnreadMessage.uuid, lastUnreadMessage.receivedTime) + } else { + Timber.d("Not marking as read - no unread messages or user hasn't scrolled past them") } } } diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/UnreadUiDefaults.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/UnreadUiDefaults.kt index f9ba166e9..2c65b947c 100644 --- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/UnreadUiDefaults.kt +++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/UnreadUiDefaults.kt @@ -45,5 +45,5 @@ internal object UnreadUiDefaults { * A longer debounce prevents thrashing the database during quick scrubs yet still feels responsive once the user * settles on a position. */ - const val SCROLL_DEBOUNCE_MILLIS = 5_000L + const val SCROLL_DEBOUNCE_MILLIS = 3_000L }