FollowSetScreen: -Make a separate component for the title and description.

- Fix onBroadcastList, making sure to get the latest changes to the set before broadcast.
-Make a separate component for FollowSet list items, and make the delete button look a bit better(?).
NostrUserListVM: - Minor cleanup.
This commit is contained in:
KotlinGeekDev
2025-09-11 14:32:58 +01:00
parent bd41354009
commit 31328089cb
2 changed files with 131 additions and 77 deletions
@@ -230,7 +230,6 @@ class NostrUserListFeedViewModel(
private val bundler = BundledUpdate(2000, Dispatchers.IO) private val bundler = BundledUpdate(2000, Dispatchers.IO)
override fun invalidateData(ignoreIfDoing: Boolean) { override fun invalidateData(ignoreIfDoing: Boolean) {
// refresh()
bundler.invalidate(ignoreIfDoing) { bundler.invalidate(ignoreIfDoing) {
// adds the time to perform the refresh into this delay // adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines. // holding off new updates in case of heavy refresh routines.
@@ -21,7 +21,10 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.followsets package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.followsets
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
@@ -29,6 +32,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
@@ -52,7 +56,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -65,8 +72,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.ListVisibility import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.ListVisibility
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -74,7 +82,6 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun FollowSetScreen( fun FollowSetScreen(
// TODO: Investigate passing follow set properties rather than follow set object.
selectedSetIdentifier: String, selectedSetIdentifier: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navigator: INav, navigator: INav,
@@ -88,60 +95,44 @@ fun FollowSetScreen(
val followSetState by followSetViewModel.feedContent.collectAsState() val followSetState by followSetViewModel.feedContent.collectAsState()
val uiScope = rememberCoroutineScope() val uiScope = rememberCoroutineScope()
val selectedSet by remember(followSetState) { val selectedSetState =
derivedStateOf { remember(followSetState) {
uiScope.launch { derivedStateOf {
delay(500L) uiScope.launch {
} delay(500L)
val note = }
followSetViewModel.getFollowSetNote( val note =
selectedSetIdentifier, followSetViewModel.getFollowSetNote(
accountViewModel.account, selectedSetIdentifier,
) accountViewModel.account,
if (note != null) {
val event = note.event as PeopleListEvent
println("Found list, with title: ${event.nameOrTitle()}")
val selectedFollowSet =
FollowSet.mapEventToSet(
event,
accountViewModel.account.signer,
) )
return@derivedStateOf selectedFollowSet
} else { if (note != null) {
null val event = note.event as PeopleListEvent
println("Found list, with title: ${event.nameOrTitle()}")
val selectedFollowSet =
FollowSet.mapEventToSet(
event,
accountViewModel.account.signer,
)
return@derivedStateOf selectedFollowSet
} else {
null
}
} }
} }
}
BackHandler { navigator.popBack() } BackHandler { navigator.popBack() }
when { when {
selectedSet != null -> { selectedSetState.value != null -> {
// TODO: Investigate moving the mapping function to a VM.(related to above TODO). val selectedSet = selectedSetState.value
val users = selectedSet!!.profileList.mapToUsers(accountViewModel).filterNotNull() val users = selectedSet!!.profileList.mapToUsers(accountViewModel).filterNotNull()
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
Row( TitleAndDescription(followSet = selectedSet)
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = selectedSet!!.title,
)
Icon(
painter =
painterResource(
when (selectedSet!!.listVisibility) {
ListVisibility.Public -> R.drawable.ic_public
ListVisibility.Private -> R.drawable.lock
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = null,
)
}
}, },
navigationIcon = { navigationIcon = {
BackButton( BackButton(
@@ -149,13 +140,18 @@ fun FollowSetScreen(
) )
}, },
actions = { actions = {
// TODO: Fix onSaveList and onBroadcastList
ListActionsMenuButton( ListActionsMenuButton(
onSaveList = {}, onBroadcastList = {
onBroadcastList = {}, val updatedSetNote =
followSetViewModel.getFollowSetNote(
selectedSet.identifierTag,
accountViewModel.account,
)
accountViewModel.broadcast(updatedSetNote!!)
},
onDeleteList = { onDeleteList = {
followSetViewModel.deleteFollowSet( followSetViewModel.deleteFollowSet(
selectedSet!!, selectedSet,
accountViewModel.account, accountViewModel.account,
) )
}, },
@@ -183,7 +179,7 @@ fun FollowSetScreen(
onDeleteUser = { onDeleteUser = {
followSetViewModel.removeUserFromSet( followSetViewModel.removeUserFromSet(
it, it,
selectedSet!!, selectedSet,
accountViewModel.account, accountViewModel.account,
) )
}, },
@@ -193,7 +189,7 @@ fun FollowSetScreen(
} }
} }
selectedSet == null -> { selectedSetState.value == null -> {
accountViewModel.toastManager.toast( accountViewModel.toastManager.toast(
"Follow Set Error", "Follow Set Error",
"Could not find requested follow set", "Could not find requested follow set",
@@ -206,6 +202,46 @@ fun FollowSetScreen(
fun Set<String>.mapToUsers(accountViewModel: AccountViewModel): List<User?> = map { accountViewModel.checkGetOrCreateUser(it) } fun Set<String>.mapToUsers(accountViewModel: AccountViewModel): List<User?> = map { accountViewModel.checkGetOrCreateUser(it) }
@Composable
fun TitleAndDescription(
modifier: Modifier = Modifier,
followSet: FollowSet,
) {
Column(
modifier = modifier,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = followSet.title,
)
Spacer(modifier = StdHorzSpacer)
Icon(
painter =
painterResource(
when (followSet.listVisibility) {
ListVisibility.Public -> R.drawable.ic_public
ListVisibility.Private -> R.drawable.lock
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = null,
)
}
if (followSet.description != null) {
Text(
text = followSet.description,
fontSize = 18.sp,
fontWeight = FontWeight.Thin,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable @Composable
private fun FollowSetListView( private fun FollowSetListView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -222,30 +258,60 @@ private fun FollowSetListView(
state = listState, state = listState,
) { ) {
itemsIndexed(followSetList, key = { _, item -> item.pubkeyHex }) { _, item -> itemsIndexed(followSetList, key = { _, item -> item.pubkeyHex }) { _, item ->
Row { FollowSetListItem(
IconButton( modifier = Modifier.animateItem(),
onClick = { user = item,
onDeleteUser(item.pubkeyHex) accountViewModel = accountViewModel,
}, nav = nav,
) { onDeleteUser = onDeleteUser,
Icon( )
imageVector = Icons.Default.Delete, }
contentDescription = null, }
) }
}
UserCompose(item, accountViewModel = accountViewModel, nav = nav) @Composable
HorizontalDivider( fun FollowSetListItem(
thickness = DividerThickness, modifier: Modifier = Modifier,
user: User,
accountViewModel: AccountViewModel,
nav: INav,
onDeleteUser: (String) -> Unit,
) {
Column(
modifier = modifier,
) {
Row {
UserCompose(
user,
overallModifier = StdPadding.weight(1f, fill = false),
accountViewModel = accountViewModel,
nav = nav,
)
IconButton(
onClick = {
onDeleteUser(user.pubkeyHex)
},
modifier =
StdPadding
.align(Alignment.CenterVertically)
.background(
color = MaterialTheme.colorScheme.errorContainer,
shape = RoundedCornerShape(size = 5.dp),
),
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
) )
} }
} }
HorizontalDivider()
} }
} }
@Composable @Composable
fun ListActionsMenuButton( fun ListActionsMenuButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onSaveList: () -> Unit,
onBroadcastList: () -> Unit, onBroadcastList: () -> Unit,
onDeleteList: () -> Unit, onDeleteList: () -> Unit,
) { ) {
@@ -258,7 +324,6 @@ fun ListActionsMenuButton(
ListActionsMenu( ListActionsMenu(
onCloseMenu = { isActionListOpen.value = false }, onCloseMenu = { isActionListOpen.value = false },
isOpen = isActionListOpen.value, isOpen = isActionListOpen.value,
onSaveList = onSaveList,
onBroadcastList = onBroadcastList, onBroadcastList = onBroadcastList,
onDeleteList = onDeleteList, onDeleteList = onDeleteList,
) )
@@ -270,7 +335,6 @@ fun ListActionsMenu(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onCloseMenu: () -> Unit, onCloseMenu: () -> Unit,
isOpen: Boolean, isOpen: Boolean,
onSaveList: () -> Unit,
onBroadcastList: () -> Unit, onBroadcastList: () -> Unit,
onDeleteList: () -> Unit, onDeleteList: () -> Unit,
) { ) {
@@ -278,15 +342,6 @@ fun ListActionsMenu(
expanded = isOpen, expanded = isOpen,
onDismissRequest = onCloseMenu, onDismissRequest = onCloseMenu,
) { ) {
DropdownMenuItem(
text = {
Text("Save Changes")
},
onClick = {
onSaveList()
onCloseMenu()
},
)
DropdownMenuItem( DropdownMenuItem(
text = { text = {
Text("Broadcast List") Text("Broadcast List")