Merge pull request #2532 from vitorpamplona/claude/group-members-as-name-h9PgW

Enhance Marmot group UI with member avatars and dynamic naming
This commit is contained in:
Vitor Pamplona
2026-04-23 17:58:12 -04:00
committed by GitHub
4 changed files with 93 additions and 19 deletions
@@ -231,19 +231,28 @@ fun NonClickableUserPictures(
room: ChatroomKey, room: ChatroomKey,
size: Dp, size: Dp,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) {
NonClickableUserPictures(room.users.toList(), size, accountViewModel)
}
@Composable
fun NonClickableUserPictures(
userHexList: List<HexKey>,
size: Dp,
accountViewModel: AccountViewModel,
) { ) {
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) { Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
when (room.users.size) { when (userHexList.size) {
0 -> {} 0 -> {}
1 -> { 1 -> {
LoadUser(baseUserHex = room.users.first(), accountViewModel) { LoadUser(baseUserHex = userHexList.first(), accountViewModel) {
it?.let { BaseUserPicture(it, size, accountViewModel, outerModifier = Modifier) } it?.let { BaseUserPicture(it, size, accountViewModel, outerModifier = Modifier) }
} }
} }
2 -> { 2 -> {
val userList = room.users.toList() val userList = userHexList
LoadUser(baseUserHex = userList[0], accountViewModel) { LoadUser(baseUserHex = userList[0], accountViewModel) {
it?.let { it?.let {
@@ -268,7 +277,7 @@ fun NonClickableUserPictures(
} }
3 -> { 3 -> {
val userList = room.users.toList() val userList = userHexList
LoadUser(baseUserHex = userList[0], accountViewModel) { LoadUser(baseUserHex = userList[0], accountViewModel) {
it?.let { it?.let {
@@ -303,7 +312,7 @@ fun NonClickableUserPictures(
} }
else -> { else -> {
val userList = room.users.toList() val userList = userHexList
LoadUser(baseUserHex = userList[0], accountViewModel) { LoadUser(baseUserHex = userList[0], accountViewModel) {
it?.let { it?.let {
@@ -21,7 +21,9 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
@@ -35,12 +37,16 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.DisplayUserSetAsSubject
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -56,6 +62,8 @@ fun MarmotGroupChatScreen(
} }
val displayName by chatroom.displayName.collectAsStateWithLifecycle() val displayName by chatroom.displayName.collectAsStateWithLifecycle()
val memberCount by chatroom.memberCount.collectAsStateWithLifecycle() val memberCount by chatroom.memberCount.collectAsStateWithLifecycle()
val members by chatroom.members.collectAsStateWithLifecycle()
val memberPubkeys = remember(members) { members.map { it.pubkey } }
DisappearingScaffold( DisappearingScaffold(
isInvertedLayout = true, isInvertedLayout = true,
@@ -70,19 +78,39 @@ fun MarmotGroupChatScreen(
} }
}, },
title = { title = {
Column( Row(
modifier = modifier =
Modifier.clickable { Modifier.clickable {
nav.nav(Route.MarmotGroupInfo(nostrGroupId)) nav.nav(Route.MarmotGroupInfo(nostrGroupId))
}, },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Text(displayName ?: "Marmot Group") if (memberPubkeys.isNotEmpty()) {
if (memberCount > 0) { NonClickableUserPictures(
Text( userHexList = memberPubkeys,
text = "$memberCount members", size = 36.dp,
style = MaterialTheme.typography.bodySmall, accountViewModel = accountViewModel,
) )
} }
Column {
if (!displayName.isNullOrBlank()) {
Text(displayName!!)
} else if (memberPubkeys.isNotEmpty()) {
DisplayUserSetAsSubject(
userList = memberPubkeys,
accountViewModel = accountViewModel,
)
} else {
Text("Marmot Group")
}
if (memberCount > 0) {
Text(
text = "$memberCount members",
style = MaterialTheme.typography.bodySmall,
)
}
}
} }
}, },
actions = { actions = {
@@ -65,7 +65,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.DisplayUserSetAsSubject
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -179,6 +181,7 @@ fun MarmotGroupListScreen(
MarmotGroupListItem( MarmotGroupListItem(
groupId = groupId, groupId = groupId,
chatroom = chatroom, chatroom = chatroom,
accountViewModel = accountViewModel,
onClick = { onClick = {
nav.nav(Route.MarmotGroupChat(groupId)) nav.nav(Route.MarmotGroupChat(groupId))
}, },
@@ -213,10 +216,13 @@ private fun loadGroupList(
fun MarmotGroupListItem( fun MarmotGroupListItem(
groupId: HexKey, groupId: HexKey,
chatroom: MarmotGroupChatroom, chatroom: MarmotGroupChatroom,
accountViewModel: AccountViewModel,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
val displayName by chatroom.displayName.collectAsStateWithLifecycle() val displayName by chatroom.displayName.collectAsStateWithLifecycle()
val unread by chatroom.unreadCount.collectAsStateWithLifecycle() val unread by chatroom.unreadCount.collectAsStateWithLifecycle()
val members by chatroom.members.collectAsStateWithLifecycle()
val memberPubkeys = remember(members) { members.map { it.pubkey } }
val newestMessage = chatroom.newestMessage val newestMessage = chatroom.newestMessage
Row( Row(
@@ -225,17 +231,40 @@ fun MarmotGroupListItem(
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 12.dp), .padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Column(modifier = Modifier.weight(1f)) { if (memberPubkeys.isNotEmpty()) {
Text( NonClickableUserPictures(
text = displayName ?: "Group ${groupId.take(8)}...", userHexList = memberPubkeys,
style = MaterialTheme.typography.titleSmall, size = 55.dp,
fontWeight = if (unread > 0) FontWeight.Bold else FontWeight.Normal, accountViewModel = accountViewModel,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
}
Column(modifier = Modifier.weight(1f)) {
if (!displayName.isNullOrBlank()) {
Text(
text = displayName!!,
style = MaterialTheme.typography.titleSmall,
fontWeight = if (unread > 0) FontWeight.Bold else FontWeight.Normal,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
} else if (memberPubkeys.isNotEmpty()) {
DisplayUserSetAsSubject(
userList = memberPubkeys,
accountViewModel = accountViewModel,
fontWeight = if (unread > 0) FontWeight.Bold else FontWeight.Normal,
)
} else {
Text(
text = "Group ${groupId.take(8)}...",
style = MaterialTheme.typography.titleSmall,
fontWeight = if (unread > 0) FontWeight.Bold else FontWeight.Normal,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
if (newestMessage != null) { if (newestMessage != null) {
Text( Text(
text = newestMessage.event?.content ?: "", text = newestMessage.event?.content ?: "",
@@ -69,7 +69,15 @@ fun DisplayUserSetAsSubject(
fontWeight: FontWeight = FontWeight.Bold, fontWeight: FontWeight = FontWeight.Bold,
) { ) {
val userList = remember(room) { room.users.toList() } val userList = remember(room) { room.users.toList() }
DisplayUserSetAsSubject(userList, accountViewModel, fontWeight)
}
@Composable
fun DisplayUserSetAsSubject(
userList: List<HexKey>,
accountViewModel: AccountViewModel,
fontWeight: FontWeight = FontWeight.Bold,
) {
if (userList.size == 1) { if (userList.size == 1) {
// Regular Design // Regular Design
Row { Row {