Improves the look of the list of lists of people screen

This commit is contained in:
Vitor Pamplona
2025-11-05 14:11:15 -05:00
parent 59cdb222d5
commit 8168d353dd
5 changed files with 180 additions and 142 deletions

View File

@@ -40,7 +40,7 @@ import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size30dp import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -83,7 +83,7 @@ fun AllPeopleListFeedView(
@Composable @Composable
fun AllPeopleListFeedEmpty(message: String = stringRes(R.string.feed_is_empty)) { fun AllPeopleListFeedEmpty(message: String = stringRes(R.string.feed_is_empty)) {
Column( Column(
Modifier.fillMaxSize().padding(horizontal = Size30dp), Modifier.fillMaxSize().padding(horizontal = Size40dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {

View File

@@ -42,7 +42,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleList import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleList
@@ -52,7 +51,6 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@Composable @Composable
@@ -131,9 +129,7 @@ fun ListOfPeopleListsScreen(
}, },
floatingActionButton = { floatingActionButton = {
PeopleListFabsAndMenu( PeopleListFabsAndMenu(
onAddSet = { name: String, description: String? -> onAddSet = addItem,
addItem(name, description)
},
) )
}, },
) { paddingValues -> ) { paddingValues ->
@@ -142,8 +138,6 @@ fun ListOfPeopleListsScreen(
.padding( .padding(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding(), bottom = paddingValues.calculateBottomPadding(),
start = 10.dp,
end = 10.dp,
).fillMaxHeight(), ).fillMaxHeight(),
) { ) {
AllPeopleListFeedView( AllPeopleListFeedView(
@@ -257,37 +251,3 @@ fun NewPeopleListCreationDialog(
}, },
) )
} }
@Preview(showSystemUi = true)
@Composable
private fun PeopleListItemPreview() {
val samplePeopleList =
PeopleList(
identifierTag = "00001-2222",
title = "Sample List Title",
description = "Sample List Description",
emptySet(),
emptySet(),
)
ThemeComparisonColumn {
PeopleListItem(
modifier = Modifier,
samplePeopleList,
onClick = {
println("follow set: ${samplePeopleList.identifierTag}")
},
onRename = {
println("Follow set new name: $it")
},
onDescriptionChange = { description ->
println("The follow set's description has been changed to $description")
},
onClone = { newName, newDesc ->
println("The follow set has been cloned, and has custom name: $newName, Desc: $newDesc")
},
onDelete = {
println(" The follow set ${samplePeopleList.title} has been deleted.")
},
)
}
}

View File

@@ -22,18 +22,21 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.People import androidx.compose.material.icons.outlined.Groups
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -41,7 +44,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
@@ -49,19 +51,110 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleList import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.Font10SP
import com.vitorpamplona.amethyst.ui.theme.NoSoTinyBorders
import com.vitorpamplona.amethyst.ui.theme.Size10Modifier
import com.vitorpamplona.amethyst.ui.theme.Size50ModifierOffset10
import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.SpacedBy2dp
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
@Preview()
@Composable
private fun PeopleListItemPreview() {
val user1: User = LocalCache.getOrCreateUser("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
val user2: User = LocalCache.getOrCreateUser("ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b")
val user3: User = LocalCache.getOrCreateUser("7eb29c126b3628077e2e3d863b917a56b74293aa9d8a9abc26a40ba3f2866baf")
val samplePeopleList1 =
PeopleList(
identifierTag = "00001-2222",
title = "Sample List Title",
description = "Sample List Description",
emptySet(),
emptySet(),
)
val samplePeopleList2 =
PeopleList(
identifierTag = "00001-2222",
title = "Sample List Title",
description = "Sample List Description",
setOf(user1, user3),
emptySet(),
)
val samplePeopleList3 =
PeopleList(
identifierTag = "00001-2222",
title = "Sample List Title",
description = "Sample List Description",
emptySet(),
setOf(user1, user3),
)
val samplePeopleList4 =
PeopleList(
identifierTag = "00001-2222",
title = "Sample List Title",
description = "Sample List Description",
setOf(user3),
setOf(user1, user2, user3),
)
ThemeComparisonColumn {
Column {
PeopleListItem(
modifier = Modifier,
peopleList = samplePeopleList1,
onClick = {},
onRename = {},
onDescriptionChange = { },
onClone = { newName, newDesc -> },
onDelete = {},
)
PeopleListItem(
modifier = Modifier,
peopleList = samplePeopleList2,
onClick = {},
onRename = {},
onDescriptionChange = { },
onClone = { newName, newDesc -> },
onDelete = {},
)
PeopleListItem(
modifier = Modifier,
peopleList = samplePeopleList3,
onClick = {},
onRename = {},
onDescriptionChange = { },
onClone = { newName, newDesc -> },
onDelete = {},
)
PeopleListItem(
modifier = Modifier,
peopleList = samplePeopleList4,
onClick = {},
onRename = {},
onDescriptionChange = { },
onClone = { newName, newDesc -> },
onDelete = {},
)
}
}
}
@Composable @Composable
fun PeopleListItem( fun PeopleListItem(
@@ -73,100 +166,18 @@ fun PeopleListItem(
onClone: (customName: String?, customDescription: String?) -> Unit, onClone: (customName: String?, customDescription: String?) -> Unit,
onDelete: () -> Unit, onDelete: () -> Unit,
) { ) {
val context = LocalContext.current ListItem(
modifier = modifier.clickable(onClick = onClick),
headlineContent = {
Row( Row(
modifier = modifier = Modifier.fillMaxWidth(),
modifier horizontalArrangement = Arrangement.SpaceBetween,
.clickable(onClick = onClick),
) { ) {
Row( Text(peopleList.title)
modifier =
Modifier
.padding(bottom = 12.dp)
.weight(1f),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.Center,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = SpacedBy5dp,
) {
Icon(
painter = painterResource(R.drawable.format_list_bulleted_type),
contentDescription = stringRes(R.string.follow_set_icon_description),
)
Text(peopleList.title, fontWeight = FontWeight.Bold)
if (peopleList.publicMembers.isEmpty() && peopleList.privateMembers.isEmpty()) {
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = stringRes(R.string.follow_set_empty_label))
},
leadingIcon = {
Icon(
imageVector = Icons.Default.People,
contentDescription = null,
)
},
shape = ButtonBorder,
)
}
if (peopleList.publicMembers.isNotEmpty()) {
val publicMemberSize = peopleList.publicMembers.size
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "$publicMemberSize")
},
leadingIcon = {
Icon(
painterResource(R.drawable.ic_public),
contentDescription = null,
)
},
shape = ButtonBorder,
)
Spacer(modifier = StdHorzSpacer)
}
if (peopleList.privateMembers.isNotEmpty()) {
val privateMemberSize = peopleList.privateMembers.size
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "$privateMemberSize")
},
leadingIcon = {
Icon(
painterResource(R.drawable.lock),
contentDescription = null,
)
},
shape = ButtonBorder,
)
}
}
Spacer(modifier = StdVertSpacer)
Text(
peopleList.description ?: "",
fontWeight = FontWeight.Light,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
)
}
}
Column( Column(
modifier = modifier = NoSoTinyBorders,
Modifier verticalArrangement = Arrangement.Center,
.padding(start = 5.dp)
.padding(vertical = 7.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.End, horizontalAlignment = Alignment.End,
) { ) {
PeopleListOptionsButton( PeopleListOptionsButton(
@@ -179,6 +190,69 @@ fun PeopleListItem(
) )
} }
} }
},
supportingContent = {
Text(
peopleList.description ?: "",
overflow = TextOverflow.Ellipsis,
maxLines = 2,
)
},
leadingContent = {
Box(contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Outlined.Groups,
contentDescription = stringRes(R.string.follow_set_icon_description),
modifier = Size50ModifierOffset10,
)
Row(
modifier = Modifier.align(Alignment.BottomCenter).offset(y = (-5).dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = SpacedBy5dp,
) {
if (peopleList.publicMembers.isEmpty() && peopleList.privateMembers.isEmpty()) {
Text(
text = stringRes(R.string.follow_set_empty_label2),
fontSize = Font10SP,
)
} else {
if (peopleList.privateMembers.isNotEmpty()) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = SpacedBy2dp,
) {
Icon(
painterResource(R.drawable.lock),
modifier = Size10Modifier,
contentDescription = null,
)
Text(
text = peopleList.privateMembers.size.toString(),
fontSize = Font10SP,
)
}
}
if (peopleList.publicMembers.isNotEmpty()) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = SpacedBy2dp,
) {
Icon(
painterResource(R.drawable.ic_public),
modifier = Size10Modifier,
contentDescription = null,
)
Text(
text = peopleList.publicMembers.size.toString(),
fontSize = Font10SP,
)
}
}
}
}
}
},
)
} }
@Composable @Composable

