From aea1dc53c3659c1cbe4686c16b030d7816f5e454 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 17:08:59 +0000 Subject: [PATCH] fix: show empty Marmot groups in Messages screen Previously ChatroomListKnownFeedFilter dropped any group whose newestMessage was null, so groups the user just created and groups received via Welcome events without any activity yet never appeared in the Messages list. Emit a stable placeholder Note (carrying the chatroom as a gatherer) per empty group, route it through the existing Marmot row path, and invalidate dmKnown on MarmotGroupList.groupListChanges so newly added or promoted groups rebuild the feed. --- .../loggedIn/AccountFeedContentStates.kt | 15 +++++++++++++ .../chats/rooms/ChatroomHeaderCompose.kt | 20 +++++++++++++----- .../rooms/dal/ChatroomListKnownFeedFilter.kt | 2 +- amethyst/src/main/res/values/strings.xml | 1 + .../model/marmotGroups/MarmotGroupChatroom.kt | 21 +++++++++++++++++++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt index ac6a5e30a..1dfbda89f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt @@ -56,6 +56,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.dal.ShortsFeedFilter import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal.VideoFeedFilter import com.vitorpamplona.amethyst.ui.screen.loggedIn.webBookmarks.dal.WebBookmarkFeedFilter import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class AccountFeedContentStates( val account: Account, @@ -100,6 +102,19 @@ class AccountFeedContentStates( val webBookmarks = FeedContentState(WebBookmarkFeedFilter(account), scope, LocalCache) + init { + // Marmot group list changes (new group, group marked known, group + // metadata synced) don't flow through LocalCache.newEventBundles, so + // the additive update path can't see them. Force a full feed rebuild + // whenever the list changes so empty groups appear and placeholder + // rows get replaced by real messages. + scope.launch(Dispatchers.IO) { + account.marmotGroupList.groupListChanges.collect { + dmKnown.invalidateData() + } + } + } + suspend fun init() { notificationSummary.initializeSuspend() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index 8e02c4945..cf2228a25 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -85,7 +85,11 @@ fun ChatroomHeaderCompose( accountViewModel: AccountViewModel, nav: INav, ) { - if (baseNote.event != null) { + val isEmptyMarmotPlaceholder = + baseNote.event == null && + baseNote.inGatherers?.any { it is MarmotGroupChatroom } == true + + if (baseNote.event != null || isEmptyMarmotPlaceholder) { ChatroomComposeChannelOrUser(baseNote, accountViewModel, nav) } else { val hasEvent by observeNoteHasEvent(baseNote, accountViewModel) @@ -248,21 +252,27 @@ private fun MarmotGroupRoomCompose( accountViewModel: AccountViewModel, nav: INav, ) { - val authorName by observeUserName(lastMessage.author!!, accountViewModel) val displayName by chatroom.displayName.collectAsStateWithLifecycle() val unread by chatroom.unreadCount.collectAsStateWithLifecycle() + val author = lastMessage.author val noteEvent = lastMessage.event - val description = noteEvent?.content?.take(200) - val groupName = displayName?.takeIf { it.isNotBlank() } ?: "Group ${chatroom.nostrGroupId.take(8)}" + val lastContent = + if (author != null && noteEvent != null) { + val authorName by observeUserName(author, accountViewModel) + "$authorName: ${noteEvent.content.take(200)}" + } else { + stringRes(R.string.marmot_group_no_messages_yet) + } + ChannelName( channelIdHex = chatroom.nostrGroupId, channelPicture = null, channelTitle = { modifier -> ChannelTitleWithLabelInfo(groupName, R.string.marmot_group, modifier) }, channelLastTime = lastMessage.createdAt(), - channelLastContent = "$authorName: $description", + channelLastContent = lastContent, hasNewMessages = unread > 0, loadProfilePicture = accountViewModel.settings.showProfilePictures(), loadRobohash = accountViewModel.settings.isNotPerformanceMode(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt index 932e3197f..e826250c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt @@ -79,7 +79,7 @@ class ChatroomListKnownFeedFilter( val marmotGroups = account.marmotGroupList.rooms.mapNotNull { _, chatroom -> - chatroom.newestMessage + chatroom.newestMessage ?: chatroom.placeholderNote() } return (privateMessages + publicChannels + ephemeralChats + marmotGroups).sortedWith(DefaultFeedOrder) diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 510936a64..b537cbcf6 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -284,6 +284,7 @@ Public Chat MLS Group + No messages yet Public Chat Metadata Public chats are visible to everyone on Nostr and anyone can participate on them. They are great for open communities around specific topics. diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/marmotGroups/MarmotGroupChatroom.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/marmotGroups/MarmotGroupChatroom.kt index bf9ec65a0..d531e2778 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/marmotGroups/MarmotGroupChatroom.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/marmotGroups/MarmotGroupChatroom.kt @@ -59,6 +59,27 @@ class MarmotGroupChatroom( */ var ownerSentMessage: Boolean = false + // Synthetic note used by list views to represent the group when no + // messages have been received yet. Lazily created and kept stable so + // equality-based feed diffing treats it as the same row across refreshes. + private var cachedPlaceholder: Note? = null + + @Synchronized + fun placeholderNote(): Note { + val existing = cachedPlaceholder + if (existing != null) return existing + val created = + Note(placeholderIdHex(nostrGroupId)).apply { + addGatherer(this@MarmotGroupChatroom) + } + cachedPlaceholder = created + return created + } + + companion object { + fun placeholderIdHex(nostrGroupId: HexKey): HexKey = "marmot-empty-$nostrGroupId" + } + private var changesFlow: WeakReference>> = WeakReference(null) fun changesFlow(): MutableSharedFlow> {