feat: add Marmot group info screen, member list, and leave group

Based on MIP spec review, add missing UI screens:

MarmotManager additions (commons):
- memberPubkeys(): extract Nostr pubkeys from MLS ratchet tree
  leaf nodes via Credential.Basic identity
- memberCount(): get group member count
- groupEpoch(): expose current MLS epoch
- GroupMemberInfo data class for leaf index + pubkey pairs

Account & AccountViewModel:
- leaveMarmotGroup(): publish SelfRemove proposal and clean up
- marmotGroupMembers(): expose member list to UI

MarmotGroupInfoScreen (new):
- Group metadata header (name, ID, epoch, member count)
- Member list with leaf indices, clickable to view profiles
- "Leave Group" with confirmation dialog
- "Add Member" access from group info

Navigation:
- Route.MarmotGroupInfo added and wired into nav graph
- Chat screen title now clickable to navigate to group info

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
This commit is contained in:
Claude
2026-04-05 21:09:25 +00:00
parent 7ed036bbaa
commit ddca55fae6
7 changed files with 374 additions and 1 deletions
@@ -35,6 +35,7 @@ import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupManager
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupStateStore
import com.vitorpamplona.quartz.marmot.mls.tree.Credential
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
@@ -254,4 +255,44 @@ class MarmotManager(
* Get all active group IDs.
*/
fun activeGroupIds(): Set<HexKey> = groupManager.activeGroupIds()
// --- Group Info ---
/**
* Get the member count for a group.
*/
fun memberCount(nostrGroupId: HexKey): Int = groupManager.getGroup(nostrGroupId)?.memberCount ?: 0
/**
* Get the member list for a group.
* Returns leaf index and Nostr pubkey (extracted from BasicCredential) for each member.
*/
fun memberPubkeys(nostrGroupId: HexKey): List<GroupMemberInfo> {
val group = groupManager.getGroup(nostrGroupId) ?: return emptyList()
return group.members().mapNotNull { (leafIndex, leafNode) ->
val pubkey =
when (val cred = leafNode.credential) {
is Credential.Basic -> cred.identity.toHexKey()
else -> null
}
if (pubkey != null) {
GroupMemberInfo(leafIndex = leafIndex, pubkey = pubkey)
} else {
null
}
}
}
/**
* Get the current epoch for a group.
*/
fun groupEpoch(nostrGroupId: HexKey): Long? = groupManager.getGroup(nostrGroupId)?.epoch
}
/**
* Information about a member in a Marmot MLS group.
*/
data class GroupMemberInfo(
val leafIndex: Int,
val pubkey: HexKey,
)