View File

@@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@@ -371,3 +372,5 @@ val SpacedBy5dp = Arrangement.spacedBy(Size5dp)
val SpacedBy10dp = Arrangement.spacedBy(Size10dp) val SpacedBy10dp = Arrangement.spacedBy(Size10dp)
val PopupUpEffect = RoundedCornerShape(0.dp, 0.dp, 15.dp, 15.dp) val PopupUpEffect = RoundedCornerShape(0.dp, 0.dp, 15.dp, 15.dp)
val Size50ModifierOffset10 = Modifier.size(50.dp).offset(y = (-10).dp)

View File

@@ -536,6 +536,7 @@
<string name="follow_set_single_member_label">member</string> <string name="follow_set_single_member_label">member</string>
<string name="follow_set_multiple_member_label">members</string> <string name="follow_set_multiple_member_label">members</string>
<string name="follow_set_empty_label">No members</string> <string name="follow_set_empty_label">No members</string>
<string name="follow_set_empty_label2">Empty</string>
<string name="follow_set_absence_indicator">%1$s is not in this list</string> <string name="follow_set_absence_indicator">%1$s is not in this list</string>
<string name="follow_set_man_dialog_title">Your Lists</string> <string name="follow_set_man_dialog_title">Your Lists</string>
<string name="follow_set_empty_dialog_msg">No follow lists were found, or you don\'t have any follow lists. Tap below to refresh, or use the menu to create one.</string> <string name="follow_set_empty_dialog_msg">No follow lists were found, or you don\'t have any follow lists. Tap below to refresh, or use the menu to create one.</string>