Completely change the UI for list management when adding/removing a profile from lists.

This commit is contained in:
KotlinGeekDev
2025-09-19 18:15:18 +01:00
parent e4a8a512d2
commit e10c429de0
7 changed files with 320 additions and 317 deletions

View File

@@ -85,6 +85,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationS
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages.NewPublicMessageScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.FollowSetsManagementDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.redirect.LoadRedirectScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationScreen
@@ -130,6 +131,9 @@ fun AppNavigation(
composableArgs<Route.FollowSetRoute> {
FollowSetScreen(it.setIdentifier, accountViewModel, listsViewModel, nav)
}
composableArgs<Route.FollowSetManagement> {
FollowSetsManagementDialog(it.userHexKey, accountViewModel.account, listsViewModel, nav)
}
composable<Route.EditProfile> { NewUserMetadataScreen(nav, accountViewModel) }
composable<Route.Search> { SearchScreen(accountViewModel, nav) }
@@ -145,7 +149,7 @@ fun AppNavigation(
composableFromEndArgs<Route.EditMediaServers> { AllMediaServersScreen(accountViewModel, nav) }
composableFromEndArgs<Route.ContentDiscovery> { DvmContentDiscoveryScreen(it.id, accountViewModel, nav) }
composableFromEndArgs<Route.Profile> { ProfileScreen(it.id, accountViewModel, listsViewModel, nav) }
composableFromEndArgs<Route.Profile> { ProfileScreen(it.id, accountViewModel, nav) }
composableFromEndArgs<Route.Note> { ThreadScreen(it.id, accountViewModel, nav) }
composableFromEndArgs<Route.Hashtag> { HashtagScreen(it, accountViewModel, nav) }
composableFromEndArgs<Route.Geohash> { GeoHashScreen(it, accountViewModel, nav) }

View File

@@ -58,6 +58,10 @@ sealed class Route {
val setIdentifier: String,
) : Route()
@Serializable data class FollowSetManagement(
val userHexKey: String,
) : Route()
@Serializable object EditProfile : Route()
@Serializable object EditRelays : Route()

View File

@@ -64,7 +64,6 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observe
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.BookmarkTabHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.TabBookmarks
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.dal.UserProfileBookmarksFeedViewModel
@@ -104,7 +103,6 @@ import kotlinx.coroutines.launch
fun ProfileScreen(
userId: String?,
accountViewModel: AccountViewModel,
nostrListsViewModel: NostrUserListFeedViewModel,
nav: INav,
) {
if (userId == null) return
@@ -124,7 +122,6 @@ fun ProfileScreen(
PrepareViewModels(
baseUser = it,
accountViewModel = accountViewModel,
nostrListsViewModel = nostrListsViewModel,
nav = nav,
)
}
@@ -134,7 +131,6 @@ fun ProfileScreen(
fun PrepareViewModels(
baseUser: User,
accountViewModel: AccountViewModel,
nostrListsViewModel: NostrUserListFeedViewModel,
nav: INav,
) {
val followsFeedViewModel: UserProfileFollowsUserFeedViewModel =
@@ -243,7 +239,6 @@ fun PrepareViewModels(
bookmarksFeedViewModel,
galleryFeedViewModel,
reportsFeedViewModel,
nostrListsViewModel,
accountViewModel = accountViewModel,
nav = nav,
)
@@ -262,7 +257,6 @@ fun ProfileScreen(
bookmarksFeedViewModel: UserProfileBookmarksFeedViewModel,
galleryFeedViewModel: UserProfileGalleryFeedViewModel,
reportsFeedViewModel: UserProfileReportFeedViewModel,
followSetsViewModel: NostrUserListFeedViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
@@ -275,7 +269,6 @@ fun ProfileScreen(
WatchLifecycleAndUpdateModel(bookmarksFeedViewModel)
WatchLifecycleAndUpdateModel(galleryFeedViewModel)
WatchLifecycleAndUpdateModel(reportsFeedViewModel)
WatchLifecycleAndUpdateModel(followSetsViewModel)
UserProfileFilterAssemblerSubscription(baseUser, accountViewModel.dataSources().profile)
@@ -294,7 +287,6 @@ fun ProfileScreen(
bookmarksFeedViewModel,
galleryFeedViewModel,
reportsFeedViewModel,
followSetsViewModel,
accountViewModel,
nav,
)
@@ -388,14 +380,13 @@ private fun RenderScreen(
bookmarksFeedViewModel: UserProfileBookmarksFeedViewModel,
galleryFeedViewModel: UserProfileGalleryFeedViewModel,
reportsFeedViewModel: UserProfileReportFeedViewModel,
followSetsViewModel: NostrUserListFeedViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
val pagerState = rememberPagerState { 11 }
Column {
ProfileHeader(baseUser, appRecommendations, followSetsViewModel, nav, accountViewModel)
ProfileHeader(baseUser, appRecommendations, nav, accountViewModel)
ScrollableTabRow(
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,

View File

@@ -1,265 +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.profile.header
import androidx.compose.foundation.border
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAddCheck
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AssistChip
import androidx.compose.material3.AssistChipDefaults
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupProperties
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.ListVisibility
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
@Composable
fun FollowSetsActionMenu(
isMenuOpen: Boolean,
setMenuOpenState: () -> Unit,
userHex: String,
followLists: List<FollowSet>,
modifier: Modifier = Modifier,
addUser: (index: Int, userPubkey: String, list: FollowSet) -> Unit,
removeUser: (index: Int, userPubkey: String, list: FollowSet) -> Unit,
) {
Column {
TextButton(
onClick = setMenuOpenState,
shape = ButtonBorder.copy(topStart = CornerSize(0f), bottomStart = CornerSize(0f)),
colors =
ButtonDefaults
.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
contentPadding = ZeroPadding,
) {
Icon(
imageVector = if (isMenuOpen) Icons.Default.ArrowDropUp else Icons.Default.ArrowDropDown,
contentDescription = "",
)
}
DropdownMenu(
expanded = isMenuOpen,
onDismissRequest = setMenuOpenState,
modifier = Modifier.fillMaxWidth(),
properties = PopupProperties(usePlatformDefaultWidth = true),
) {
DropDownMenuHeader(headerText = "Add to lists")
followLists.forEachIndexed { index, list ->
Spacer(StdVertSpacer)
DropdownMenuItem(
text = {
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = list.title,
listVisibility = list.visibility,
isUserInList = list.profileList.contains(userHex),
onRemoveUser = {
removeUser(index, userHex, list)
},
onAddUser = {
addUser(index, userHex, list)
},
)
},
onClick = {},
modifier = Modifier.fillMaxWidth(),
)
}
}
}
}
@Composable
private fun DropDownMenuHeader(
modifier: Modifier = Modifier,
headerText: String,
) {
Column {
DropdownMenuItem(
text = {
Text(text = headerText, fontWeight = FontWeight.SemiBold)
},
onClick = {},
enabled = false,
)
HorizontalDivider()
}
}
@Composable
fun FollowSetItem(
modifier: Modifier = Modifier,
listHeader: String,
listVisibility: ListVisibility,
isUserInList: Boolean,
onAddUser: () -> Unit,
onRemoveUser: () -> Unit,
) {
Row(
modifier =
modifier
// .clickable(onClick = onAddUser)
.border(
width = Dp.Hairline,
color = Color.Gray,
shape = RoundedCornerShape(percent = 20),
).padding(all = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = modifier.weight(1f),
verticalArrangement = Arrangement.Center,
) {
Text(listHeader, fontWeight = FontWeight.Bold)
Spacer(modifier = StdVertSpacer)
Row {
FilterChip(
selected = isUserInList,
enabled = isUserInList,
onClick = {},
label = {
Text(text = if (isUserInList) "In List" else "Not in List")
},
leadingIcon =
if (isUserInList) {
{
Icon(
imageVector = Icons.AutoMirrored.Filled.PlaylistAddCheck,
contentDescription = null,
)
}
} else {
null
},
shape = ButtonBorder,
)
Spacer(modifier = StdHorzSpacer)
AssistChip(
onClick = {
if (isUserInList) onRemoveUser() else onAddUser()
},
label = {
Text(text = if (isUserInList) "Remove" else "Add")
},
leadingIcon = {
if (isUserInList) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
} else {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
}
},
shape = ButtonBorder,
colors =
AssistChipDefaults.assistChipColors(
containerColor =
if (isUserInList) {
MaterialTheme.colorScheme.errorContainer
} else {
MaterialTheme.colorScheme.primary
},
),
border =
AssistChipDefaults
.assistChipBorder(
enabled = true,
borderColor =
if (!isUserInList) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.errorContainer
},
),
)
}
}
listVisibility.let {
val text by derivedStateOf {
when (it) {
ListVisibility.Public -> "Public"
ListVisibility.Private -> "Private"
ListVisibility.Mixed -> "Mixed"
}
}
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter =
painterResource(
when (it) {
ListVisibility.Public -> R.drawable.ic_public
ListVisibility.Private -> R.drawable.incognito
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = "Icon for $text List",
)
Text(text, color = Color.Gray)
}
}
}
}

View File

@@ -0,0 +1,289 @@
/**
* 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.profile.header
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.consumeWindowInsets
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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAddCheck
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.ListVisibility
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FollowSetsManagementDialog(
userHex: String,
account: Account,
followSetsViewModel: NostrUserListFeedViewModel,
navigator: INav,
) {
val followSetsState by followSetsViewModel.feedContent.collectAsState()
val userInfo by remember { derivedStateOf { LocalCache.getOrCreateUser(userHex) } }
when (followSetsState) {
is FollowSetState.Loaded -> {
val lists = (followSetsState as FollowSetState.Loaded).feed
Scaffold(
modifier =
Modifier
.fillMaxSize()
.recalculateWindowInsets(),
containerColor = AlertDialogDefaults.containerColor,
topBar = {
TopAppBar(
title = {
Column {
Text(text = "Your Lists", fontWeight = FontWeight.SemiBold)
// HorizontalDivider()
}
},
navigationIcon = {
IconButton(
onClick = { navigator.popBack() },
) {
ArrowBackIcon()
}
},
colors =
TopAppBarDefaults
.topAppBarColors(
containerColor = AlertDialogDefaults.containerColor,
),
)
},
) { contentPadding ->
Column(
modifier =
Modifier
.padding(
start = 10.dp,
end = 10.dp,
top = contentPadding.calculateTopPadding(),
bottom = contentPadding.calculateBottomPadding(),
).consumeWindowInsets(contentPadding)
.imePadding(),
) {
lists.forEachIndexed { index, list ->
Spacer(StdVertSpacer)
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = list.title,
listVisibility = list.visibility,
userName = userInfo.info?.bestName() ?: userInfo.pubkeyDisplayHex(),
isUserInList = list.profileList.contains(userHex),
onRemoveUser = {
Log.d(
"Amethyst",
"ProfileActions: Removing item from list ...",
)
followSetsViewModel.removeUserFromSet(
userHex,
list,
account,
)
Log.d(
"Amethyst",
"Updated List. New size: ${list.profileList.size}",
)
},
onAddUser = {
Log.d(
"Amethyst",
"ProfileActions: Adding item to list ...",
)
followSetsViewModel.addUserToSet(userHex, list, account)
Log.d(
"Amethyst",
"Updated List. New size: ${list.profileList.size}",
)
},
)
}
}
}
}
else -> {}
}
}
@Composable
fun FollowSetItem(
modifier: Modifier = Modifier,
listHeader: String,
listVisibility: ListVisibility,
userName: String,
isUserInList: Boolean,
onAddUser: () -> Unit,
onRemoveUser: () -> Unit,
) {
Row(
modifier =
modifier
// .clickable(onClick = onAddUser)
.border(
width = Dp.Hairline,
color = Color.Gray,
shape = RoundedCornerShape(percent = 20),
).padding(all = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = modifier.weight(1f),
verticalArrangement = Arrangement.Center,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(listHeader, fontWeight = FontWeight.Bold)
Spacer(modifier = StdHorzSpacer)
listVisibility.let {
val text by derivedStateOf {
when (it) {
ListVisibility.Public -> "Public"
ListVisibility.Private -> "Private"
ListVisibility.Mixed -> "Mixed"
}
}
Icon(
painter =
painterResource(
when (listVisibility) {
ListVisibility.Public -> R.drawable.ic_public
ListVisibility.Private -> R.drawable.lock
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = "Icon for $text List",
)
}
}
Spacer(modifier = StdVertSpacer)
Row {
FilterChip(
selected = isUserInList,
enabled = isUserInList,
onClick = {},
label = {
Text(text = if (isUserInList) "$userName is present in this list" else "$userName is not in this list")
},
leadingIcon =
if (isUserInList) {
{
Icon(
imageVector = Icons.AutoMirrored.Filled.PlaylistAddCheck,
contentDescription = null,
)
}
} else {
null
},
shape = ButtonBorder,
)
Spacer(modifier = StdHorzSpacer)
}
}
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconButton(
onClick = {
if (isUserInList) onRemoveUser() else onAddUser()
},
modifier =
Modifier
.background(
color =
if (isUserInList) {
MaterialTheme.colorScheme.errorContainer
} else {
ButtonDefaults.filledTonalButtonColors().containerColor
},
shape = RoundedCornerShape(percent = 80),
),
) {
if (isUserInList) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
} else {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
}
}
Text(text = if (isUserInList) "Remove" else "Add", color = Color.Gray)
}
}
}

View File

@@ -20,34 +20,32 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header
import android.util.Log
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.List
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
@Composable
fun ProfileActions(
baseUser: User,
followSetsViewModel: NostrUserListFeedViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
val followSetsState by followSetsViewModel.feedContent.collectAsState()
val (isMenuOpen, setMenuValue) = remember { mutableStateOf(false) }
val uiScope = rememberCoroutineScope()
val isMe by
remember(accountViewModel) { derivedStateOf { accountViewModel.userProfile() == baseUser } }
@@ -63,31 +61,15 @@ fun ProfileActions(
DisplayFollowUnfollowButton(baseUser, accountViewModel)
}
when (followSetsState) {
is FollowSetState.Loaded -> {
val lists = (followSetsState as FollowSetState.Loaded).feed
FollowSetsActionMenu(
isMenuOpen = isMenuOpen,
setMenuOpenState = {
uiScope.launch {
delay(100)
setMenuValue(!isMenuOpen)
}
},
userHex = baseUser.pubkeyHex,
followLists = lists,
addUser = { index, userPubkey, list ->
Log.d("Amethyst", "ProfileActions: Updating list ...")
followSetsViewModel.addUserToSet(baseUser.pubkeyHex, list, accountViewModel.account)
Log.d("Amethyst", "Updated List. New size: ${lists[index].profileList.size}")
},
removeUser = { index, userPubkey, list ->
Log.d("Amethyst", "ProfileActions: Updating list ...")
followSetsViewModel.removeUserFromSet(baseUser.pubkeyHex, list, accountViewModel.account)
Log.d("Amethyst", "Updated List. New size: ${lists[index].profileList.size}")
},
)
}
else -> {}
TextButton(
onClick = { nav.nav(Route.FollowSetManagement(baseUser.pubkeyHex)) },
shape = ButtonBorder.copy(topStart = CornerSize(0f), bottomStart = CornerSize(0f)),
colors = ButtonDefaults.filledTonalButtonColors(),
contentPadding = ZeroPadding,
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.List,
contentDescription = "Add or remove user from lists, or create a new list with this user.",
)
}
}

View File

@@ -54,7 +54,6 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
@@ -68,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.theme.userProfileBorderModifier
fun ProfileHeader(
baseUser: User,
appRecommendations: UserAppRecommendationsFeedViewModel,
followSetsViewModel: NostrUserListFeedViewModel,
nav: INav,
accountViewModel: AccountViewModel,
) {
@@ -156,7 +154,7 @@ fun ProfileHeader(
) {
MessageButton(baseUser, accountViewModel, nav)
ProfileActions(baseUser, followSetsViewModel, accountViewModel, nav)
ProfileActions(baseUser, accountViewModel, nav)
}
}