From 11ba03334c8247e073010b6e101837e72ba6a317 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Apr 2026 21:40:23 +0000 Subject: [PATCH] fix: classify Marmot groups in Messages Known/New tabs by admin follow Before, every Marmot group landed unconditionally under the Known tab of the Messages screen while the New Requests tab ignored groups entirely, so an invite from a stranger appeared next to real conversations. The dedicated Marmot group list screen split by ownerSentMessage only, so unreplied groups where an admin was followed still showed as New Requests. Replace both splits with a shared MarmotGroupChatroom.isKnown(followingKeySet): - user has replied -> Known - no known members and no messages -> New Requests - otherwise Known iff any admin is in the follow set Wire the combined Messages feeds (ChatroomListKnownFeedFilter and ChatroomListNewFeedFilter) through the helper, invalidate dmNew on group list changes so empty groups flow into New Requests, and have MarmotGroupListScreen observe kind3FollowList so newly followed admins move groups between tabs immediately. --- .../screen/loggedIn/AccountFeedContentStates.kt | 1 + .../chats/marmotGroup/MarmotGroupListScreen.kt | 8 ++++++-- .../rooms/dal/ChatroomListKnownFeedFilter.kt | 6 +++++- .../chats/rooms/dal/ChatroomListNewFeedFilter.kt | 11 ++++++++++- .../model/marmotGroups/MarmotGroupChatroom.kt | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 4 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 5c52c7b27..fb08a3e52 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 @@ -124,6 +124,7 @@ class AccountFeedContentStates( scope.launch(Dispatchers.IO) { account.marmotGroupList.groupListChanges.collect { dmKnown.invalidateData() + dmNew.invalidateData() } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupListScreen.kt index 6154ade01..cbcd0d9ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupListScreen.kt @@ -93,8 +93,12 @@ fun MarmotGroupListScreen( // (Account.ensureMarmotKeyPackagePublished), so this screen no longer // needs to do anything to make sure invitees can find a KeyPackage. - val knownGroups = remember(groupList) { groupList.filter { it.second.ownerSentMessage } } - val newRequestGroups = remember(groupList) { groupList.filter { !it.second.ownerSentMessage } } + val followState by accountViewModel.account.kind3FollowList.flow + .collectAsStateWithLifecycle() + val followingKeySet = followState.authors + + val knownGroups = remember(groupList, followingKeySet) { groupList.filter { it.second.isKnown(followingKeySet) } } + val newRequestGroups = remember(groupList, followingKeySet) { groupList.filter { !it.second.isKnown(followingKeySet) } } val visibleGroups = if (selectedTab == 0) knownGroups else newRequestGroups Scaffold( 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 e826250c2..59645b592 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,11 @@ class ChatroomListKnownFeedFilter( val marmotGroups = account.marmotGroupList.rooms.mapNotNull { _, chatroom -> - chatroom.newestMessage ?: chatroom.placeholderNote() + if (chatroom.isKnown(followingKeySet)) { + chatroom.newestMessage ?: chatroom.placeholderNote() + } else { + null + } } return (privateMessages + publicChannels + ephemeralChats + marmotGroups).sortedWith(DefaultFeedOrder) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListNewFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListNewFeedFilter.kt index 27ac9854e..9f859dd14 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListNewFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListNewFeedFilter.kt @@ -47,7 +47,16 @@ class ChatroomListNewFeedFilter( } } - return privateMessages.sortedWith(DefaultFeedOrder) + val marmotGroups = + account.marmotGroupList.rooms.mapNotNull { _, chatroom -> + if (!chatroom.isKnown(followingKeySet)) { + chatroom.newestMessage ?: chatroom.placeholderNote() + } else { + null + } + } + + return (privateMessages + marmotGroups).sortedWith(DefaultFeedOrder) } override fun updateListWith( 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 85a857f7f..cd106aabd 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 @@ -69,6 +69,21 @@ class MarmotGroupChatroom( */ var ownerSentMessage: Boolean = false + /** + * Classifies this group for the Known/New Requests split. + * + * Rules: + * - If the local user has already participated ([ownerSentMessage]), Known. + * - If the group has no known members and no messages yet, New Requests + * (we don't know who invited us, so it's untrusted by default). + * - Otherwise, Known iff at least one admin is in the follow set. + */ + fun isKnown(followingKeySet: Set): Boolean { + if (ownerSentMessage) return true + if (memberCount.value == 0 && messages.isEmpty()) return false + return adminPubkeys.value.any { it in followingKeySet } + } + // 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.