mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
revert(meetingrooms): drop the kind:30313 drawer split
User changed direction — the Nests / Meeting Rooms separation was
the wrong call. Kind 30313 stays out of the Nests list, but doesn't
get its own drawer entry either; it surfaces through the standard
NoteCompose / thread paths instead.
Reverted:
- Deleted: meetingrooms/MeetingRoomsScreen, MeetingRoomsTopBar,
MeetingRoomsFeedLoaded, dal/MeetingRoomsFeedFilter.
- Removed Route.MeetingRooms, NavBarItem.MEETING_ROOMS, the
catalog entry, the drawer entry.
- Removed meetingRoomsFeed from AccountFeedContentStates and
ScrollStateKeys.MEETING_ROOMS_SCREEN.
- Removed `meeting_rooms` string.
- Updated AppNavigation imports.
Kept:
- NestsFeedFilter narrowed to kind:30312 only (per user: "only
show kind 30312 in the items of the NestScreen"). The dual-kind
branches stay gone.
- Doc comment updated to note that kind:30313 renders on the
standard NoteCompose / thread paths outside the Nests drawer.
https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
This commit is contained in:
@@ -68,7 +68,6 @@ object ScrollStateKeys {
|
||||
const val FOLLOW_PACKS_SCREEN = "FollowPacksFeed"
|
||||
const val LIVE_STREAMS_SCREEN = "LiveStreamsFeed"
|
||||
const val NESTS_SCREEN = "NestsFeed"
|
||||
const val MEETING_ROOMS_SCREEN = "MeetingRoomsFeed"
|
||||
const val LONGS_SCREEN = "LongsFeed"
|
||||
const val ARTICLES_SCREEN = "ArticlesFeed"
|
||||
|
||||
|
||||
@@ -127,7 +127,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleL
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.LiveStreamsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.LongsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.meetingrooms.MeetingRoomsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen
|
||||
@@ -247,7 +246,6 @@ fun BuildNavigation(
|
||||
composableFromEnd<Route.FollowPacks> { FollowPacksScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.LiveStreams> { LiveStreamsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.Nests> { NestsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.MeetingRooms> { MeetingRoomsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.Longs> { LongsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.Articles> { ArticlesScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.NewHlsVideo> { NewHlsVideoScreen(accountViewModel, nav) }
|
||||
|
||||
-9
@@ -55,7 +55,6 @@ enum class NavBarItem {
|
||||
FOLLOW_PACKS,
|
||||
LIVE_STREAMS,
|
||||
NESTS,
|
||||
MEETING_ROOMS,
|
||||
LONGS,
|
||||
POLLS,
|
||||
BADGES,
|
||||
@@ -220,13 +219,6 @@ val NavBarCatalog: Map<NavBarItem, NavBarItemDef> =
|
||||
icon = MaterialSymbols.Mic,
|
||||
resolveRoute = { Route.Nests },
|
||||
),
|
||||
NavBarItem.MEETING_ROOMS to
|
||||
NavBarItemDef(
|
||||
id = NavBarItem.MEETING_ROOMS,
|
||||
labelRes = R.string.meeting_rooms,
|
||||
icon = MaterialSymbols.Groups,
|
||||
resolveRoute = { Route.MeetingRooms },
|
||||
),
|
||||
NavBarItem.LONGS to
|
||||
NavBarItemDef(
|
||||
id = NavBarItem.LONGS,
|
||||
@@ -314,7 +306,6 @@ val DrawerFeedsItems: List<NavBarItem> =
|
||||
NavBarItem.FOLLOW_PACKS,
|
||||
NavBarItem.LIVE_STREAMS,
|
||||
if (isDebug) NavBarItem.NESTS else null,
|
||||
if (isDebug) NavBarItem.MEETING_ROOMS else null,
|
||||
NavBarItem.LONGS,
|
||||
NavBarItem.POLLS,
|
||||
NavBarItem.BADGES,
|
||||
|
||||
@@ -91,8 +91,6 @@ sealed class Route {
|
||||
|
||||
@Serializable object Nests : Route()
|
||||
|
||||
@Serializable object MeetingRooms : Route()
|
||||
|
||||
@Serializable object Longs : Route()
|
||||
|
||||
@Serializable object Articles : Route()
|
||||
|
||||
-4
@@ -47,7 +47,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeLiveFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeNewThreadFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.dal.LiveStreamsFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.dal.LongsFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.meetingrooms.dal.MeetingRoomsFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.dal.NestsFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationSummaryState
|
||||
@@ -103,7 +102,6 @@ class AccountFeedContentStates(
|
||||
val followPacksFeed = FeedContentState(FollowPacksFeedFilter(account), scope, LocalCache)
|
||||
val liveStreamsFeed = FeedContentState(LiveStreamsFeedFilter(account), scope, LocalCache)
|
||||
val nestsFeed = FeedContentState(NestsFeedFilter(account), scope, LocalCache)
|
||||
val meetingRoomsFeed = FeedContentState(MeetingRoomsFeedFilter(account), scope, LocalCache)
|
||||
val longsFeed = FeedContentState(LongsFeedFilter(account), scope, LocalCache)
|
||||
val articlesFeed = FeedContentState(ArticlesFeedFilter(account), scope, LocalCache)
|
||||
|
||||
@@ -171,7 +169,6 @@ class AccountFeedContentStates(
|
||||
followPacksFeed.updateFeedWith(newNotes)
|
||||
liveStreamsFeed.updateFeedWith(newNotes)
|
||||
nestsFeed.updateFeedWith(newNotes)
|
||||
meetingRoomsFeed.updateFeedWith(newNotes)
|
||||
longsFeed.updateFeedWith(newNotes)
|
||||
articlesFeed.updateFeedWith(newNotes)
|
||||
|
||||
@@ -219,7 +216,6 @@ class AccountFeedContentStates(
|
||||
followPacksFeed.deleteFromFeed(newNotes)
|
||||
liveStreamsFeed.deleteFromFeed(newNotes)
|
||||
nestsFeed.deleteFromFeed(newNotes)
|
||||
meetingRoomsFeed.deleteFromFeed(newNotes)
|
||||
longsFeed.deleteFromFeed(newNotes)
|
||||
articlesFeed.deleteFromFeed(newNotes)
|
||||
|
||||
|
||||
-107
@@ -1,107 +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.meetingrooms
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
|
||||
|
||||
/**
|
||||
* Read-only list of NIP-53 kind 30313 [MeetingRoomEvent]s. Each row
|
||||
* mirrors the Discovery card UI ([RenderLiveActivityThumb]) — cover
|
||||
* image with status flag, participant gallery overlay, and the
|
||||
* channel header (creator, name, like + zap reactions) at the
|
||||
* bottom. Tapping a row opens the standard thread view via
|
||||
* `routeFor` so the user can see the meeting's full details and
|
||||
* any `streaming` URL.
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MeetingRoomsFeedLoaded(
|
||||
loaded: FeedState.Loaded,
|
||||
listState: LazyListState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val items by loaded.feed.collectAsStateWithLifecycle()
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = rememberFeedContentPadding(FeedPadding),
|
||||
state = listState,
|
||||
) {
|
||||
itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item ->
|
||||
Row(Modifier.fillMaxWidth().animateItem()) {
|
||||
MeetingRoomFeedCard(
|
||||
baseNote = item,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MeetingRoomFeedCard(
|
||||
baseNote: Note,
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
if (baseNote.event !is MeetingRoomEvent) return
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
modifier.clickable {
|
||||
nav.nav { routeFor(baseNote, accountViewModel.account) }
|
||||
},
|
||||
) {
|
||||
Column(StdPadding) {
|
||||
RenderLiveActivityThumb(baseNote, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
-130
@@ -1,130 +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.meetingrooms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
|
||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssemblerSubscription
|
||||
|
||||
/**
|
||||
* Drawer screen for NIP-53 kind 30313 (Meeting). Reads from the
|
||||
* `meetingRoomsFeed` content state which scans
|
||||
* `LocalCache.liveChatChannels` for `MeetingRoomEvent`s; the wire
|
||||
* subscription is the same `makeLiveActivitiesFilter` the Nests
|
||||
* screen uses (it requests kinds 30311/30312/30313/1311 in one
|
||||
* REQ), so mounting the `NestsFilterAssemblerSubscription` here
|
||||
* keeps wire traffic identical whichever screen the user lands
|
||||
* on first.
|
||||
*
|
||||
* Read-only list — the app doesn't compose kind-30313 events
|
||||
* locally. The per-row CTA (Join, recording link, etc.) lives
|
||||
* inside [com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent].
|
||||
*/
|
||||
@Composable
|
||||
fun MeetingRoomsScreen(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
MeetingRoomsScreen(
|
||||
meetingRoomsFeedContentState = accountViewModel.feedStates.meetingRoomsFeed,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MeetingRoomsScreen(
|
||||
meetingRoomsFeedContentState: FeedContentState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
WatchLifecycleAndUpdateModel(meetingRoomsFeedContentState)
|
||||
WatchAccountForMeetingRoomsScreen(meetingRoomsFeedState = meetingRoomsFeedContentState, accountViewModel = accountViewModel)
|
||||
NestsFilterAssemblerSubscription(accountViewModel)
|
||||
|
||||
DisappearingScaffold(
|
||||
isInvertedLayout = false,
|
||||
topBar = {
|
||||
MeetingRoomsTopBar(accountViewModel, nav)
|
||||
},
|
||||
bottomBar = {
|
||||
AppBottomBar(Route.MeetingRooms, accountViewModel) { route ->
|
||||
if (route == Route.MeetingRooms) {
|
||||
meetingRoomsFeedContentState.sendToTop()
|
||||
} else {
|
||||
nav.navBottomBar(route)
|
||||
}
|
||||
}
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
RefresheableBox(meetingRoomsFeedContentState, true) {
|
||||
SaveableFeedContentState(meetingRoomsFeedContentState, scrollStateKey = ScrollStateKeys.MEETING_ROOMS_SCREEN) { listState ->
|
||||
RenderFeedContentState(
|
||||
feedContentState = meetingRoomsFeedContentState,
|
||||
accountViewModel = accountViewModel,
|
||||
listState = listState,
|
||||
nav = nav,
|
||||
routeForLastRead = "MeetingRoomsFeed",
|
||||
onLoaded = { loaded ->
|
||||
MeetingRoomsFeedLoaded(
|
||||
loaded = loaded,
|
||||
listState = listState,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WatchAccountForMeetingRoomsScreen(
|
||||
meetingRoomsFeedState: FeedContentState,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val listState by accountViewModel.account.liveLiveStreamsFollowLists.collectAsStateWithLifecycle()
|
||||
// Use `by` to unwrap the StateFlow's value into the LaunchedEffect
|
||||
// key — without it, the State<T> object is reference-stable across
|
||||
// recompositions and the effect never re-fires when the user
|
||||
// mutes someone, leaving the feed stale until a manual refresh.
|
||||
val hiddenUsers by accountViewModel.account.hiddenUsers.flow
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(accountViewModel, listState, hiddenUsers) {
|
||||
meetingRoomsFeedState.checkKeysInvalidateDataAndSendToTop()
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +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.meetingrooms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.FeedFilterSpinner
|
||||
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.stringRes
|
||||
|
||||
@Composable
|
||||
fun MeetingRoomsTopBar(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
UserDrawerSearchTopBar(accountViewModel, nav) {
|
||||
val list by accountViewModel.account.settings.defaultLiveStreamsFollowList
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
MeetingRoomsTopNavFilterBar(
|
||||
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||
listName = list,
|
||||
accountViewModel = accountViewModel,
|
||||
onChange = accountViewModel.account.settings::changeDefaultLiveStreamsFollowList,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MeetingRoomsTopNavFilterBar(
|
||||
followListsModel: TopNavFilterState,
|
||||
listName: TopFilter,
|
||||
accountViewModel: AccountViewModel,
|
||||
onChange: (FeedDefinition) -> Unit,
|
||||
) {
|
||||
val allLists by followListsModel.kind3GlobalPeopleRoutes.collectAsStateWithLifecycle()
|
||||
|
||||
FeedFilterSpinner(
|
||||
placeholderCode = listName,
|
||||
explainer = stringRes(R.string.select_list_to_filter),
|
||||
options = allLists,
|
||||
onSelect = onChange,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
-132
@@ -1,132 +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.meetingrooms.dal
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
||||
import com.vitorpamplona.amethyst.model.TopFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag
|
||||
|
||||
/**
|
||||
* Drawer feed for NIP-53 kind 30313 (Meeting). Pulls
|
||||
* [MeetingRoomEvent]s out of `LocalCache.liveChatChannels` — which is
|
||||
* the same backing store the Live Streams and Nests feeds walk —
|
||||
* and presents them in their own drawer entry so the audio-rooms
|
||||
* surface (kind 30312) doesn't mix scheduled meetings into its list.
|
||||
*
|
||||
* Required-field gate: a kind-30313 with a blank title is
|
||||
* unrenderable, so it's skipped at the feed level — same UX rule
|
||||
* the Nests feed applies to its own missing-fields case.
|
||||
*/
|
||||
class MeetingRoomsFeedFilter(
|
||||
val account: Account,
|
||||
) : AdditiveFeedFilter<Note>() {
|
||||
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
||||
|
||||
override fun limit() = 50
|
||||
|
||||
fun followList(): TopFilter = account.settings.defaultLiveStreamsFollowList.value
|
||||
|
||||
private fun TopFilter.isMuteList() = this is TopFilter.MuteList
|
||||
|
||||
private fun TopFilter.isBlockList() = this is TopFilter.PeopleList && this.address == account.blockPeopleList.getBlockListAddress()
|
||||
|
||||
private fun TopFilter.wantsToSeeNegativeStuff() = isMuteList() || isBlockList()
|
||||
|
||||
override fun showHiddenKey(): Boolean = followList().wantsToSeeNegativeStuff()
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val allNotes = LocalCache.liveChatChannels.mapNotNull { _, channel -> LocalCache.getAddressableNoteIfExists(channel.address) }
|
||||
return sort(innerApplyFilter(allNotes))
|
||||
}
|
||||
|
||||
override fun applyFilter(newItems: Set<Note>): Set<Note> = innerApplyFilter(newItems)
|
||||
|
||||
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||
val filterParams =
|
||||
FilterByListParams.create(
|
||||
followLists = account.liveLiveStreamsFollowLists.value,
|
||||
hiddenUsers = account.hiddenUsers.flow.value,
|
||||
)
|
||||
|
||||
return collection.filterTo(HashSet()) {
|
||||
val noteEvent = it.event as? MeetingRoomEvent ?: return@filterTo false
|
||||
!noteEvent.title().isNullOrBlank() && filterParams.match(noteEvent, it.relays)
|
||||
}
|
||||
}
|
||||
|
||||
override fun sort(items: Set<Note>): List<Note> {
|
||||
val topFilter = account.liveLiveStreamsFollowLists.value
|
||||
val topFilterAuthors =
|
||||
when (topFilter) {
|
||||
is AuthorsByOutboxTopNavFilter -> topFilter.authors
|
||||
is MutedAuthorsByOutboxTopNavFilter -> topFilter.authors
|
||||
is AllFollowsByOutboxTopNavFilter -> topFilter.authors
|
||||
is SingleCommunityTopNavFilter -> topFilter.authors
|
||||
is AuthorsByProxyTopNavFilter -> topFilter.authors
|
||||
is MutedAuthorsByProxyTopNavFilter -> topFilter.authors
|
||||
is AllFollowsByProxyTopNavFilter -> topFilter.authors
|
||||
else -> null
|
||||
}
|
||||
|
||||
val followingKeySet = topFilterAuthors ?: account.kind3FollowList.flow.value.authors
|
||||
|
||||
val counter = ParticipantListBuilder()
|
||||
val participantCounts = items.associate { it to counter.countFollowsThatParticipateOn(it, followingKeySet) }
|
||||
val allParticipants = items.associate { it to counter.countFollowsThatParticipateOn(it, null) }
|
||||
|
||||
return items
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ convertStatusToOrder(it.event as? MeetingRoomEvent) },
|
||||
{ participantCounts[it] },
|
||||
{ allParticipants[it] },
|
||||
{
|
||||
when (val e = it.event) {
|
||||
is MeetingRoomEvent -> e.starts() ?: it.createdAt()
|
||||
else -> it.createdAt()
|
||||
}
|
||||
},
|
||||
{ it.idHex },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
private fun convertStatusToOrder(event: MeetingRoomEvent?): Int =
|
||||
when (event?.status()) {
|
||||
StatusTag.STATUS.LIVE -> 2
|
||||
StatusTag.STATUS.PLANNED -> 1
|
||||
StatusTag.STATUS.ENDED -> 0
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -40,10 +40,12 @@ import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag
|
||||
/**
|
||||
* Drawer feed for NIP-53 kind 30312 (Interactive Rooms / audio spaces).
|
||||
*
|
||||
* Shares LocalCache.liveChatChannels with the Live Streams feed, narrowed
|
||||
* to MeetingSpaceEvent (30312) so the Clubhouse-style audio-room surface
|
||||
* is independent of video live streams. Kind 30313 (NIP-53 meetings) lives
|
||||
* in a sibling drawer entry — see [com.vitorpamplona.amethyst.ui.screen.loggedIn.meetingrooms.dal.MeetingRoomsFeedFilter].
|
||||
* Shares LocalCache.liveChatChannels with the Live Streams feed,
|
||||
* narrowed to MeetingSpaceEvent (30312) so the audio-room surface
|
||||
* is independent of video live streams. Kind 30313 (NIP-53 meetings)
|
||||
* intentionally does NOT surface here — the room concept and the
|
||||
* meeting concept render on the standard NoteCompose / thread paths
|
||||
* outside the Nests drawer.
|
||||
*/
|
||||
class NestsFeedFilter(
|
||||
val account: Account,
|
||||
|
||||
@@ -489,7 +489,6 @@
|
||||
<string name="follow_packs">Follow Packs</string>
|
||||
<string name="live_streams">Live Streams</string>
|
||||
<string name="nests">Nests</string>
|
||||
<string name="meeting_rooms">Meeting Rooms</string>
|
||||
<string name="nest_stage">Stage</string>
|
||||
<string name="nest_audience">Audience</string>
|
||||
<string name="nest_raise_hand">Raise hand</string>
|
||||
|
||||
Reference in New Issue
Block a user