Refactor: From List -> Set, and Viewmodel rename as well. Move FollowSet and related classes to model package.

This commit is contained in:
KotlinGeekDev
2025-09-25 21:36:34 +01:00
parent ba0fc67a94
commit 4b60c3e20b
11 changed files with 192 additions and 146 deletions
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
package com.vitorpamplona.amethyst.model.nip51Lists.followSets
import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.nip01Core.core.value
@@ -31,9 +31,9 @@ data class FollowSet(
val identifierTag: String,
val title: String,
val description: String?,
val visibility: ListVisibility,
val profileList: Set<String>,
) : NostrList(listVisibility = visibility, content = profileList) {
val visibility: SetVisibility,
val profiles: Set<String>,
) : NostrSet(setVisibility = visibility, content = profiles) {
companion object {
fun mapEventToSet(
event: PeopleListEvent,
@@ -53,16 +53,16 @@ data class FollowSet(
identifierTag = dTag,
title = listTitle,
description = listDescription,
visibility = ListVisibility.Private,
profileList = privateFollows.toSet(),
visibility = SetVisibility.Private,
profiles = privateFollows.toSet(),
)
} else if (publicFollows.isNotEmpty() && privateFollows.isEmpty()) {
FollowSet(
identifierTag = dTag,
title = listTitle,
description = listDescription,
visibility = ListVisibility.Public,
profileList = publicFollows.toSet(),
visibility = SetVisibility.Public,
profiles = publicFollows.toSet(),
)
} else {
// Follow set is empty, so assume public. Why? Nostr limitation.
@@ -71,8 +71,8 @@ data class FollowSet(
identifierTag = dTag,
title = listTitle,
description = listDescription,
visibility = ListVisibility.Public,
profileList = publicFollows.toSet(),
visibility = SetVisibility.Public,
profiles = publicFollows.toSet(),
)
}
}
@@ -0,0 +1,52 @@
/**
* 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.model.nip51Lists.followSets
/**
* 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.
*/
sealed class NostrSet(
val setVisibility: SetVisibility,
val content: Collection<String>,
)
class CuratedBookmarkSet(
val name: String,
val visibility: SetVisibility,
val setItems: List<String>,
) : NostrSet(visibility, setItems)
@@ -0,0 +1,47 @@
/**
* 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.model.nip51Lists.followSets
/**
* 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.
*/
enum class SetVisibility {
Public,
Private,
Mixed,
}
@@ -59,6 +59,8 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -76,10 +78,10 @@ fun ListsAndSetsScreen(
accountViewModel: AccountViewModel,
nav: INav,
) {
val followSetsViewModel: NostrUserListFeedViewModel =
val followSetsViewModel: FollowSetFeedViewModel =
viewModel(
key = "NostrUserListFeedViewModel",
factory = NostrUserListFeedViewModel.Factory(accountViewModel.account),
key = "FollowSetFeedViewModel",
factory = FollowSetFeedViewModel.Factory(accountViewModel.account),
)
ListsAndSetsScreen(
@@ -91,7 +93,7 @@ fun ListsAndSetsScreen(
@Composable
fun ListsAndSetsScreen(
followSetsViewModel: NostrUserListFeedViewModel,
followSetsViewModel: FollowSetFeedViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
@@ -117,8 +119,8 @@ fun ListsAndSetsScreen(
refresh = {
followSetsViewModel.invalidateData()
},
addItem = { title: String, description: String?, listType: ListVisibility ->
val isSetPrivate = listType == ListVisibility.Private
addItem = { title: String, description: String?, listType: SetVisibility ->
val isSetPrivate = listType == SetVisibility.Private
followSetsViewModel.addFollowSet(
setName = title,
setDescription = description,
@@ -149,9 +151,9 @@ fun ListsAndSetsScreen(
@Composable
fun CustomListsScreen(
followSetState: FollowSetState,
followSetFeedState: FollowSetFeedState,
refresh: () -> Unit,
addItem: (title: String, description: String?, listType: ListVisibility) -> Unit,
addItem: (title: String, description: String?, listType: SetVisibility) -> Unit,
openItem: (identifier: String) -> Unit,
renameItem: (followSet: FollowSet, newName: String) -> Unit,
deleteItem: (followSet: FollowSet) -> Unit,
@@ -195,10 +197,10 @@ fun CustomListsScreen(
// TODO: Show components based on current tab
FollowSetFabsAndMenu(
onAddPrivateSet = { name: String, description: String? ->
addItem(name, description, ListVisibility.Private)
addItem(name, description, SetVisibility.Private)
},
onAddPublicSet = { name: String, description: String? ->
addItem(name, description, ListVisibility.Public)
addItem(name, description, SetVisibility.Public)
},
)
},
@@ -216,7 +218,7 @@ fun CustomListsScreen(
when (page) {
0 ->
FollowSetFeedView(
followSetState = followSetState,
followSetFeedState = followSetFeedState,
onRefresh = refresh,
onOpenItem = openItem,
onRenameItem = renameItem,
@@ -410,7 +412,7 @@ private fun SetItemPreview() {
identifierTag = "00001-2222",
title = "Sample List Title",
description = "Sample List Description",
visibility = ListVisibility.Mixed,
visibility = SetVisibility.Mixed,
emptySet(),
)
ThemeComparisonColumn {
@@ -55,6 +55,8 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.ui.stringRes
@@ -98,7 +100,7 @@ fun CustomSetItem(
selected = true,
onClick = {},
label = {
Text(text = "${followSet.profileList.size}")
Text(text = "${followSet.profiles.size}")
},
leadingIcon = {
Icon(
@@ -121,9 +123,9 @@ fun CustomSetItem(
followSet.visibility.let {
val text by derivedStateOf {
when (it) {
ListVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
ListVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
ListVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
}
}
Column(
@@ -135,9 +137,9 @@ fun CustomSetItem(
painter =
painterResource(
when (it) {
ListVisibility.Public -> R.drawable.ic_public
ListVisibility.Private -> R.drawable.lock
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = stringRes(R.string.follow_set_type_description, text),
@@ -20,16 +20,18 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
sealed class FollowSetState {
data object Loading : FollowSetState()
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
sealed class FollowSetFeedState {
data object Loading : FollowSetFeedState()
data class Loaded(
val feed: List<FollowSet>,
) : FollowSetState()
) : FollowSetFeedState()
data object Empty : FollowSetState()
data object Empty : FollowSetFeedState()
data class FeedError(
val errorMessage: String,
) : FollowSetState()
) : FollowSetFeedState()
}
@@ -30,6 +30,8 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
@@ -49,12 +51,11 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.UUID
// TODO Update: Rename this to be used only for follow sets, and create separate VMs for bookmark sets, etc.
class NostrUserListFeedViewModel(
class FollowSetFeedViewModel(
val dataSource: FeedFilter<FollowSet>,
) : ViewModel(),
InvalidatableContent {
private val _feedContent = MutableStateFlow<FollowSetState>(FollowSetState.Loading)
private val _feedContent = MutableStateFlow<FollowSetFeedState>(FollowSetFeedState.Loading)
val feedContent = _feedContent.asStateFlow()
fun refresh() {
@@ -67,9 +68,8 @@ class NostrUserListFeedViewModel(
noteIdentifier: String,
account: Account,
): AddressableNote? {
// checkNotInMainThread()
val potentialNote =
runBlocking(Dispatchers.IO) { account.getFollowSetNotes() }
runBlocking(Dispatchers.IO) { account.followSetsState.getFollowSetNotes() }
.find { it.dTag() == noteIdentifier }
return potentialNote
}
@@ -79,7 +79,7 @@ class NostrUserListFeedViewModel(
account: Account,
): Boolean {
val potentialNote =
runBlocking(viewModelScope.coroutineContext) { account.getFollowSetNotes() }
runBlocking(viewModelScope.coroutineContext) { account.followSetsState.getFollowSetNotes() }
.find { (it.event as PeopleListEvent).nameOrTitle() == setName }
return potentialNote != null
}
@@ -94,7 +94,7 @@ class NostrUserListFeedViewModel(
val newSets = dataSource.loadTop().toImmutableList()
if (oldFeedState is FollowSetState.Loaded) {
if (oldFeedState is FollowSetFeedState.Loaded) {
val oldFeedList = oldFeedState.feed.toImmutableList()
// Using size as a proxy for has changed.
if (!equalImmutableLists(newSets, oldFeedList)) {
@@ -108,7 +108,7 @@ class NostrUserListFeedViewModel(
this.javaClass.simpleName,
"refreshSuspended: Error loading or refreshing feed -> ${e.message}",
)
_feedContent.update { FollowSetState.FeedError(e.message.toString()) }
_feedContent.update { FollowSetFeedState.FeedError(e.message.toString()) }
} finally {
isRefreshing.value = false
}
@@ -190,7 +190,7 @@ class NostrUserListFeedViewModel(
PeopleListEvent.addUser(
earlierVersion = followSetEvent,
pubKeyHex = userProfileHex,
isPrivate = followSet.visibility == ListVisibility.Private,
isPrivate = followSet.visibility == SetVisibility.Private,
signer = account.signer,
) {
account.sendMyPublicAndPrivateOutbox(it)
@@ -223,9 +223,9 @@ class NostrUserListFeedViewModel(
private fun updateFeed(sets: ImmutableList<FollowSet>) {
if (sets.isNotEmpty()) {
_feedContent.update { FollowSetState.Loaded(sets) }
_feedContent.update { FollowSetFeedState.Loaded(sets) }
} else {
_feedContent.update { FollowSetState.Empty }
_feedContent.update { FollowSetFeedState.Empty }
}
}
@@ -244,7 +244,7 @@ class NostrUserListFeedViewModel(
init {
Log.d("Init", this.javaClass.simpleName)
Log.d(this.javaClass.simpleName, " FollowSetState : ${_feedContent.value}")
Log.d(this.javaClass.simpleName, " FollowSetFeedState : ${_feedContent.value}")
collectorJob =
viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { newNotes ->
@@ -266,8 +266,8 @@ class NostrUserListFeedViewModel(
val account: Account,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
NostrUserListFeedViewModel(
FollowSetFeedFilter(account),
FollowSetFeedViewModel(
FollowSetFeedFilter(account.followSetsState),
) as T
}
}
@@ -1,27 +0,0 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
enum class ListVisibility {
Public,
Private,
Mixed,
}
@@ -1,32 +0,0 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
sealed class NostrList(
val listVisibility: ListVisibility,
val content: Collection<String>,
)
class CuratedBookmarkList(
val name: String,
val visibility: ListVisibility,
val listItems: List<String>,
) : NostrList(visibility, listItems)
@@ -67,14 +67,14 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
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.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@@ -92,10 +92,10 @@ fun FollowSetScreen(
accountViewModel: AccountViewModel,
navigator: INav,
) {
val followSetViewModel: NostrUserListFeedViewModel =
val followSetViewModel: FollowSetFeedViewModel =
viewModel(
key = "NostrUserListFeedViewModel",
factory = NostrUserListFeedViewModel.Factory(accountViewModel.account),
key = "FollowSetFeedViewModel",
factory = FollowSetFeedViewModel.Factory(accountViewModel.account),
)
FollowSetScreen(selectedSetIdentifier, followSetViewModel, accountViewModel, navigator)
@@ -105,7 +105,7 @@ fun FollowSetScreen(
@Composable
fun FollowSetScreen(
selectedSetIdentifier: String,
followSetViewModel: NostrUserListFeedViewModel,
followSetViewModel: FollowSetFeedViewModel,
accountViewModel: AccountViewModel,
navigator: INav,
) {
@@ -144,7 +144,7 @@ fun FollowSetScreen(
when {
selectedSetState.value != null -> {
val selectedSet = selectedSetState.value
val users = selectedSet!!.profileList.mapToUsers(accountViewModel).filterNotNull()
val users = selectedSet!!.profiles.mapToUsers(accountViewModel).filterNotNull()
Scaffold(
topBar = {
TopAppBar(
@@ -235,10 +235,10 @@ fun TitleAndDescription(
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
when (followSet.setVisibility) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = null,
@@ -78,13 +78,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
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.ListVisibility
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetFeedState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NewSetCreationDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrUserListFeedViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@@ -97,10 +97,10 @@ fun FollowSetsManagementDialog(
accountViewModel: AccountViewModel,
navigator: INav,
) {
val followSetViewModel: NostrUserListFeedViewModel =
val followSetViewModel: FollowSetFeedViewModel =
viewModel(
key = "NostrUserListFeedViewModel",
factory = NostrUserListFeedViewModel.Factory(accountViewModel.account),
key = "FollowSetFeedViewModel",
factory = FollowSetFeedViewModel.Factory(accountViewModel.account),
)
FollowSetsManagementDialog(userHex, followSetViewModel, accountViewModel.account, navigator)
@@ -110,7 +110,7 @@ fun FollowSetsManagementDialog(
@Composable
fun FollowSetsManagementDialog(
userHex: String,
followSetsViewModel: NostrUserListFeedViewModel,
followSetsViewModel: FollowSetFeedViewModel,
account: Account,
navigator: INav,
) {
@@ -164,17 +164,17 @@ fun FollowSetsManagementDialog(
.imePadding(),
) {
when (followSetsState) {
is FollowSetState.Loaded -> {
val lists = (followSetsState as FollowSetState.Loaded).feed
is FollowSetFeedState.Loaded -> {
val lists = (followSetsState as FollowSetFeedState.Loaded).feed
lists.forEachIndexed { index, list ->
Spacer(StdVertSpacer)
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = list.title,
listVisibility = list.visibility,
setVisibility = list.visibility,
userName = userInfo.toBestDisplayName(),
isUserInList = list.profileList.contains(userHex),
isUserInList = list.profiles.contains(userHex),
onRemoveUser = {
Log.d(
"Amethyst",
@@ -187,7 +187,7 @@ fun FollowSetsManagementDialog(
)
Log.d(
"Amethyst",
"Updated List. New size: ${list.profileList.size}",
"Updated List. New size: ${list.profiles.size}",
)
},
onAddUser = {
@@ -198,28 +198,28 @@ fun FollowSetsManagementDialog(
followSetsViewModel.addUserToSet(userHex, list, account)
Log.d(
"Amethyst",
"Updated List. New size: ${list.profileList.size}",
"Updated List. New size: ${list.profiles.size}",
)
},
)
}
}
FollowSetState.Empty -> {
FollowSetFeedState.Empty -> {
EmptyOrNoneFound { followSetsViewModel.refresh() }
}
is FollowSetState.FeedError -> {
val errorMsg = (followSetsState as FollowSetState.FeedError).errorMessage
is FollowSetFeedState.FeedError -> {
val errorMsg = (followSetsState as FollowSetFeedState.FeedError).errorMessage
ErrorMessage(errorMsg) { followSetsViewModel.refresh() }
}
FollowSetState.Loading -> {
FollowSetFeedState.Loading -> {
Loading()
}
}
if (followSetsState != FollowSetState.Loading) {
if (followSetsState != FollowSetFeedState.Loading) {
FollowSetsCreationMenu(
userName = userInfo.toBestDisplayName(),
onSetCreate = { setName, setIsPrivate, description ->
@@ -304,7 +304,7 @@ private fun ErrorMessage(
fun FollowSetItem(
modifier: Modifier = Modifier,
listHeader: String,
listVisibility: ListVisibility,
setVisibility: SetVisibility,
userName: String,
isUserInList: Boolean,
onAddUser: () -> Unit,
@@ -330,21 +330,21 @@ fun FollowSetItem(
) {
Text(listHeader, fontWeight = FontWeight.Bold)
Spacer(modifier = StdHorzSpacer)
listVisibility.let {
setVisibility.let {
val text by derivedStateOf {
when (it) {
ListVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
ListVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
ListVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_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
when (setVisibility) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = stringRes(R.string.follow_set_type_description, text),