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.
This commit is contained in:
Claude
2026-04-22 21:40:23 +00:00
parent c82f4342c4
commit 11ba03334c
5 changed files with 37 additions and 4 deletions
@@ -124,6 +124,7 @@ class AccountFeedContentStates(
scope.launch(Dispatchers.IO) {
account.marmotGroupList.groupListChanges.collect {
dmKnown.invalidateData()
dmNew.invalidateData()
}
}
}
@@ -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(
@@ -79,7 +79,11 @@ class ChatroomListKnownFeedFilter(
val marmotGroups =
account.marmotGroupList.rooms.mapNotNull { _, chatroom ->
if (chatroom.isKnown(followingKeySet)) {
chatroom.newestMessage ?: chatroom.placeholderNote()
} else {
null
}
}
return (privateMessages + publicChannels + ephemeralChats + marmotGroups).sortedWith(DefaultFeedOrder)
@@ -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(
@@ -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<HexKey>): 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.