mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-07-22 07:48:27 +00:00
Compare commits
3 Commits
b6bf3b8a70
...
claude/con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8103a8da47 | ||
|
|
d103fdd2d1 | ||
|
|
3d68b0894c |
@@ -453,23 +453,25 @@ class Account(
|
||||
geohashCache = geohashListDecryptionCache,
|
||||
)
|
||||
|
||||
fun topNavFilterFlow(listName: MutableStateFlow<TopFilter>) =
|
||||
FeedTopNavFilterState(
|
||||
feedFilterListName = listName,
|
||||
kind3Follows = kind3FollowList.flow,
|
||||
allFollows = allFollows.flow,
|
||||
locationFlow = geolocationFlow,
|
||||
followsRelays = defaultGlobalRelays.flow,
|
||||
blockedRelays = blockedRelayList.flow,
|
||||
proxyRelays = proxyRelayList.flow,
|
||||
relayFeeds = relayFeedsList.flow,
|
||||
caches = feedDecryptionCaches,
|
||||
signer = signer,
|
||||
scope = scope,
|
||||
favoriteAlgoFeedsOrchestrator = favoriteAlgoFeedsOrchestrator,
|
||||
favoriteAlgoFeedAddresses = favoriteAlgoFeedsList.flow,
|
||||
interestSetHashtags = interestSets.hashtagsByIdentifier,
|
||||
).flow
|
||||
fun topNavFilterFlow(
|
||||
listName: MutableStateFlow<TopFilter>,
|
||||
scope: CoroutineScope = this.scope,
|
||||
) = FeedTopNavFilterState(
|
||||
feedFilterListName = listName,
|
||||
kind3Follows = kind3FollowList.flow,
|
||||
allFollows = allFollows.flow,
|
||||
locationFlow = geolocationFlow,
|
||||
followsRelays = defaultGlobalRelays.flow,
|
||||
blockedRelays = blockedRelayList.flow,
|
||||
proxyRelays = proxyRelayList.flow,
|
||||
relayFeeds = relayFeedsList.flow,
|
||||
caches = feedDecryptionCaches,
|
||||
signer = signer,
|
||||
scope = scope,
|
||||
favoriteAlgoFeedsOrchestrator = favoriteAlgoFeedsOrchestrator,
|
||||
favoriteAlgoFeedAddresses = favoriteAlgoFeedsList.flow,
|
||||
interestSetHashtags = interestSets.hashtagsByIdentifier,
|
||||
).flow
|
||||
|
||||
// App-ready Feeds
|
||||
val liveHomeFollowLists: StateFlow<IFeedTopNavFilter> = topNavFilterFlow(settings.defaultHomeFollowList)
|
||||
|
||||
@@ -264,7 +264,7 @@ fun BuildNavigation(
|
||||
composableFromEnd<Route.ProfileBadges> { ProfileBadgesScreen(accountViewModel, nav) }
|
||||
composableFromBottomArgs<Route.AwardBadge> { AwardBadgeScreen(it.kind, it.pubKeyHex, it.dTag, accountViewModel, nav) }
|
||||
composableFromEnd<Route.Pictures> { PicturesScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.SoftwareApps> { SoftwareAppsScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.SoftwareApps> { SoftwareAppsScreen(it, accountViewModel, nav) }
|
||||
composableFromEnd<Route.Calendars> { CalendarsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.CalendarCollections> { CalendarCollectionsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.CalendarReminderSettings> { CalendarReminderSettingsScreen(nav) }
|
||||
|
||||
@@ -209,7 +209,7 @@ val NavBarCatalog: Map<NavBarItem, NavBarItemDef> =
|
||||
id = NavBarItem.SOFTWARE_APPS,
|
||||
labelRes = R.string.software_apps,
|
||||
icon = MaterialSymbols.Apps,
|
||||
resolveRoute = { Route.SoftwareApps },
|
||||
resolveRoute = { Route.SoftwareApps() },
|
||||
),
|
||||
NavBarItem.CALENDARS to
|
||||
NavBarItemDef(
|
||||
|
||||
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.navigation.routes
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.toRoute
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
@@ -81,7 +82,9 @@ sealed class Route {
|
||||
|
||||
@Serializable object Pictures : Route()
|
||||
|
||||
@Serializable object SoftwareApps : Route()
|
||||
@Serializable data class SoftwareApps(
|
||||
val topFilter: TopFilter? = null,
|
||||
) : Route()
|
||||
|
||||
@Serializable object Calendars : Route()
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -56,9 +57,11 @@ import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.LinkIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@@ -170,7 +173,14 @@ fun RenderSoftwareApplication(
|
||||
|
||||
if (platforms.isNotEmpty() || topics.isNotEmpty() || license != null) {
|
||||
Spacer(StdVertSpacer)
|
||||
ChipFlowRow(platforms = platforms, topics = topics, license = license)
|
||||
ChipFlowRow(
|
||||
platforms = platforms,
|
||||
topics = topics,
|
||||
license = license,
|
||||
onTopicClick = { topic ->
|
||||
nav.nav(Route.SoftwareApps(TopFilter.Hashtag(topic.lowercase())))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (website != null || repo != null) {
|
||||
@@ -206,11 +216,12 @@ private fun ChipFlowRow(
|
||||
platforms: List<String>,
|
||||
topics: List<String>,
|
||||
license: String?,
|
||||
onTopicClick: (String) -> Unit,
|
||||
) {
|
||||
// FlowRow is in compose foundation but we use a simple LazyRow to keep this compatible.
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
items(platforms) { Chip(it) }
|
||||
items(topics) { Chip("#$it") }
|
||||
items(topics) { topic -> Chip("#$topic", onClick = { onTopicClick(topic) }) }
|
||||
license?.let { item { Chip(it, tint = MaterialTheme.colorScheme.secondaryContainer) } }
|
||||
}
|
||||
}
|
||||
@@ -219,13 +230,14 @@ private fun ChipFlowRow(
|
||||
private fun Chip(
|
||||
text: String,
|
||||
tint: Color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Box(
|
||||
val base =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(tint)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
val withClick = if (onClick != null) base.clickable(onClick = onClick) else base
|
||||
Box(withClick.padding(horizontal = 8.dp, vertical = 4.dp)) {
|
||||
Text(text = text, fontSize = 11.sp, style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,16 @@ class AccountFeedContentStates(
|
||||
val articlesFeed = FeedContentState(ArticlesFeedFilter(account), scope, LocalCache)
|
||||
val musicTracksFeed = FeedContentState(MusicTracksFeedFilter(account), scope, LocalCache)
|
||||
val musicPlaylistsFeed = FeedContentState(MusicPlaylistsFeedFilter(account), scope, LocalCache)
|
||||
val softwareAppsFeed = FeedContentState(SoftwareAppsFeedFilter(account), scope, LocalCache)
|
||||
val softwareAppsFeed =
|
||||
FeedContentState(
|
||||
SoftwareAppsFeedFilter(
|
||||
account = account,
|
||||
topFilterFlow = account.settings.defaultSoftwareAppsFollowList,
|
||||
liveFollowListsFlow = account.liveSoftwareAppsFollowLists,
|
||||
),
|
||||
scope,
|
||||
LocalCache,
|
||||
)
|
||||
|
||||
val notifications = CardFeedContentState(NotificationFeedFilter(account), scope)
|
||||
val notificationsFollowing = CardFeedContentState(NotificationFeedFilter(account, TopFilter.AllFollows), scope)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxLoaderState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Bundle of every flow a top-nav-filter consumer needs: the current
|
||||
* [TopFilter], the resolved follow list, the per-relay routing for relay
|
||||
* subscriptions, and a setter callback. Screens hand this to their TopBar,
|
||||
* their DAL filter (via [topFilterFlow] + [liveFollowListsFlow]), and their
|
||||
* relay subscription assembler (via [topFilterFlow] + [followsPerRelayFlow]).
|
||||
*
|
||||
* Use the account-default source (built from `account.settings.defaultXxxFollowList`
|
||||
* and the matching `account.liveXxxFollowLists*` flows) for the regular tab entry,
|
||||
* and [rememberLocalTopNavFilterSource] for a per-route override scoped to a
|
||||
* single back-stack entry.
|
||||
*/
|
||||
@Stable
|
||||
class TopNavFilterSource(
|
||||
val topFilterFlow: StateFlow<TopFilter>,
|
||||
val liveFollowListsFlow: StateFlow<IFeedTopNavFilter>,
|
||||
val followsPerRelayFlow: StateFlow<IFeedTopNavPerRelayFilterSet>,
|
||||
val onChange: (TopFilter) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Per-screen-instance TopNavFilterSource: builds a local [MutableStateFlow]
|
||||
* seeded with [initial], then derives the resolved follow list and per-relay
|
||||
* routing from it. The internal flows run on the supplied [scope] — callers
|
||||
* should pass `rememberCoroutineScope()` so the pipeline cancels when the
|
||||
* screen leaves composition, instead of `account.scope` which would keep
|
||||
* the flows alive until logout.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberLocalTopNavFilterSource(
|
||||
account: Account,
|
||||
initial: TopFilter,
|
||||
scope: CoroutineScope,
|
||||
cache: LocalCache = LocalCache,
|
||||
): TopNavFilterSource =
|
||||
remember(account, initial, scope) {
|
||||
val filter = MutableStateFlow(initial)
|
||||
val live = account.topNavFilterFlow(filter, scope)
|
||||
val perRelay = OutboxLoaderState(live, cache, scope).flow
|
||||
TopNavFilterSource(
|
||||
topFilterFlow = filter,
|
||||
liveFollowListsFlow = live,
|
||||
followsPerRelayFlow = perRelay,
|
||||
onChange = { filter.tryEmit(it) },
|
||||
)
|
||||
}
|
||||
@@ -29,11 +29,14 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
||||
@@ -45,6 +48,9 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareApplication
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TopNavFilterSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.rememberLocalTopNavFilterSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.dal.SoftwareAppsFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.datasource.SoftwareAppsFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
@@ -52,34 +58,85 @@ import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.Softw
|
||||
|
||||
@Composable
|
||||
fun SoftwareAppsScreen(
|
||||
route: Route.SoftwareApps,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
SoftwareAppsScreen(
|
||||
feedContentState = accountViewModel.feedStates.softwareAppsFeed,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
val account = accountViewModel.account
|
||||
val override = route.topFilter
|
||||
if (override == null) {
|
||||
val sharedSource =
|
||||
remember(account) {
|
||||
TopNavFilterSource(
|
||||
topFilterFlow = account.settings.defaultSoftwareAppsFollowList,
|
||||
liveFollowListsFlow = account.liveSoftwareAppsFollowLists,
|
||||
followsPerRelayFlow = account.liveSoftwareAppsFollowListsPerRelay,
|
||||
onChange = account.settings::changeDefaultSoftwareAppsFollowList,
|
||||
)
|
||||
}
|
||||
SoftwareAppsScreen(
|
||||
feedContentState = accountViewModel.feedStates.softwareAppsFeed,
|
||||
source = sharedSource,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
} else {
|
||||
val scope = rememberCoroutineScope()
|
||||
val localSource = rememberLocalTopNavFilterSource(account, override, scope)
|
||||
val localFeed =
|
||||
remember(account, localSource, scope) {
|
||||
FeedContentState(
|
||||
SoftwareAppsFeedFilter(
|
||||
account = account,
|
||||
topFilterFlow = localSource.topFilterFlow,
|
||||
liveFollowListsFlow = localSource.liveFollowListsFlow,
|
||||
),
|
||||
scope,
|
||||
LocalCache,
|
||||
)
|
||||
}
|
||||
SoftwareAppsScreen(
|
||||
feedContentState = localFeed,
|
||||
source = localSource,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SoftwareAppsScreen(
|
||||
feedContentState: FeedContentState,
|
||||
source: TopNavFilterSource,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
WatchLifecycleAndUpdateModel(feedContentState)
|
||||
WatchAccountForSoftwareAppsScreen(feedContentState = feedContentState, accountViewModel = accountViewModel)
|
||||
SoftwareAppsFilterAssemblerSubscription(accountViewModel)
|
||||
WatchAccountForSoftwareAppsScreen(
|
||||
feedContentState = feedContentState,
|
||||
source = source,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
SoftwareAppsFilterAssemblerSubscription(
|
||||
dataSource = accountViewModel.dataSources().softwareApps,
|
||||
feedContentState = feedContentState,
|
||||
topFilterFlow = source.topFilterFlow,
|
||||
followsPerRelayFlow = source.followsPerRelayFlow,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
DisappearingScaffold(
|
||||
isInvertedLayout = false,
|
||||
topBar = {
|
||||
SoftwareAppsTopBar(accountViewModel, nav)
|
||||
SoftwareAppsTopBar(
|
||||
source = source,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
AppBottomBar(Route.SoftwareApps, nav, accountViewModel) { route ->
|
||||
if (route == Route.SoftwareApps) {
|
||||
AppBottomBar(Route.SoftwareApps(), nav, accountViewModel) { route ->
|
||||
if (route is Route.SoftwareApps) {
|
||||
feedContentState.sendToTop()
|
||||
} else {
|
||||
nav.navBottomBar(route)
|
||||
@@ -113,9 +170,10 @@ fun SoftwareAppsScreen(
|
||||
@Composable
|
||||
fun WatchAccountForSoftwareAppsScreen(
|
||||
feedContentState: FeedContentState,
|
||||
source: TopNavFilterSource,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val listState by accountViewModel.account.liveSoftwareAppsFollowLists.collectAsStateWithLifecycle()
|
||||
val listState by source.liveFollowListsFlow.collectAsStateWithLifecycle()
|
||||
val hiddenUsers =
|
||||
accountViewModel.account.hiddenUsers.flow
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -31,22 +31,23 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.FeedDefinition
|
||||
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TopNavFilterSource
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@Composable
|
||||
fun SoftwareAppsTopBar(
|
||||
source: TopNavFilterSource,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
UserDrawerSearchTopBar(accountViewModel, nav) {
|
||||
val list by accountViewModel.account.settings.defaultSoftwareAppsFollowList
|
||||
.collectAsStateWithLifecycle()
|
||||
val list by source.topFilterFlow.collectAsStateWithLifecycle()
|
||||
|
||||
SoftwareAppsTopNavFilterBar(
|
||||
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||
listName = list,
|
||||
accountViewModel = accountViewModel,
|
||||
onChange = accountViewModel.account.settings::changeDefaultSoftwareAppsFollowList,
|
||||
onChange = { source.onChange(it.code) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,23 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.model.filterIntoSet
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class SoftwareAppsFeedFilter(
|
||||
val account: Account,
|
||||
val topFilterFlow: StateFlow<TopFilter>,
|
||||
val liveFollowListsFlow: StateFlow<IFeedTopNavFilter>,
|
||||
) : AdditiveFeedFilter<Note>() {
|
||||
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
||||
|
||||
override fun limit() = 200
|
||||
|
||||
fun followList(): TopFilter = account.settings.defaultSoftwareAppsFollowList.value
|
||||
fun followList(): TopFilter = topFilterFlow.value
|
||||
|
||||
fun TopFilter.isMuteList() = this is TopFilter.MuteList
|
||||
|
||||
@@ -61,7 +65,7 @@ class SoftwareAppsFeedFilter(
|
||||
|
||||
fun buildFilterParams(account: Account): FilterByListParams =
|
||||
FilterByListParams.create(
|
||||
account.liveSoftwareAppsFollowLists.value,
|
||||
liveFollowListsFlow.value,
|
||||
account.hiddenUsers.flow.value,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +22,19 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.datasource
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class SoftwareAppsQueryState(
|
||||
val account: Account,
|
||||
val feedStates: AccountFeedContentStates,
|
||||
val feedContentState: FeedContentState,
|
||||
val topFilterFlow: StateFlow<TopFilter>,
|
||||
val followsPerRelayFlow: StateFlow<IFeedTopNavPerRelayFilterSet>,
|
||||
val scope: CoroutineScope,
|
||||
)
|
||||
|
||||
|
||||
@@ -24,12 +24,19 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Composable
|
||||
fun SoftwareAppsFilterAssemblerSubscription(accountViewModel: AccountViewModel) {
|
||||
SoftwareAppsFilterAssemblerSubscription(
|
||||
accountViewModel.dataSources().softwareApps,
|
||||
accountViewModel.feedStates.softwareAppsFeed,
|
||||
accountViewModel.account.settings.defaultSoftwareAppsFollowList,
|
||||
accountViewModel.account.liveSoftwareAppsFollowListsPerRelay,
|
||||
accountViewModel,
|
||||
)
|
||||
}
|
||||
@@ -37,11 +44,20 @@ fun SoftwareAppsFilterAssemblerSubscription(accountViewModel: AccountViewModel)
|
||||
@Composable
|
||||
fun SoftwareAppsFilterAssemblerSubscription(
|
||||
dataSource: SoftwareAppsFilterAssembler,
|
||||
feedContentState: FeedContentState,
|
||||
topFilterFlow: StateFlow<TopFilter>,
|
||||
followsPerRelayFlow: StateFlow<IFeedTopNavPerRelayFilterSet>,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val state =
|
||||
remember(accountViewModel.account) {
|
||||
SoftwareAppsQueryState(accountViewModel.account, accountViewModel.feedStates, accountViewModel.viewModelScope)
|
||||
remember(accountViewModel.account, feedContentState, topFilterFlow, followsPerRelayFlow) {
|
||||
SoftwareAppsQueryState(
|
||||
account = accountViewModel.account,
|
||||
feedContentState = feedContentState,
|
||||
topFilterFlow = topFilterFlow,
|
||||
followsPerRelayFlow = followsPerRelayFlow,
|
||||
scope = accountViewModel.viewModelScope,
|
||||
)
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
|
||||
@@ -42,22 +42,14 @@ class SoftwareAppsSubAssembler(
|
||||
key: SoftwareAppsQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter> {
|
||||
val feedSettings = key.followsPerRelay()
|
||||
val feedSettings = key.followsPerRelayFlow.value
|
||||
|
||||
return makeSoftwareAppsFilter(feedSettings, since, key.feedStates.softwareAppsFeed.lastNoteCreatedAtIfFilled())
|
||||
return makeSoftwareAppsFilter(feedSettings, since, key.feedContentState.lastNoteCreatedAtIfFilled())
|
||||
}
|
||||
|
||||
override fun user(key: SoftwareAppsQueryState) = key.account.userProfile()
|
||||
|
||||
override fun list(key: SoftwareAppsQueryState) = key.listName()
|
||||
|
||||
fun SoftwareAppsQueryState.listNameFlow() = account.settings.defaultSoftwareAppsFollowList
|
||||
|
||||
fun SoftwareAppsQueryState.listName() = listNameFlow().value
|
||||
|
||||
fun SoftwareAppsQueryState.followsPerRelayFlow() = account.liveSoftwareAppsFollowListsPerRelay
|
||||
|
||||
fun SoftwareAppsQueryState.followsPerRelay() = followsPerRelayFlow().value
|
||||
override fun list(key: SoftwareAppsQueryState) = key.topFilterFlow.value
|
||||
|
||||
val userJobMap = mutableMapOf<User, List<Job>>()
|
||||
|
||||
@@ -68,17 +60,17 @@ class SoftwareAppsSubAssembler(
|
||||
userJobMap[user] =
|
||||
listOf(
|
||||
key.scope.launch(Dispatchers.IO) {
|
||||
key.listNameFlow().collectLatest {
|
||||
key.topFilterFlow.collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
key.scope.launch(Dispatchers.IO) {
|
||||
key.followsPerRelayFlow().sample(500).collectLatest {
|
||||
key.followsPerRelayFlow.sample(500).collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
key.account.scope.launch(Dispatchers.IO) {
|
||||
key.feedStates.softwareAppsFeed.lastNoteCreatedAtWhenFullyLoaded.sample(5000).collectLatest {
|
||||
key.feedContentState.lastNoteCreatedAtWhenFullyLoaded.sample(5000).collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user