mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
Refactor: From List -> Set, and Viewmodel rename as well. Move FollowSet and related classes to model package.
This commit is contained in:
+10
-10
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+52
@@ -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)
|
||||
+47
@@ -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,
|
||||
}
|
||||
+14
-12
@@ -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 {
|
||||
|
||||
+9
-7
@@ -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),
|
||||
|
||||
+7
-5
@@ -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()
|
||||
}
|
||||
+14
-14
@@ -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
|
||||
}
|
||||
}
|
||||
-27
@@ -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,
|
||||
}
|
||||
-32
@@ -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)
|
||||
+12
-12
@@ -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,
|
||||
|
||||
+27
-27
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user