mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 21:16:46 +01:00
Completely change the UI for list management when adding/removing a profile from lists.
This commit is contained in:
@@ -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) }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user