diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt index 3e7ab3c069..4528457987 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt @@ -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" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 3b43962176..69c66edcfc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -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 { FollowPacksScreen(accountViewModel, nav) } composableFromEnd { LiveStreamsScreen(accountViewModel, nav) } composableFromEnd { NestsScreen(accountViewModel, nav) } - composableFromEnd { MeetingRoomsScreen(accountViewModel, nav) } composableFromEnd { LongsScreen(accountViewModel, nav) } composableFromEnd { ArticlesScreen(accountViewModel, nav) } composableFromEnd { NewHlsVideoScreen(accountViewModel, nav) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt index c4d48f48e7..85bf299b70 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt @@ -55,7 +55,6 @@ enum class NavBarItem { FOLLOW_PACKS, LIVE_STREAMS, NESTS, - MEETING_ROOMS, LONGS, POLLS, BADGES, @@ -220,13 +219,6 @@ val NavBarCatalog: Map = 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.FOLLOW_PACKS, NavBarItem.LIVE_STREAMS, if (isDebug) NavBarItem.NESTS else null, - if (isDebug) NavBarItem.MEETING_ROOMS else null, NavBarItem.LONGS, NavBarItem.POLLS, NavBarItem.BADGES, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index 0f575c4703..bf8742969a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -91,8 +91,6 @@ sealed class Route { @Serializable object Nests : Route() - @Serializable object MeetingRooms : Route() - @Serializable object Longs : Route() @Serializable object Articles : Route() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt index 1166c2ec68..8c9e821b9e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt @@ -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) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsFeedLoaded.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsFeedLoaded.kt deleted file mode 100644 index c7516f51a8..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsFeedLoaded.kt +++ /dev/null @@ -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) - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsScreen.kt deleted file mode 100644 index 8afe67486c..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsScreen.kt +++ /dev/null @@ -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 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() - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsTopBar.kt deleted file mode 100644 index 7be825d9e4..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/MeetingRoomsTopBar.kt +++ /dev/null @@ -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, - ) -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/dal/MeetingRoomsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/dal/MeetingRoomsFeedFilter.kt deleted file mode 100644 index c0585f5e09..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/meetingrooms/dal/MeetingRoomsFeedFilter.kt +++ /dev/null @@ -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() { - 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 { - val allNotes = LocalCache.liveChatChannels.mapNotNull { _, channel -> LocalCache.getAddressableNoteIfExists(channel.address) } - return sort(innerApplyFilter(allNotes)) - } - - override fun applyFilter(newItems: Set): Set = innerApplyFilter(newItems) - - private fun innerApplyFilter(collection: Collection): Set { - 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): List { - 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 - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/dal/NestsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/dal/NestsFeedFilter.kt index e213299012..90c06ca77c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/dal/NestsFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/dal/NestsFeedFilter.kt @@ -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, diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 24d3bd5b6d..e18e41a0c2 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -489,7 +489,6 @@ Follow Packs Live Streams Nests - Meeting Rooms Stage Audience Raise hand