Refactoring of class names and breaking down bigger files into many

This commit is contained in:
Vitor Pamplona
2025-11-05 17:48:16 -05:00
parent 83f460c01d
commit 4535966c32
9 changed files with 742 additions and 612 deletions

View File

@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
@@ -43,10 +42,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults.cardElevation
@@ -97,7 +94,6 @@ import com.vitorpamplona.amethyst.ui.note.AboutDisplay
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.note.ClearTextIcon
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UserComposeNoAction
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.AnimateOnNewSearch
@@ -107,9 +103,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.HalfHalfHorzModifier
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
import com.vitorpamplona.amethyst.ui.theme.LightRedColor
import com.vitorpamplona.amethyst.ui.theme.PopupUpEffect
@@ -120,13 +113,11 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.lang.reflect.Modifier.isPrivate
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -219,7 +210,7 @@ private fun ListViewAndEditColumn(
nav: INav,
) {
Column(modifier = modifier) {
FollowSetListView(
PeopleListPager(
viewModel = viewModel,
pagerState = pagerState,
modifier = Modifier.weight(1f),
@@ -312,7 +303,7 @@ private fun RenderAddUserFieldAndSuggestions(
}
@Composable
private fun FollowSetListView(
private fun PeopleListPager(
viewModel: PeopleListViewModel,
pagerState: PagerState,
modifier: Modifier,
@@ -325,7 +316,7 @@ private fun FollowSetListView(
HorizontalPager(state = pagerState, modifier) { page ->
when (page) {
0 ->
FollowSetListView(
PeopleListView(
memberList = selectedSet.privateMembersList,
onDeleteUser = { user ->
onDeleteUser(user, true)
@@ -336,7 +327,7 @@ private fun FollowSetListView(
)
1 ->
FollowSetListView(
PeopleListView(
memberList = selectedSet.publicMembersList,
onDeleteUser = { user ->
onDeleteUser(user, false)
@@ -361,7 +352,7 @@ fun FollowSetListViewPreview() {
ThemeComparisonRow {
Column {
FollowSetListView(
PeopleListView(
memberList = persistentListOf(user1, user2, user3),
onDeleteUser = { user -> },
accountViewModel = accountViewModel,
@@ -419,33 +410,6 @@ fun FollowSetListViewPreview() {
}
}
@Composable
private fun FollowSetListView(
memberList: ImmutableList<User>,
modifier: Modifier = Modifier,
onDeleteUser: (User) -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
) {
val listState = rememberLazyListState()
LazyColumn(
modifier = modifier,
contentPadding = FeedPadding,
state = listState,
) {
itemsIndexed(memberList, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
FollowSetListItem(
modifier = Modifier.animateContentSize(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
}
}
}
@Composable
fun ShowUserSuggestions(
userSuggestions: UserSuggestionState,
@@ -569,47 +533,6 @@ fun RowScope.HasUserTag(
}
}
@Composable
fun FollowSetListItem(
modifier: Modifier = Modifier,
user: User,
accountViewModel: AccountViewModel,
nav: INav,
onDeleteUser: (User) -> Unit,
) {
Column(
modifier = modifier,
) {
Row(HalfHalfHorzModifier) {
UserComposeNoAction(
user,
modifier = HalfPadding.weight(1f, fill = false),
accountViewModel = accountViewModel,
nav = nav,
)
IconButton(
onClick = {
onDeleteUser(user)
},
modifier =
HalfPadding
.align(Alignment.CenterVertically)
.background(
color = MaterialTheme.colorScheme.errorContainer,
shape = RoundedCornerShape(percent = 80),
),
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
modifier = Modifier.size(20.dp),
)
}
}
HorizontalDivider(thickness = DividerThickness)
}
}
@Composable
fun ListActionsMenuButton(
viewModel: PeopleListViewModel,

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.UserComposeNoAction
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.PeopleListItem
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.HalfHalfHorzModifier
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import kotlinx.collections.immutable.ImmutableList
@Composable
fun PeopleListView(
memberList: ImmutableList<User>,
modifier: Modifier = Modifier,
onDeleteUser: (User) -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
) {
val listState = rememberLazyListState()
LazyColumn(
modifier = modifier,
contentPadding = FeedPadding,
state = listState,
) {
itemsIndexed(memberList, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
PeopleListItem(
modifier = Modifier.animateContentSize(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
}
}
}
@Composable
fun PeopleListItem(
modifier: Modifier = Modifier,
user: User,
accountViewModel: AccountViewModel,
nav: INav,
onDeleteUser: (User) -> Unit,
) {
Column(
modifier = modifier,
) {
Row(HalfHalfHorzModifier) {
UserComposeNoAction(
user,
modifier = HalfPadding.weight(1f, fill = false),
accountViewModel = accountViewModel,
nav = nav,
)
IconButton(
onClick = {
onDeleteUser(user)
},
modifier =
HalfPadding
.align(Alignment.CenterVertically)
.background(
color = MaterialTheme.colorScheme.errorContainer,
shape = RoundedCornerShape(percent = 80),
),
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
modifier = Modifier.size(20.dp),
)
}
}
HorizontalDivider(thickness = DividerThickness)
}
}

View File

@@ -20,29 +20,21 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
@@ -50,7 +42,6 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import kotlinx.coroutines.flow.StateFlow
@Composable
@@ -184,70 +175,3 @@ private fun PeopleListFabsAndMenu(onAddSet: (name: String, description: String?)
)
}
}
@Composable
fun NewPeopleListCreationDialog(
modifier: Modifier = Modifier,
onDismiss: () -> Unit,
onCreateList: (name: String, description: String?) -> Unit,
) {
val newListName = remember { mutableStateOf("") }
val newListDescription = remember { mutableStateOf<String?>(null) }
AlertDialog(
onDismissRequest = onDismiss,
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringRes(R.string.follow_set_creation_dialog_title),
)
}
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(5.dp),
) {
// For the new list name
TextField(
value = newListName.value,
onValueChange = { newListName.value = it },
label = {
Text(text = stringRes(R.string.follow_set_creation_name_label))
},
)
Spacer(modifier = DoubleVertSpacer)
// For the set description
TextField(
value =
(
if (newListDescription.value != null) newListDescription.value else ""
).toString(),
onValueChange = { newListDescription.value = it },
label = {
Text(text = stringRes(R.string.follow_set_creation_desc_label))
},
)
}
},
confirmButton = {
Button(
onClick = {
onCreateList(newListName.value, newListDescription.value)
onDismiss()
},
) {
Text(stringRes(R.string.follow_set_creation_action_btn_label))
}
},
dismissButton = {
Button(
onClick = onDismiss,
) {
Text(stringRes(R.string.cancel))
}
},
)
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
@Composable
fun NewPeopleListCreationDialog(
modifier: Modifier = Modifier,
onDismiss: () -> Unit,
onCreateList: (name: String, description: String?) -> Unit,
) {
val newListName = remember { mutableStateOf("") }
val newListDescription = remember { mutableStateOf<String?>(null) }
AlertDialog(
modifier = modifier,
onDismissRequest = onDismiss,
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringRes(R.string.follow_set_creation_dialog_title),
)
}
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(5.dp),
) {
// For the new list name
TextField(
value = newListName.value,
onValueChange = { newListName.value = it },
label = {
Text(text = stringRes(R.string.follow_set_creation_name_label))
},
)
Spacer(modifier = DoubleVertSpacer)
// For the set description
TextField(
value =
(
if (newListDescription.value != null) newListDescription.value else ""
).toString(),
onValueChange = { newListDescription.value = it },
label = {
Text(text = stringRes(R.string.follow_set_creation_desc_label))
},
)
}
},
confirmButton = {
Button(
onClick = {
onCreateList(newListName.value, newListDescription.value)
onDismiss()
},
) {
Text(stringRes(R.string.follow_set_creation_action_btn_label))
}
},
dismissButton = {
Button(
onClick = onDismiss,
) {
Text(stringRes(R.string.cancel))
}
},
)
}

View File

@@ -90,7 +90,7 @@ private fun PeopleListItemPreview() {
val samplePeopleList2 =
PeopleList(
identifierTag = "00001-2222",
identifierTag = "00001-2223",
title = "Sample List Title",
description = "Sample List Description",
setOf(user1, user3),
@@ -99,7 +99,7 @@ private fun PeopleListItemPreview() {
val samplePeopleList3 =
PeopleList(
identifierTag = "00001-2222",
identifierTag = "00001-2224",
title = "Sample List Title",
description = "Sample List Description",
emptySet(),
@@ -108,7 +108,7 @@ private fun PeopleListItemPreview() {
val samplePeopleList4 =
PeopleList(
identifierTag = "00001-2222",
identifierTag = "00001-2225",
title = "Sample List Title",
description = "Sample List Description",
setOf(user3),

View File

@@ -1,451 +0,0 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.recalculateWindowInsets
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material.icons.filled.PersonAdd
import androidx.compose.material.icons.filled.PersonRemove
import androidx.compose.material.icons.outlined.Groups
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.RemoveCircleOutline
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.DisplayParticipantNumberAndStatus
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.NewPeopleListCreationDialog
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.HalfHalfVertPadding
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size50ModifierOffset10
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditPeopleListScreen(
userToAddOrRemove: HexKey,
accountViewModel: AccountViewModel,
nav: INav,
) {
var userBase by remember { mutableStateOf(LocalCache.getUserIfExists(userToAddOrRemove)) }
if (userBase == null) {
LaunchedEffect(userToAddOrRemove) {
val newUserBase = LocalCache.checkGetOrCreateUser(userToAddOrRemove)
if (newUserBase != userBase) {
userBase = newUserBase
}
}
}
userBase?.let {
EditPeopleListScreen(it, accountViewModel, nav)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditPeopleListScreen(
userToAddOrRemove: User,
accountViewModel: AccountViewModel,
nav: INav,
) {
Scaffold(
modifier =
Modifier
.fillMaxSize()
.recalculateWindowInsets(),
floatingActionButton = {
PeopleListFabsAndMenu(accountViewModel)
},
topBar = {
TopBarWithBackButton(stringRes(id = R.string.follow_set_man_dialog_title2, userToAddOrRemove.toBestDisplayName()), nav::popBack)
},
) { contentPadding ->
Column(
modifier =
Modifier
.padding(
top = contentPadding.calculateTopPadding(),
bottom = contentPadding.calculateBottomPadding(),
).consumeWindowInsets(contentPadding)
.imePadding(),
) {
FollowSetManagementScreenBody(userToAddOrRemove, accountViewModel, nav)
}
}
}
@Composable
private fun PeopleListFabsAndMenu(accountViewModel: AccountViewModel) {
var isOpen by remember { mutableStateOf(false) }
ExtendedFloatingActionButton(
text = {
Text(text = stringRes(R.string.follow_set_create_btn_label))
},
icon = {
Icon(
imageVector = Icons.AutoMirrored.Filled.PlaylistAdd,
contentDescription = null,
)
},
onClick = { isOpen = !isOpen },
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
)
if (isOpen) {
NewPeopleListCreationDialog(
onDismiss = {
isOpen = false
},
onCreateList = { name, description ->
accountViewModel.runIOCatching {
accountViewModel.account.peopleLists.addFollowList(
listName = name,
listDescription = description,
account = accountViewModel.account,
)
}
isOpen = false
},
)
}
}
@Composable
private fun FollowSetManagementScreenBody(
userToAddOrRemove: User,
accountViewModel: AccountViewModel,
nav: INav,
) {
val followSetsState by accountViewModel.account.peopleLists.uiListFlow
.collectAsStateWithLifecycle()
if (followSetsState.isEmpty()) {
EmptyOrNoneFound()
} else {
val userName by observeUserName(userToAddOrRemove, accountViewModel)
LazyColumn(modifier = Modifier.fillMaxWidth()) {
itemsIndexed(followSetsState, key = { _, item -> item.identifierTag }) { _, list ->
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = list.title,
listDescription = list.description ?: "",
userName = userName,
userIsPrivateMember = list.privateMembers.contains(userToAddOrRemove),
userIsPublicMember = list.publicMembers.contains(userToAddOrRemove),
onRemoveUser = {
accountViewModel.runIOCatching {
accountViewModel.account.peopleLists.removeUserFromSet(
userToAddOrRemove,
isPrivate = list.privateMembers.contains(userToAddOrRemove),
list.identifierTag,
accountViewModel.account,
)
}
},
privateMemberSize = list.privateMembers.size,
publicMemberSize = list.publicMembers.size,
onAddUserToList = { userShouldBePrivate ->
accountViewModel.runIOCatching {
accountViewModel.account.peopleLists.addUserToSet(
userToAddOrRemove,
list.identifierTag,
userShouldBePrivate,
accountViewModel.account,
)
}
},
)
HorizontalDivider(thickness = DividerThickness)
}
}
}
}
@Composable
private fun EmptyOrNoneFound() {
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(0.5f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(text = stringRes(R.string.follow_set_empty_dialog_msg))
Spacer(modifier = StdVertSpacer)
}
}
@Preview
@Composable
fun FollowSetItemMemberPreview() {
ThemeComparisonColumn {
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = "list title",
listDescription = "desc",
userName = "User",
userIsPrivateMember = true,
userIsPublicMember = true,
privateMemberSize = 3,
publicMemberSize = 2,
onAddUserToList = {},
onRemoveUser = {},
)
}
}
@Preview
@Composable
fun FollowSetItemNotMemberPreview() {
ThemeComparisonColumn {
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = "list title",
listDescription = "desc",
userName = "User",
userIsPrivateMember = false,
userIsPublicMember = false,
privateMemberSize = 3,
publicMemberSize = 2,
onAddUserToList = {},
onRemoveUser = {},
)
}
}
@Composable
fun FollowSetItem(
modifier: Modifier = Modifier,
listHeader: String,
listDescription: String,
userName: String,
userIsPrivateMember: Boolean,
userIsPublicMember: Boolean,
publicMemberSize: Int,
privateMemberSize: Int,
onAddUserToList: (shouldBePrivateMember: Boolean) -> Unit,
onRemoveUser: () -> Unit,
) {
val isUserInList = userIsPrivateMember || userIsPublicMember
ListItem(
modifier = modifier,
headlineContent = {
Text(listHeader, maxLines = 1, overflow = TextOverflow.Ellipsis)
},
supportingContent = {
Row(
modifier = HalfHalfVertPadding,
horizontalArrangement = SpacedBy5dp,
verticalAlignment = Alignment.CenterVertically,
) {
val text =
if (isUserInList) {
if (userIsPublicMember) {
stringRes(R.string.follow_set_public_presence_indicator, userName)
} else {
stringRes(R.string.follow_set_private_presence_indicator, userName)
}
} else {
stringRes(R.string.follow_set_absence_indicator2, userName)
}
if (isUserInList) {
if (userIsPublicMember) {
Icon(
imageVector = Icons.Outlined.Public,
contentDescription = text,
modifier = Size15Modifier,
tint = MaterialTheme.colorScheme.primary,
)
} else if (userIsPrivateMember) {
Icon(
imageVector = Icons.Outlined.Lock,
contentDescription = text,
modifier = Size15Modifier,
tint = MaterialTheme.colorScheme.primary,
)
}
} else {
Icon(
imageVector = Icons.Outlined.RemoveCircleOutline,
contentDescription = text,
modifier = Size15Modifier,
)
}
Text(
text = text,
overflow = TextOverflow.MiddleEllipsis,
maxLines = 1,
)
}
},
leadingContent = {
Box(contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Outlined.Groups,
contentDescription = stringRes(R.string.follow_set_icon_description),
modifier = Size50ModifierOffset10,
)
DisplayParticipantNumberAndStatus(
modifier = Modifier.align(Alignment.BottomCenter),
privateMembersSize = privateMemberSize,
publicMembersSize = publicMemberSize,
)
}
},
trailingContent = {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val isUserAddTapped = remember { mutableStateOf(false) }
IconButton(
onClick = {
if (isUserInList) {
onRemoveUser()
} else {
isUserAddTapped.value = true
}
},
modifier =
Modifier
.background(
color =
if (isUserInList) {
MaterialTheme.colorScheme.errorContainer
} else {
MaterialTheme.colorScheme.primary
},
shape = RoundedCornerShape(percent = 80),
),
) {
if (isUserInList) {
Icon(
imageVector = Icons.Filled.PersonRemove,
contentDescription = stringRes(R.string.remove_user_from_the_list),
tint = MaterialTheme.colorScheme.onBackground,
)
} else {
Icon(
imageVector = Icons.Filled.PersonAdd,
contentDescription = stringRes(R.string.add_user_to_the_list),
tint = Color.White,
)
}
}
UserAdditionOptionsMenu(
isExpanded = isUserAddTapped.value,
onUserAdd = { shouldBePrivateMember ->
onAddUserToList(shouldBePrivateMember)
},
onDismiss = { isUserAddTapped.value = false },
)
}
},
)
}
@Composable
private fun UserAdditionOptionsMenu(
isExpanded: Boolean,
onUserAdd: (asPrivateMember: Boolean) -> Unit,
onDismiss: () -> Unit,
) {
DropdownMenu(
expanded = isExpanded,
onDismissRequest = onDismiss,
) {
DropdownMenuItem(
text = {
Text(text = stringRes(R.string.follow_set_public_member_add_label))
},
onClick = {
onUserAdd(false)
onDismiss()
},
)
DropdownMenuItem(
text = {
Text(text = stringRes(R.string.follow_set_private_member_add_label))
},
onClick = {
onUserAdd(true)
onDismiss()
},
)
}
}

View File

@@ -0,0 +1,255 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PersonAdd
import androidx.compose.material.icons.filled.PersonRemove
import androidx.compose.material.icons.outlined.Groups
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.RemoveCircleOutline
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.DisplayParticipantNumberAndStatus
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.HalfHalfVertPadding
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size50ModifierOffset10
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
@Preview
@Composable
fun PeopleListAndUserMemberPreview() {
ThemeComparisonColumn {
PeopleListAndUserItem(
modifier = Modifier.fillMaxWidth(),
listHeader = "list title",
userName = "User",
userIsPrivateMember = true,
userIsPublicMember = true,
privateMemberSize = 3,
publicMemberSize = 2,
onAddUserToList = {},
onRemoveUser = {},
)
}
}
@Preview
@Composable
fun PeopleListAndUserNotMemberPreview() {
ThemeComparisonColumn {
PeopleListAndUserItem(
modifier = Modifier.fillMaxWidth(),
listHeader = "list title",
userName = "User",
userIsPrivateMember = false,
userIsPublicMember = false,
privateMemberSize = 3,
publicMemberSize = 2,
onAddUserToList = {},
onRemoveUser = {},
)
}
}
@Composable
fun PeopleListAndUserItem(
modifier: Modifier = Modifier,
listHeader: String,
userName: String,
userIsPrivateMember: Boolean,
userIsPublicMember: Boolean,
publicMemberSize: Int,
privateMemberSize: Int,
onAddUserToList: (shouldBePrivateMember: Boolean) -> Unit,
onRemoveUser: () -> Unit,
) {
ListItem(
modifier = modifier,
headlineContent = {
Text(
text = listHeader,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
supportingContent = {
UserStatusInList(userName, userIsPrivateMember, userIsPublicMember)
},
leadingContent = {
Box(contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Outlined.Groups,
contentDescription = stringRes(R.string.follow_set_icon_description),
modifier = Size50ModifierOffset10,
)
DisplayParticipantNumberAndStatus(
modifier = Modifier.align(Alignment.BottomCenter),
privateMembersSize = privateMemberSize,
publicMembersSize = publicMemberSize,
)
}
},
trailingContent = {
val isUserInList = userIsPrivateMember || userIsPublicMember
UserAdditionOptions(isUserInList, onAddUserToList, onRemoveUser)
},
)
}
@Composable
fun UserStatusInList(
userName: String,
userIsPrivateMember: Boolean,
userIsPublicMember: Boolean,
) {
Row(
modifier = HalfHalfVertPadding,
horizontalArrangement = SpacedBy5dp,
verticalAlignment = Alignment.CenterVertically,
) {
val text =
if (userIsPublicMember) {
stringRes(R.string.follow_set_public_presence_indicator, userName)
} else if (userIsPrivateMember) {
stringRes(R.string.follow_set_private_presence_indicator, userName)
} else {
stringRes(R.string.follow_set_absence_indicator2, userName)
}
val icon =
if (userIsPublicMember) {
Icons.Outlined.Public
} else if (userIsPrivateMember) {
Icons.Outlined.Lock
} else {
Icons.Outlined.RemoveCircleOutline
}
Icon(
imageVector = icon,
contentDescription = text,
modifier = Size15Modifier,
tint = MaterialTheme.colorScheme.primary,
)
Text(
text = text,
overflow = TextOverflow.MiddleEllipsis,
maxLines = 1,
)
}
}
@Composable
private fun UserAdditionOptions(
isUserInList: Boolean,
onAddUserToList: (asPrivateMember: Boolean) -> Unit,
onRemoveUser: () -> Unit,
) {
val isUserAddTapped = remember { mutableStateOf(false) }
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconButton(
onClick = {
if (isUserInList) {
onRemoveUser()
} else {
isUserAddTapped.value = true
}
},
modifier =
Modifier
.background(
color =
if (isUserInList) {
MaterialTheme.colorScheme.errorContainer
} else {
MaterialTheme.colorScheme.primary
},
shape = RoundedCornerShape(percent = 80),
),
) {
if (isUserInList) {
Icon(
imageVector = Icons.Filled.PersonRemove,
contentDescription = stringRes(R.string.remove_user_from_the_list),
tint = MaterialTheme.colorScheme.onErrorContainer,
)
} else {
Icon(
imageVector = Icons.Filled.PersonAdd,
contentDescription = stringRes(R.string.add_user_to_the_list),
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
DropdownMenu(
expanded = isUserAddTapped.value,
onDismissRequest = { isUserAddTapped.value = false },
) {
DropdownMenuItem(
text = {
Text(text = stringRes(R.string.follow_set_public_member_add_label))
},
onClick = {
onAddUserToList(false)
isUserAddTapped.value = false
},
)
DropdownMenuItem(
text = {
Text(text = stringRes(R.string.follow_set_private_member_add_label))
},
onClick = {
onAddUserToList(true)
isUserAddTapped.value = false
},
)
}
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.recalculateWindowInsets
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.NewPeopleListCreationDialog
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditPeopleListScreen(
userToAddOrRemove: HexKey,
accountViewModel: AccountViewModel,
nav: INav,
) {
var userBase by remember { mutableStateOf(LocalCache.getUserIfExists(userToAddOrRemove)) }
if (userBase == null) {
LaunchedEffect(userToAddOrRemove) {
val newUserBase = LocalCache.checkGetOrCreateUser(userToAddOrRemove)
if (newUserBase != userBase) {
userBase = newUserBase
}
}
}
userBase?.let {
EditPeopleListScreen(it, accountViewModel, nav)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditPeopleListScreen(
userToAddOrRemove: User,
accountViewModel: AccountViewModel,
nav: INav,
) {
Scaffold(
modifier = Modifier.fillMaxSize().recalculateWindowInsets(),
floatingActionButton = {
PeopleListAndUserFab(accountViewModel)
},
topBar = {
val userName by observeUserName(userToAddOrRemove, accountViewModel)
TopBarWithBackButton(
caption = stringRes(id = R.string.follow_set_man_dialog_title2, userName),
popBack = nav::popBack,
)
},
) { contentPadding ->
Column(
modifier =
Modifier
.padding(
top = contentPadding.calculateTopPadding(),
bottom = contentPadding.calculateBottomPadding(),
).consumeWindowInsets(contentPadding)
.imePadding(),
) {
PeopleListAndUserView(userToAddOrRemove, accountViewModel, nav)
}
}
}
@Composable
private fun PeopleListAndUserFab(accountViewModel: AccountViewModel) {
var isOpen by remember { mutableStateOf(false) }
ExtendedFloatingActionButton(
text = {
Text(text = stringRes(R.string.follow_set_create_btn_label))
},
icon = {
Icon(
imageVector = Icons.AutoMirrored.Filled.PlaylistAdd,
contentDescription = null,
)
},
onClick = { isOpen = !isOpen },
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
)
if (isOpen) {
NewPeopleListCreationDialog(
onDismiss = {
isOpen = false
},
onCreateList = { name, description ->
accountViewModel.runIOCatching {
accountViewModel.account.peopleLists.addFollowList(
listName = name,
listDescription = description,
account = accountViewModel.account,
)
}
isOpen = false
},
)
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
@Composable
fun PeopleListAndUserView(
userToAddOrRemove: User,
accountViewModel: AccountViewModel,
nav: INav,
) {
val followSetsState by accountViewModel.account.peopleLists.uiListFlow
.collectAsStateWithLifecycle()
if (followSetsState.isEmpty()) {
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(0.5f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(text = stringRes(R.string.follow_set_empty_dialog_msg))
Spacer(modifier = StdVertSpacer)
}
} else {
val userName by observeUserName(userToAddOrRemove, accountViewModel)
LazyColumn(modifier = Modifier.fillMaxWidth()) {
itemsIndexed(followSetsState, key = { _, item -> item.identifierTag }) { _, list ->
PeopleListAndUserItem(
modifier = Modifier.fillMaxWidth(),
listHeader = list.title,
userName = userName,
userIsPrivateMember = list.privateMembers.contains(userToAddOrRemove),
userIsPublicMember = list.publicMembers.contains(userToAddOrRemove),
onRemoveUser = {
accountViewModel.runIOCatching {
accountViewModel.account.peopleLists.removeUserFromSet(
userToAddOrRemove,
isPrivate = list.privateMembers.contains(userToAddOrRemove),
list.identifierTag,
accountViewModel.account,
)
}
},
privateMemberSize = list.privateMembers.size,
publicMemberSize = list.publicMembers.size,
onAddUserToList = { userShouldBePrivate ->
accountViewModel.runIOCatching {
accountViewModel.account.peopleLists.addUserToSet(
userToAddOrRemove,
list.identifierTag,
userShouldBePrivate,
accountViewModel.account,
)
}
},
)
HorizontalDivider(thickness = DividerThickness)
}
}
}
}