diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index fd914d1ce..398fd2c5a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -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 { FollowSetScreen(it.setIdentifier, accountViewModel, listsViewModel, nav) } + composableArgs { + FollowSetsManagementDialog(it.userHexKey, accountViewModel.account, listsViewModel, nav) + } composable { NewUserMetadataScreen(nav, accountViewModel) } composable { SearchScreen(accountViewModel, nav) } @@ -145,7 +149,7 @@ fun AppNavigation( composableFromEndArgs { AllMediaServersScreen(accountViewModel, nav) } composableFromEndArgs { DvmContentDiscoveryScreen(it.id, accountViewModel, nav) } - composableFromEndArgs { ProfileScreen(it.id, accountViewModel, listsViewModel, nav) } + composableFromEndArgs { ProfileScreen(it.id, accountViewModel, nav) } composableFromEndArgs { ThreadScreen(it.id, accountViewModel, nav) } composableFromEndArgs { HashtagScreen(it, accountViewModel, nav) } composableFromEndArgs { GeoHashScreen(it, accountViewModel, nav) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index 35e2c98f8..c7e17f57d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -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() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt index c17d273d3..71eb5802c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt @@ -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, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/FollowSetsActionMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/FollowSetsActionMenu.kt deleted file mode 100644 index b0929089a..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/FollowSetsActionMenu.kt +++ /dev/null @@ -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, - 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) - } - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/FollowSetsManagementDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/FollowSetsManagementDialog.kt new file mode 100644 index 000000000..958f464be --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/FollowSetsManagementDialog.kt @@ -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) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt index 94c98d440..676a2507c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt @@ -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.", + ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt index 46062134c..0ef030fde 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt @@ -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) } }