Refactoring of class names and breaking down bigger files into many
This commit is contained in:
+5
-82
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
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.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Cancel
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults.cardElevation
|
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.ArrowBackIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClearTextIcon
|
import com.vitorpamplona.amethyst.ui.note.ClearTextIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
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.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.AnimateOnNewSearch
|
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.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
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.HalfHalfHorzModifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.LightRedColor
|
import com.vitorpamplona.amethyst.ui.theme.LightRedColor
|
||||||
import com.vitorpamplona.amethyst.ui.theme.PopupUpEffect
|
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.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.lang.reflect.Modifier.isPrivate
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -219,7 +210,7 @@ private fun ListViewAndEditColumn(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
FollowSetListView(
|
PeopleListPager(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
@@ -312,7 +303,7 @@ private fun RenderAddUserFieldAndSuggestions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FollowSetListView(
|
private fun PeopleListPager(
|
||||||
viewModel: PeopleListViewModel,
|
viewModel: PeopleListViewModel,
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
@@ -325,7 +316,7 @@ private fun FollowSetListView(
|
|||||||
HorizontalPager(state = pagerState, modifier) { page ->
|
HorizontalPager(state = pagerState, modifier) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 ->
|
0 ->
|
||||||
FollowSetListView(
|
PeopleListView(
|
||||||
memberList = selectedSet.privateMembersList,
|
memberList = selectedSet.privateMembersList,
|
||||||
onDeleteUser = { user ->
|
onDeleteUser = { user ->
|
||||||
onDeleteUser(user, true)
|
onDeleteUser(user, true)
|
||||||
@@ -336,7 +327,7 @@ private fun FollowSetListView(
|
|||||||
)
|
)
|
||||||
|
|
||||||
1 ->
|
1 ->
|
||||||
FollowSetListView(
|
PeopleListView(
|
||||||
memberList = selectedSet.publicMembersList,
|
memberList = selectedSet.publicMembersList,
|
||||||
onDeleteUser = { user ->
|
onDeleteUser = { user ->
|
||||||
onDeleteUser(user, false)
|
onDeleteUser(user, false)
|
||||||
@@ -361,7 +352,7 @@ fun FollowSetListViewPreview() {
|
|||||||
|
|
||||||
ThemeComparisonRow {
|
ThemeComparisonRow {
|
||||||
Column {
|
Column {
|
||||||
FollowSetListView(
|
PeopleListView(
|
||||||
memberList = persistentListOf(user1, user2, user3),
|
memberList = persistentListOf(user1, user2, user3),
|
||||||
onDeleteUser = { user -> },
|
onDeleteUser = { user -> },
|
||||||
accountViewModel = accountViewModel,
|
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
|
@Composable
|
||||||
fun ShowUserSuggestions(
|
fun ShowUserSuggestions(
|
||||||
userSuggestions: UserSuggestionState,
|
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
|
@Composable
|
||||||
fun ListActionsMenuButton(
|
fun ListActionsMenuButton(
|
||||||
viewModel: PeopleListViewModel,
|
viewModel: PeopleListViewModel,
|
||||||
|
|||||||
+119
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
-76
@@ -20,29 +20,21 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list
|
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.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
|
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.ExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
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 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
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
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.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 kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
@Composable
|
@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))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
+107
@@ -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))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
+3
-3
@@ -90,7 +90,7 @@ private fun PeopleListItemPreview() {
|
|||||||
|
|
||||||
val samplePeopleList2 =
|
val samplePeopleList2 =
|
||||||
PeopleList(
|
PeopleList(
|
||||||
identifierTag = "00001-2222",
|
identifierTag = "00001-2223",
|
||||||
title = "Sample List Title",
|
title = "Sample List Title",
|
||||||
description = "Sample List Description",
|
description = "Sample List Description",
|
||||||
setOf(user1, user3),
|
setOf(user1, user3),
|
||||||
@@ -99,7 +99,7 @@ private fun PeopleListItemPreview() {
|
|||||||
|
|
||||||
val samplePeopleList3 =
|
val samplePeopleList3 =
|
||||||
PeopleList(
|
PeopleList(
|
||||||
identifierTag = "00001-2222",
|
identifierTag = "00001-2224",
|
||||||
title = "Sample List Title",
|
title = "Sample List Title",
|
||||||
description = "Sample List Description",
|
description = "Sample List Description",
|
||||||
emptySet(),
|
emptySet(),
|
||||||
@@ -108,7 +108,7 @@ private fun PeopleListItemPreview() {
|
|||||||
|
|
||||||
val samplePeopleList4 =
|
val samplePeopleList4 =
|
||||||
PeopleList(
|
PeopleList(
|
||||||
identifierTag = "00001-2222",
|
identifierTag = "00001-2225",
|
||||||
title = "Sample List Title",
|
title = "Sample List Title",
|
||||||
description = "Sample List Description",
|
description = "Sample List Description",
|
||||||
setOf(user3),
|
setOf(user3),
|
||||||
|
|||||||
-451
@@ -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()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+255
@@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+149
@@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+104
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user