From 09fc5bf57f0c6ce46e3172cdc83ee18ebb79a048 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 18:12:26 +0000 Subject: [PATCH] feat: Filter home feed by hashtag in-place instead of navigating away Same approach as relay filtering: selecting a hashtag from the top nav filter now filters the home feed in-place across all tabs, with subscriptions scoped to that hashtag. https://claude.ai/code/session_01PZLLjE7MWh7PPz1woVqmxM --- .../topNavFeeds/FeedTopNavFilterState.kt | 3 +- .../topNavFeeds/hashtag/HashtagFeedFlow.kt | 53 +++++++++++++++++++ .../amethyst/ui/screen/TopNavFilterState.kt | 1 - 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/hashtag/HashtagFeedFlow.kt 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 c4bba7800..530637d98 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 @@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.Kind3UserFoll import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.chess.ChessFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalFeedFlow +import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.NoteFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.relay.RelayFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.unknown.UnknownFeedFlow @@ -135,7 +136,7 @@ class FeedTopNavFilterState( } is TopFilter.Hashtag -> { - UnknownFeedFlow(listName) + HashtagFeedFlow(listName.tag, followsRelays, proxyRelays) } is TopFilter.Relay -> { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/hashtag/HashtagFeedFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/hashtag/HashtagFeedFlow.kt new file mode 100644 index 000000000..f34fcb75a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/hashtag/HashtagFeedFlow.kt @@ -0,0 +1,53 @@ +/* + * 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.hashtag + +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.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine + +class HashtagFeedFlow( + val hashtag: String, + val outboxRelays: StateFlow>, + val proxyRelays: StateFlow>, +) : IFeedFlowsType { + fun buildFilter(relays: Set) = HashtagTopNavFilter(setOf(hashtag), relays) + + override fun flow(): Flow = + combine(outboxRelays, proxyRelays) { outbox, proxy -> + if (proxy.isNotEmpty()) buildFilter(proxy) else buildFilter(outbox) + } + + override fun startValue(): HashtagTopNavFilter = + if (proxyRelays.value.isNotEmpty()) { + buildFilter(proxyRelays.value) + } else { + buildFilter(outboxRelays.value) + } + + override suspend fun startValue(collector: FlowCollector) { + collector.emit(startValue()) + } +} 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 f3752fe60..71ce0c557 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 @@ -146,7 +146,6 @@ class TopNavFilterState( FeedDefinition( TopFilter.Hashtag(it), HashtagName(it), - route = Route.Hashtag(it), ) }