From 3a07d78da3f2a2050488acad425463783b244c99 Mon Sep 17 00:00:00 2001 From: infi Date: Wed, 17 Jun 2026 20:16:29 +0200 Subject: [PATCH] feat: m3 list style --- .../stoat/composables/chat/MemberListItem.kt | 55 +++- .../stoat/internals/extensions/Defaults.kt | 2 +- .../stoat/screens/chat/views/FriendsScreen.kt | 86 +++++- .../stoat/screens/create/CreateGroupScreen.kt | 12 +- .../stoat/screens/settings/SettingsScreen.kt | 244 ++++++++---------- .../java/chat/stoat/sheets/MemberListSheet.kt | 74 +++--- .../chat/stoat/sheets/ReactionInfoSheet.kt | 19 +- 7 files changed, 301 insertions(+), 191 deletions(-) diff --git a/app/src/main/java/chat/stoat/composables/chat/MemberListItem.kt b/app/src/main/java/chat/stoat/composables/chat/MemberListItem.kt index 28cb6f7a..dc6518cc 100644 --- a/app/src/main/java/chat/stoat/composables/chat/MemberListItem.kt +++ b/app/src/main/java/chat/stoat/composables/chat/MemberListItem.kt @@ -1,22 +1,28 @@ package chat.stoat.composables.chat +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.text.style.TextOverflow import chat.stoat.api.internals.BrushCompat import chat.stoat.api.internals.Roles import chat.stoat.api.internals.solidColor -import chat.stoat.core.model.schemas.Member -import chat.stoat.core.model.schemas.User import chat.stoat.composables.generic.UserAvatar import chat.stoat.composables.generic.presenceFromStatus import chat.stoat.core.model.data.STOAT_FILES -import chat.stoat.internals.extensions.TransparentListItemColours +import chat.stoat.core.model.schemas.Member +import chat.stoat.core.model.schemas.User + +private val NoneLambda = { } @Composable fun MemberListItem( @@ -25,6 +31,10 @@ fun MemberListItem( serverId: String?, userId: String, modifier: Modifier = Modifier, + first: Boolean = false, + last: Boolean = false, + onClick: () -> Unit = {}, + onLongClick: () -> Unit = NoneLambda, trailingContent: @Composable (() -> Unit)? = null, ) { val highestColourRole = serverId?.let { @@ -41,8 +51,43 @@ fun MemberListItem( ?: Brush.solidColor(LocalContentColor.current) ListItem( - colors = TransparentListItemColours, - modifier = modifier, + colors = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ), + modifier = modifier + .then( + when { + first && last -> Modifier.clip(MaterialTheme.shapes.large) + + first -> Modifier.clip( + MaterialTheme.shapes.extraSmall.copy( + topStart = MaterialTheme.shapes.large.topStart, + topEnd = MaterialTheme.shapes.large.topEnd + ) + ) + + last -> Modifier.clip( + MaterialTheme.shapes.extraSmall.copy( + bottomStart = MaterialTheme.shapes.large.bottomStart, + bottomEnd = MaterialTheme.shapes.large.bottomEnd + ) + ) + + else -> Modifier.clip(MaterialTheme.shapes.extraSmall) + } + ) + .then( + if (onLongClick != NoneLambda) { + Modifier.combinedClickable( + onClick = onClick, + onLongClick = onLongClick + ) + } else { + Modifier.clickable( + onClick = onClick + ) + } + ), headlineContent = { Text( text = member?.nickname diff --git a/app/src/main/java/chat/stoat/internals/extensions/Defaults.kt b/app/src/main/java/chat/stoat/internals/extensions/Defaults.kt index 8e5ef9a3..d7930981 100644 --- a/app/src/main/java/chat/stoat/internals/extensions/Defaults.kt +++ b/app/src/main/java/chat/stoat/internals/extensions/Defaults.kt @@ -7,6 +7,6 @@ import androidx.compose.ui.graphics.Color val TransparentListItemColours: ListItemColors @Composable - get() = ListItemDefaults.colors().copy( + get() = ListItemDefaults.colors( containerColor = Color.Transparent ) \ No newline at end of file diff --git a/app/src/main/java/chat/stoat/screens/chat/views/FriendsScreen.kt b/app/src/main/java/chat/stoat/screens/chat/views/FriendsScreen.kt index 76ce0cc1..60932648 100644 --- a/app/src/main/java/chat/stoat/screens/chat/views/FriendsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/chat/views/FriendsScreen.kt @@ -7,7 +7,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -179,8 +178,24 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () text = AnnotatedString.fromHtml( stringResource( R.string.friends_add_by_tag_sheet_description, - "", - "", + "", + "", "", ) ), @@ -601,19 +616,28 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () val item = FriendRequests.getIncoming().getOrNull(it) if (item == null) return@items + val isLast = it == FriendRequests.getIncoming().size - 1 + MemberListItem( member = null, user = item, serverId = null, userId = item.id ?: "", - modifier = Modifier.clickable { + first = it == 0, + last = isLast, + onClick = { scope.launch { item.id?.let { userId -> ActionChannel.send(Action.OpenUserSheet(userId, null)) } } - } + }, + modifier = Modifier.padding(horizontal = 16.dp) ) + + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } stickyHeader(key = "outgoing") { @@ -627,19 +651,28 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () val item = FriendRequests.getOutgoing().getOrNull(it) if (item == null) return@items + val isLast = it == FriendRequests.getOutgoing().size - 1 + MemberListItem( member = null, user = item, serverId = null, userId = item.id ?: "", - modifier = Modifier.clickable { + first = it == 0, + last = isLast, + onClick = { scope.launch { item.id?.let { userId -> ActionChannel.send(Action.OpenUserSheet(userId, null)) } } - } + }, + modifier = Modifier.padding(horizontal = 16.dp) ) + + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } stickyHeader(key = "online") { @@ -653,19 +686,28 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () val item = FriendRequests.getOnlineFriends().getOrNull(it) if (item == null) return@items + val isLast = it == FriendRequests.getOnlineFriends().size - 1 + MemberListItem( member = null, user = item, serverId = null, userId = item.id ?: "", - modifier = Modifier.clickable { + first = it == 0, + last = isLast, + onClick = { scope.launch { item.id?.let { userId -> ActionChannel.send(Action.OpenUserSheet(userId, null)) } } - } + }, + modifier = Modifier.padding(horizontal = 16.dp) ) + + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } stickyHeader(key = "not_online") { @@ -679,19 +721,28 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () val item = FriendRequests.getFriends(true).getOrNull(it) if (item == null) return@items + val isLast = it == FriendRequests.getFriends(true).size - 1 + MemberListItem( member = null, user = item, serverId = null, userId = item.id ?: "", - modifier = Modifier.clickable { + first = it == 0, + last = isLast, + onClick = { scope.launch { item.id?.let { userId -> ActionChannel.send(Action.OpenUserSheet(userId, null)) } } - } + }, + modifier = Modifier.padding(horizontal = 16.dp) ) + + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } stickyHeader(key = "blocked") { @@ -706,19 +757,28 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () val item = FriendRequests.getBlocked().getOrNull(it) if (item == null) return@items + val isLast = it == FriendRequests.getBlocked().size - 1 + MemberListItem( member = null, user = item, serverId = null, userId = item.id ?: "", - modifier = Modifier.clickable { + first = it == 0, + last = isLast, + onClick = { scope.launch { item.id?.let { userId -> ActionChannel.send(Action.OpenUserSheet(userId, null)) } } - } + }, + modifier = Modifier.padding(horizontal = 16.dp) ) + + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } } diff --git a/app/src/main/java/chat/stoat/screens/create/CreateGroupScreen.kt b/app/src/main/java/chat/stoat/screens/create/CreateGroupScreen.kt index a35a82f9..e9d3cb87 100644 --- a/app/src/main/java/chat/stoat/screens/create/CreateGroupScreen.kt +++ b/app/src/main/java/chat/stoat/screens/create/CreateGroupScreen.kt @@ -3,10 +3,11 @@ package chat.stoat.screens.create import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -192,13 +193,16 @@ fun CreateGroupScreen( val friend = StoatAPI.userCache[viewModel.friendsFilteredBySearch[index]] ?: return@items val isMember = viewModel.groupMembers.contains(friend.id) + val isLast = index == viewModel.friendsFilteredBySearch.size - 1 MemberListItem( member = null, user = friend, serverId = null, userId = friend.id!!, - modifier = Modifier.clickable { + first = index == 0, + last = isLast, + onClick = { if (isMember) { viewModel.groupMembers.remove(friend.id) } else { @@ -207,6 +211,7 @@ fun CreateGroupScreen( } } }, + modifier = Modifier.padding(horizontal = 16.dp), trailingContent = { Checkbox( checked = isMember, @@ -215,6 +220,9 @@ fun CreateGroupScreen( ) } ) + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } } } diff --git a/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt b/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt index 3d38fcaa..65a9f217 100644 --- a/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt @@ -4,7 +4,9 @@ import android.content.Intent import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -13,6 +15,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -21,6 +24,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag @@ -100,18 +104,15 @@ fun SettingsScreen( Column( modifier = Modifier .fillMaxSize() - .padding(vertical = 10.dp) + .padding(bottom = 10.dp) ) { ListHeader { Text(stringResource(R.string.settings_category_account)) } - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_account) - ) - }, + SettingsListItem( + first = true, + headlineContent = { Text(text = stringResource(id = R.string.settings_account)) }, leadingContent = { SettingsIcon { Icon( @@ -122,17 +123,11 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_account") - .clickable { - navController.navigate("settings/account") - } + .clickable { navController.navigate("settings/account") } ) - - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_profile) - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + headlineContent = { Text(text = stringResource(id = R.string.settings_profile)) }, leadingContent = { SettingsIcon { Icon( @@ -143,17 +138,12 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_profile") - .clickable { - navController.navigate("settings/profile") - } + .clickable { navController.navigate("settings/profile") } ) - - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_sessions) - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + last = true, + headlineContent = { Text(text = stringResource(id = R.string.settings_sessions)) }, leadingContent = { SettingsIcon { Icon( @@ -164,21 +154,16 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_sessions") - .clickable { - navController.navigate("settings/sessions") - } + .clickable { navController.navigate("settings/sessions") } ) ListHeader { Text(stringResource(R.string.settings_category_general)) } - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_appearance) - ) - }, + SettingsListItem( + first = true, + headlineContent = { Text(text = stringResource(id = R.string.settings_appearance)) }, leadingContent = { SettingsIcon { Icon( @@ -189,17 +174,11 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_appearance") - .clickable { - navController.navigate("settings/appearance") - } + .clickable { navController.navigate("settings/appearance") } ) - - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_language) - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + headlineContent = { Text(text = stringResource(id = R.string.settings_language)) }, leadingContent = { SettingsIcon { Icon( @@ -210,17 +189,11 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_language") - .clickable { - navController.navigate("settings/language") - } + .clickable { navController.navigate("settings/language") } ) - - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_chat) - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + headlineContent = { Text(text = stringResource(id = R.string.settings_chat)) }, leadingContent = { SettingsIcon { Icon( @@ -231,16 +204,12 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_chat") - .clickable { - navController.navigate("settings/chat") - } + .clickable { navController.navigate("settings/chat") } ) - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_notifications) - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + last = true, + headlineContent = { Text(text = stringResource(id = R.string.settings_notifications)) }, leadingContent = { SettingsIcon { Icon( @@ -251,21 +220,25 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_notifications") - .clickable { - navController.navigate("settings/notifications") - } + .clickable { navController.navigate("settings/notifications") } ) ListHeader { Text(stringResource(R.string.settings_category_miscellaneous)) } - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.about) - ) - }, + val miscLastIsExperiments = LoadedSettings.experimentsEnabled + val miscLastIsLabs = + !miscLastIsExperiments && FeatureFlags.labsAccessControlGranted + val miscLastIsDebug = + !miscLastIsExperiments && !miscLastIsLabs && BuildConfig.DEBUG + val miscLastIsAbout = + !miscLastIsExperiments && !miscLastIsLabs && !miscLastIsDebug + + SettingsListItem( + first = true, + last = miscLastIsAbout, + headlineContent = { Text(text = stringResource(id = R.string.about)) }, leadingContent = { SettingsIcon { Icon( @@ -276,18 +249,14 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_about") - .clickable { - navController.navigate("about") - } + .clickable { navController.navigate("about") } ) if (BuildConfig.DEBUG) { - ListItem( - headlineContent = { - Text( - text = "Debug" - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + last = miscLastIsDebug, + headlineContent = { Text(text = "Debug") }, leadingContent = { SettingsIcon { Icon( @@ -298,19 +267,15 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_debug") - .clickable { - navController.navigate("settings/debug") - } + .clickable { navController.navigate("settings/debug") } ) } if (FeatureFlags.labsAccessControlGranted) { - ListItem( - headlineContent = { - Text( - text = "Labs" - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + last = miscLastIsLabs, + headlineContent = { Text(text = "Labs") }, leadingContent = { SettingsIcon { Icon( @@ -321,19 +286,15 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_labs") - .clickable { - navController.navigate("labs") - } + .clickable { navController.navigate("labs") } ) } if (LoadedSettings.experimentsEnabled) { - ListItem( - headlineContent = { - Text( - text = "Experiments" - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + last = true, + headlineContent = { Text(text = "Experiments") }, leadingContent = { SettingsIcon { Icon( @@ -344,9 +305,7 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_experiments") - .clickable { - navController.navigate("settings/experiments") - } + .clickable { navController.navigate("settings/experiments") } ) } @@ -359,12 +318,9 @@ fun SettingsScreen( ) } - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_changelog) - ) - }, + SettingsListItem( + first = true, + headlineContent = { Text(text = stringResource(id = R.string.settings_changelog)) }, leadingContent = { SettingsIcon { Icon( @@ -375,22 +331,12 @@ fun SettingsScreen( }, modifier = Modifier .testTag("settings_view_changelog") - .clickable { - navController.navigate("changelog/latest") - } + .clickable { navController.navigate("changelog/latest") } ) - - ListItem( - headlineContent = { - Text( - text = stringResource(id = R.string.settings_feedback) - ) - }, - supportingContent = { - Text( - text = stringResource(id = R.string.settings_feedback_description) - ) - }, + Spacer(Modifier.height(2.dp)) + SettingsListItem( + headlineContent = { Text(text = stringResource(id = R.string.settings_feedback)) }, + supportingContent = { Text(text = stringResource(id = R.string.settings_feedback_description)) }, leadingContent = { SettingsIcon { Icon( @@ -406,18 +352,16 @@ fun SettingsScreen( context, InviteActivity::class.java ).setAction(Intent.ACTION_VIEW) - intent.data = "https://stt.gg/Testers".toUri() context.startActivity(intent) } ) - - ListItem( + Spacer(Modifier.height(2.dp)) + SettingsListItem( + last = true, headlineContent = { CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { - Text( - text = stringResource(id = R.string.logout) - ) + Text(text = stringResource(id = R.string.logout)) } }, leadingContent = { @@ -445,6 +389,42 @@ fun SettingsScreen( } } +@Composable +private fun SettingsListItem( + first: Boolean = false, + last: Boolean = false, + headlineContent: @Composable () -> Unit, + supportingContent: (@Composable () -> Unit)? = null, + leadingContent: (@Composable () -> Unit)? = null, + modifier: Modifier = Modifier, +) { + ListItem( + colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceContainer), + headlineContent = headlineContent, + supportingContent = supportingContent, + leadingContent = leadingContent, + modifier = Modifier + .padding(horizontal = 16.dp) + .clip( + when { + first && last -> MaterialTheme.shapes.large + first -> MaterialTheme.shapes.extraSmall.copy( + topStart = MaterialTheme.shapes.large.topStart, + topEnd = MaterialTheme.shapes.large.topEnd + ) + + last -> MaterialTheme.shapes.extraSmall.copy( + bottomStart = MaterialTheme.shapes.large.bottomStart, + bottomEnd = MaterialTheme.shapes.large.bottomEnd + ) + + else -> MaterialTheme.shapes.extraSmall + } + ) + .then(modifier) + ) +} + @Composable fun SettingsIcon(danger: Boolean = false, content: @Composable () -> Unit) { CompositionLocalProvider( @@ -454,4 +434,4 @@ fun SettingsIcon(danger: Boolean = false, content: @Composable () -> Unit) { ) { content() } -} \ No newline at end of file +} diff --git a/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt b/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt index 68b33cfa..00ec1617 100644 --- a/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt +++ b/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt @@ -4,11 +4,12 @@ import android.app.Application import android.util.Log import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api @@ -311,47 +312,58 @@ fun MemberListSheet( ) } - is MemberListSheetItem.MemberItem -> item(key = item.member.id!!.user) { - MemberListItem( - user = StoatAPI.userCache[item.member.id!!.user], - member = item.member, - serverId = serverId, - userId = item.member.id!!.user, - modifier = Modifier - .combinedClickable( - onClick = { - userInfoSheetTarget = item.member.id!!.user - showUserInfoSheet = true - }, - onClickLabel = stringResource(R.string.user_info_sheet_open), - onLongClick = { - memberContextSheetTarget = item.member.id!!.user - showMemberContextSheet = true - }, - onLongClickLabel = stringResource(R.string.member_context_sheet_open) - ) - ) + is MemberListSheetItem.MemberItem -> { + val isFirst = index == 0 || viewModel.fullItemList[index - 1] is MemberListSheetItem.CategoryItem + val isLast = index == viewModel.fullItemList.size - 1 || viewModel.fullItemList[index + 1] is MemberListSheetItem.CategoryItem + item(key = item.member.id!!.user) { + MemberListItem( + user = StoatAPI.userCache[item.member.id!!.user], + member = item.member, + serverId = serverId, + userId = item.member.id!!.user, + first = isFirst, + last = isLast, + onClick = { + userInfoSheetTarget = item.member.id!!.user + showUserInfoSheet = true + }, + onLongClick = { + memberContextSheetTarget = item.member.id!!.user + showMemberContextSheet = true + }, + modifier = Modifier.padding(horizontal = 16.dp) + ) + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } + } } - is MemberListSheetItem.UserItem -> item(key = item.user.id!!) { - MemberListItem( - user = item.user, - member = null, - serverId = serverId, - userId = item.user.id!!, - modifier = Modifier.combinedClickable( + is MemberListSheetItem.UserItem -> { + val isFirst = index == 0 || viewModel.fullItemList[index - 1] is MemberListSheetItem.CategoryItem + val isLast = index == viewModel.fullItemList.size - 1 || viewModel.fullItemList[index + 1] is MemberListSheetItem.CategoryItem + item(key = item.user.id!!) { + MemberListItem( + user = item.user, + member = null, + serverId = serverId, + userId = item.user.id!!, + first = isFirst, + last = isLast, onClick = { userInfoSheetTarget = item.user.id!! showUserInfoSheet = true }, - onClickLabel = stringResource(R.string.user_info_sheet_open), onLongClick = { memberContextSheetTarget = item.user.id!! showMemberContextSheet = true }, - onLongClickLabel = stringResource(R.string.member_context_sheet_open) + modifier = Modifier.padding(horizontal = 16.dp) ) - ) + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } + } } } } diff --git a/app/src/main/java/chat/stoat/sheets/ReactionInfoSheet.kt b/app/src/main/java/chat/stoat/sheets/ReactionInfoSheet.kt index e74d8f79..46ba8813 100644 --- a/app/src/main/java/chat/stoat/sheets/ReactionInfoSheet.kt +++ b/app/src/main/java/chat/stoat/sheets/ReactionInfoSheet.kt @@ -14,7 +14,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider @@ -218,9 +218,9 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) { verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding( top = 16.dp, - start = 16.dp, - end = 16.dp, - bottom = 4.dp + start = 24.dp, + end = 24.dp, + bottom = 16.dp ), ) { Row( @@ -308,15 +308,14 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) { ) } } - - HorizontalDivider() } } } val reactionsForEmoji = reactions?.get(reactionEmoji[selectedReactionIndex]) ?: emptyList() - items(items = reactionsForEmoji) { reaction -> + itemsIndexed(items = reactionsForEmoji) { index, reaction -> + val isLast = index == reactionsForEmoji.size - 1 val userOrNull = StoatAPI.userCache[reaction] val user = userOrNull ?: User.getPlaceholder(reaction) val member = if (channel.server != null && user.id != null) { @@ -340,7 +339,13 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) { user = user, serverId = channel.server, userId = reaction, + first = index == 0, + last = isLast, + modifier = Modifier.padding(horizontal = 16.dp) ) + if (!isLast) { + Spacer(modifier = Modifier.height(2.dp)) + } } item("bottom") {