From 5f88249be696f0f439582c109bd266a8fd4d8ee2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 10 Nov 2025 19:45:39 -0500 Subject: [PATCH] Creates a feed filter option for only kind 3 users --- .../vitorpamplona/amethyst/model/Account.kt | 4 ++ .../amethyst/model/AccountSettings.kt | 3 + .../topNavFeeds/FeedTopNavFilterState.kt | 6 ++ .../Kind3UserFollowsFeedFlow.kt | 69 +++++++++++++++++++ .../amethyst/ui/note/ReactionsRow.kt | 2 +- .../amethyst/ui/screen/TopNavFilterState.kt | 20 ++++-- .../loggedIn/discover/DiscoveryTopBar.kt | 2 +- .../ui/screen/loggedIn/home/HomeTopBar.kt | 2 +- .../notifications/NotificationTopBar.kt | 2 +- .../ui/screen/loggedIn/video/StoriesTopBar.kt | 2 +- amethyst/src/main/res/values/strings.xml | 1 + 11 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/Kind3UserFollowsFeedFlow.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index dd32e14ca..57f15f239 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -350,6 +350,7 @@ class Account( val liveHomeFollowLists: StateFlow = FeedTopNavFilterState( feedFilterListName = settings.defaultHomeFollowList, + kind3Follows = kind3FollowList.flow, allFollows = allFollows.flow, locationFlow = geolocationFlow, followsRelays = defaultGlobalRelays.flow, @@ -365,6 +366,7 @@ class Account( val liveStoriesFollowLists: StateFlow = FeedTopNavFilterState( feedFilterListName = settings.defaultStoriesFollowList, + kind3Follows = kind3FollowList.flow, allFollows = allFollows.flow, locationFlow = geolocationFlow, followsRelays = defaultGlobalRelays.flow, @@ -380,6 +382,7 @@ class Account( val liveDiscoveryFollowLists: StateFlow = FeedTopNavFilterState( feedFilterListName = settings.defaultDiscoveryFollowList, + kind3Follows = kind3FollowList.flow, allFollows = allFollows.flow, locationFlow = geolocationFlow, followsRelays = defaultGlobalRelays.flow, @@ -395,6 +398,7 @@ class Account( val liveNotificationFollowLists: StateFlow = FeedTopNavFilterState( feedFilterListName = settings.defaultNotificationFollowList, + kind3Follows = kind3FollowList.flow, allFollows = allFollows.flow, locationFlow = geolocationFlow, followsRelays = defaultGlobalRelays.flow, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index e81151655..c1bcc246e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -101,6 +101,9 @@ val ALL_FOLLOWS = " All Follows " // This has spaces to avoid mixing with a potential NIP-51 list with the same name. val ALL_USER_FOLLOWS = " All User Follows " +// This has spaces to avoid mixing with a potential NIP-51 list with the same name. +val KIND3_FOLLOWS = " Main User Follows " + // This has spaces to avoid mixing with a potential NIP-51 list with the same name. val AROUND_ME = " Around Me " diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt index 89db92684..a9cd1a484 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt @@ -24,10 +24,13 @@ import com.vitorpamplona.amethyst.model.ALL_FOLLOWS import com.vitorpamplona.amethyst.model.ALL_USER_FOLLOWS import com.vitorpamplona.amethyst.model.AROUND_ME import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS +import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.nip02FollowLists.Kind3FollowListState import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.AllUserFollowsFeedFlow +import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.Kind3UserFollowsFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.NoteFeedFlow @@ -42,6 +45,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -49,6 +53,7 @@ import kotlinx.coroutines.flow.transformLatest class FeedTopNavFilterState( val feedFilterListName: MutableStateFlow, + val kind3Follows: StateFlow, val allFollows: StateFlow, val locationFlow: StateFlow, val followsRelays: StateFlow>, @@ -63,6 +68,7 @@ class FeedTopNavFilterState( GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays, proxyRelays) ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays) ALL_USER_FOLLOWS -> AllUserFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays) + KIND3_FOLLOWS -> Kind3UserFollowsFeedFlow(kind3Follows, followsRelays, blockedRelays, proxyRelays) AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays) else -> { val note = LocalCache.checkGetOrCreateAddressableNote(listName) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/Kind3UserFollowsFeedFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/Kind3UserFollowsFeedFlow.kt new file mode 100644 index 000000000..ebf0967b8 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/Kind3UserFollowsFeedFlow.kt @@ -0,0 +1,69 @@ +/** + * 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.topNavFeeds.allUserFollows + +import com.vitorpamplona.amethyst.model.nip02FollowLists.Kind3FollowListState +import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedFlowsType +import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine + +class Kind3UserFollowsFeedFlow( + val allFollows: StateFlow, + val followsRelays: StateFlow>, + val blockedRelays: StateFlow>, + val proxyRelays: StateFlow>, +) : IFeedFlowsType { + fun convert( + allFollows: Kind3FollowListState.Kind3Follows?, + proxyRelays: Set, + ): IFeedTopNavFilter = + if (allFollows != null) { + if (proxyRelays.isEmpty()) { + AllUserFollowsByOutboxTopNavFilter( + authors = allFollows.authors, + defaultRelays = followsRelays, + blockedRelays = blockedRelays, + ) + } else { + AllUserFollowsByProxyTopNavFilter( + authors = allFollows.authors, + proxyRelays = proxyRelays, + ) + } + } else { + AllUserFollowsByOutboxTopNavFilter( + authors = emptySet(), + defaultRelays = followsRelays, + blockedRelays = blockedRelays, + ) + } + + override fun flow() = combine(allFollows, proxyRelays, ::convert) + + override fun startValue(): IFeedTopNavFilter = convert(allFollows.value, proxyRelays.value) + + override suspend fun startValue(collector: FlowCollector) { + collector.emit(startValue()) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 1ed163a43..20560510c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -933,7 +933,7 @@ fun ObserveLikeText( inner: @Composable (Int) -> Unit, ) { val reactionCount by observeNoteReactionCount(baseNote, accountViewModel) - println("AABBCC $reactionCount ${baseNote.idHex}") + inner(reactionCount) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt index 3d6f89fc1..6cf7aea7d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt @@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.model.AROUND_ME import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS +import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.stringRes @@ -72,7 +73,7 @@ class TopNavFilterState( val account: Account, val scope: CoroutineScope, ) { - val kind3Follow = + val allFollows = PeopleListOutBoxFeedDefinition( code = ALL_FOLLOWS, name = ResourceName(R.string.follow_list_kind3follows), @@ -81,7 +82,7 @@ class TopNavFilterState( unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)), ) - val kind3FollowUsers = + val userFollows = PeopleListOutBoxFeedDefinition( code = ALL_USER_FOLLOWS, name = ResourceName(R.string.follow_list_kind3follows_users_only), @@ -90,6 +91,15 @@ class TopNavFilterState( unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)), ) + val kind3Follows = + PeopleListOutBoxFeedDefinition( + code = KIND3_FOLLOWS, + name = ResourceName(R.string.follow_list_kind3_follows_users_only), + type = CodeNameType.HARDCODED, + kinds = DEFAULT_FEED_KINDS, + unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)), + ) + val globalFollow = GlobalFeedDefinition( code = GLOBAL_FOLLOWS, @@ -115,7 +125,7 @@ class TopNavFilterState( unpackList = listOf(MuteListEvent.blockListFor(account.userProfile().pubkeyHex)), ) - val defaultLists = persistentListOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow, muteListFollow) + val defaultLists = persistentListOf(allFollows, userFollows, kind3Follows, aroundMe, globalFollow, muteListFollow) fun mergePeopleLists( peopleLists: List, @@ -229,7 +239,7 @@ class TopNavFilterState( checkNotInMainThread() emit( listOf( - listOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow), + listOf(allFollows, userFollows, kind3Follows, aroundMe, globalFollow), myLivePeopleListsFlow, myLiveKind3FollowsFlow, listOf(muteListFollow), @@ -245,7 +255,7 @@ class TopNavFilterState( checkNotInMainThread() emit( listOf( - listOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow), + listOf(allFollows, userFollows, kind3Follows, aroundMe, globalFollow), myLivePeopleListsFlow, listOf(muteListFollow), ).flatten().toImmutableList(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt index 0718e0265..a3884ac28 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt @@ -63,7 +63,7 @@ private fun TopNavFilterBar( placeholderCode = listName, explainer = stringRes(R.string.select_list_to_filter), options = allLists, - onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) }, + onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.allFollows) }, accountViewModel = accountViewModel, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt index 5bc35e195..745f30b02 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt @@ -68,7 +68,7 @@ private fun TopNavFilterBar( placeholderCode = listName, explainer = stringRes(R.string.select_list_to_filter), options = allLists, - onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) }, + onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.allFollows) }, accountViewModel = accountViewModel, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt index 2762ca840..d24f63e60 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt @@ -63,7 +63,7 @@ private fun TopNavFilterBar( placeholderCode = listName, explainer = stringRes(R.string.select_list_to_filter), options = allLists, - onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) }, + onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.allFollows) }, accountViewModel = accountViewModel, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt index 29efbe674..b221989c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt @@ -64,7 +64,7 @@ private fun TopNavFilterBar( placeholderCode = listName, explainer = stringRes(R.string.select_list_to_filter), options = allLists, - onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) }, + onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.allFollows) }, accountViewModel = accountViewModel, ) } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 39066f295..c8a3aff9f 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -506,6 +506,7 @@ Follow List All Follows All User Follows + Default Follow List Follows via Proxy Around Me Global