diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 31fd76865..94b434f5d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -163,6 +163,9 @@ import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent import com.vitorpamplona.quartz.nip52Calendar.calendar.CalendarEvent import com.vitorpamplona.quartz.nip52Calendar.rsvp.CalendarRSVPEvent import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent +import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip56Reports.ReportEvent @@ -2623,6 +2626,9 @@ object LocalCache : ILocalCache, ICacheProvider { is LabeledBookmarkListEvent -> consumeBaseReplaceable(event, relay, wasVerified) is LiveActivitiesEvent -> consume(event, relay, wasVerified) is LiveActivitiesChatMessageEvent -> consume(event, relay, wasVerified) + is MeetingSpaceEvent -> consumeBaseReplaceable(event, relay, wasVerified) + is MeetingRoomEvent -> consumeBaseReplaceable(event, relay, wasVerified) + is MeetingRoomPresenceEvent -> consumeBaseReplaceable(event, relay, wasVerified) is LnZapEvent -> consume(event, relay, wasVerified) is LnZapRequestEvent -> consume(event, relay, wasVerified) is NIP90StatusEvent -> consumeRegularEvent(event, relay, wasVerified) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index e97d6e8c6..320033df7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -138,6 +138,8 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessChallenge import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessGameEnd import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent +import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent +import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingSpaceEvent import com.vitorpamplona.amethyst.ui.note.types.RenderMintRecommendation import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90Status @@ -251,6 +253,8 @@ import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip56Reports.ReportEvent @@ -987,6 +991,14 @@ private fun RenderNoteRow( RenderLiveActivityEvent(baseNote, accountViewModel, nav) } + is MeetingSpaceEvent -> { + RenderMeetingSpaceEvent(baseNote, accountViewModel, nav) + } + + is MeetingRoomEvent -> { + RenderMeetingRoomEvent(baseNote, accountViewModel, nav) + } + is GitRepositoryEvent -> { RenderGitRepositoryEvent(baseNote, accountViewModel, nav) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt new file mode 100644 index 000000000..23657075f --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt @@ -0,0 +1,328 @@ +/* + * 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.note.types + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor +import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture +import com.vitorpamplona.amethyst.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ScheduledFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CrossfadeCheckIfVideoIsOnline +import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.SmallBorder +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import java.util.Locale +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag as MeetingSpaceStatusTag + +@Composable +fun RenderMeetingSpaceEvent( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + Row(modifier = Modifier.padding(top = 5.dp)) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + RenderMeetingSpaceEventInner(baseNote = baseNote, accountViewModel, nav) + } + } +} + +@Composable +fun RenderMeetingSpaceEventInner( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + val noteEvent = baseNote.event as? MeetingSpaceEvent ?: return + + val eventUpdates by observeNote(baseNote, accountViewModel) + + val roomName = remember(eventUpdates) { noteEvent.room() } + val summary = remember(eventUpdates) { noteEvent.summary() } + val status = remember(eventUpdates) { noteEvent.status() } + val participants = remember(eventUpdates) { noteEvent.participants() } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .padding(vertical = 5.dp) + .fillMaxWidth(), + ) { + roomName?.let { + Text( + text = it, + fontWeight = FontWeight.Bold, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + } + + Spacer(modifier = StdHorzSpacer) + + CrossfadeIfEnabled(targetState = status, label = "MeetingSpaceStatus", accountViewModel = accountViewModel) { + when (it) { + MeetingSpaceStatusTag.STATUS.OPEN -> { + MeetingSpaceOpenFlag() + } + + MeetingSpaceStatusTag.STATUS.PRIVATE -> { + MeetingSpacePrivateFlag() + } + + MeetingSpaceStatusTag.STATUS.CLOSED -> { + MeetingSpaceClosedFlag() + } + + null -> {} + } + } + } + + summary?.let { + Text( + text = it, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.placeholderText, + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 5.dp), + ) + } + + RenderParticipants(participants, accountViewModel, nav) +} + +@Composable +fun RenderMeetingRoomEvent( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + Row(modifier = Modifier.padding(top = 5.dp)) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + RenderMeetingRoomEventInner(baseNote = baseNote, accountViewModel, nav) + } + } +} + +@Composable +fun RenderMeetingRoomEventInner( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + val noteEvent = baseNote.event as? MeetingRoomEvent ?: return + + val eventUpdates by observeNote(baseNote, accountViewModel) + + val subject = remember(eventUpdates) { noteEvent.title() } + val media = remember(eventUpdates) { noteEvent.streaming() } + val status = remember(eventUpdates) { noteEvent.status() } + val starts = remember(eventUpdates) { noteEvent.starts() } + val participants = remember(eventUpdates) { noteEvent.participants() } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .padding(vertical = 5.dp) + .fillMaxWidth(), + ) { + subject?.let { + Text( + text = it, + fontWeight = FontWeight.Bold, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + } + + Spacer(modifier = StdHorzSpacer) + + CrossfadeIfEnabled(targetState = status, label = "MeetingRoomStatus", accountViewModel = accountViewModel) { + when (it) { + StatusTag.STATUS.LIVE -> { + media?.let { + CrossfadeCheckIfVideoIsOnline(it, accountViewModel) { + LiveFlag() + } + } ?: LiveFlag() + } + + StatusTag.STATUS.PLANNED -> { + ScheduledFlag(starts) + } + + StatusTag.STATUS.ENDED -> {} + + null -> {} + } + } + } + + RenderParticipants(participants, accountViewModel, nav) +} + +@Composable +private fun RenderParticipants( + participants: List, + accountViewModel: AccountViewModel, + nav: INav, +) { + var participantUsers by remember { + mutableStateOf>>( + persistentListOf(), + ) + } + + LaunchedEffect(key1 = participants) { + accountViewModel.loadParticipants(participants) { newParticipantUsers -> + if (!equalImmutableLists(newParticipantUsers, participantUsers)) { + participantUsers = newParticipantUsers + } + } + } + + participantUsers.forEach { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .padding(vertical = 5.dp) + .clickable { nav.nav(routeFor(it.second)) }, + ) { + ClickableUserPicture(it.second, 25.dp, accountViewModel) + Spacer(StdHorzSpacer) + UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) + Spacer(StdHorzSpacer) + it.first.role?.let { + Text( + text = + it.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() + }, + color = MaterialTheme.colorScheme.placeholderText, + maxLines = 1, + ) + } + } + } +} + +@Composable +fun MeetingSpaceOpenFlag() { + Text( + text = stringRes(id = R.string.meeting_space_open_tag), + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + modifier = + remember { + Modifier + .clip(SmallBorder) + .background(Color(0xFF4CAF50)) + .padding(horizontal = 5.dp) + }, + ) +} + +@Composable +fun MeetingSpacePrivateFlag() { + Text( + text = stringRes(id = R.string.meeting_space_private_tag), + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + modifier = + remember { + Modifier + .clip(SmallBorder) + .background(Color(0xFFFF9800)) + .padding(horizontal = 5.dp) + }, + ) +} + +@Composable +fun MeetingSpaceClosedFlag() { + Text( + text = stringRes(id = R.string.meeting_space_closed_tag), + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + modifier = + remember { + Modifier + .clip(SmallBorder) + .background(Color.Black) + .padding(horizontal = 5.dp) + }, + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt index bcf2cd36e..9927240cf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt @@ -46,6 +46,8 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent @@ -149,6 +151,8 @@ fun InnerChannelCardWithReactions( ) { when (baseNote.event) { is LiveActivitiesEvent -> InnerCardRow(baseNote, accountViewModel, nav) + is MeetingSpaceEvent -> InnerCardRow(baseNote, accountViewModel, nav) + is MeetingRoomEvent -> InnerCardRow(baseNote, accountViewModel, nav) is CommunityDefinitionEvent -> InnerCardRow(baseNote, accountViewModel, nav) is ChannelCreateEvent -> InnerCardRow(baseNote, accountViewModel, nav) is ClassifiedsEvent -> InnerCardBox(baseNote, accountViewModel, nav) @@ -202,6 +206,8 @@ private fun RenderNoteRow( ) { when (baseNote.event) { is LiveActivitiesEvent -> RenderLiveActivityThumb(baseNote, accountViewModel, nav) + is MeetingSpaceEvent -> RenderLiveActivityThumb(baseNote, accountViewModel, nav) + is MeetingRoomEvent -> RenderLiveActivityThumb(baseNote, accountViewModel, nav) is CommunityDefinitionEvent -> RenderCommunitiesThumb(baseNote, accountViewModel, nav) is ChannelCreateEvent -> RenderPublicChatChannelThumb(baseNote, accountViewModel, nav) is AppDefinitionEvent -> RenderContentDVMThumb(baseNote, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt index 883366290..53f212001 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt @@ -35,6 +35,8 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthors import com.vitorpamplona.amethyst.service.OnlineChecker 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.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag @@ -75,7 +77,8 @@ open class DiscoverLiveFeedFilter( return collection.filterTo(HashSet()) { val noteEvent = it.event - noteEvent is LiveActivitiesEvent && filterParams.match(noteEvent, it.relays) + (noteEvent is LiveActivitiesEvent || noteEvent is MeetingSpaceEvent || noteEvent is MeetingRoomEvent) && + filterParams.match(noteEvent, it.relays) } } @@ -106,33 +109,61 @@ open class DiscoverLiveFeedFilter( return items .sortedWith( compareBy( - { convertStatusToOrder((it.event as? LiveActivitiesEvent)) }, + { convertStatusToOrder(it.event) }, { participantCounts[it] }, { allParticipants[it] }, - { (it.event as? LiveActivitiesEvent)?.starts() ?: it.createdAt() }, + { + when (val e = it.event) { + is LiveActivitiesEvent -> e.starts() ?: it.createdAt() + is MeetingRoomEvent -> e.starts() ?: it.createdAt() + else -> it.createdAt() + } + }, { it.idHex }, ), ).reversed() } - fun convertStatusToOrder(event: LiveActivitiesEvent?): Int { + fun convertStatusToOrder(event: com.vitorpamplona.quartz.nip01Core.core.Event?): Int { if (event == null) return 0 - val url = event.streaming() ?: return 0 - return when (event.status()) { - StatusTag.STATUS.LIVE -> { - if (OnlineChecker.isCachedAndOffline(url)) { - 0 - } else { - 2 + return when (event) { + is LiveActivitiesEvent -> { + val url = event.streaming() ?: return 0 + when (event.status()) { + StatusTag.STATUS.LIVE -> { + if (OnlineChecker.isCachedAndOffline(url)) 0 else 2 + } + + StatusTag.STATUS.PLANNED -> { + 1 + } + + StatusTag.STATUS.ENDED -> { + 0 + } + + else -> { + 0 + } } } - StatusTag.STATUS.PLANNED -> { - 1 + is MeetingRoomEvent -> { + when (event.status()) { + StatusTag.STATUS.LIVE -> 2 + StatusTag.STATUS.PLANNED -> 1 + StatusTag.STATUS.ENDED -> 0 + else -> 0 + } } - StatusTag.STATUS.ENDED -> { - 0 + is MeetingSpaceEvent -> { + when (event.status()) { + com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.OPEN -> 2 + com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.PRIVATE -> 1 + com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.CLOSED -> 0 + else -> 0 + } } else -> { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt index 3c89d5e9c..5737494c4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt @@ -69,6 +69,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutabl import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.quartz.nip01Core.core.Address +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag @@ -98,19 +100,69 @@ fun RenderLiveActivityThumb( nav: INav, ) { val card by observeNoteAndMap(baseNote, accountViewModel) { - val noteEvent = it.event as? LiveActivitiesEvent + when (val noteEvent = it.event) { + is LiveActivitiesEvent -> { + LiveActivityCard( + id = noteEvent.address(), + name = noteEvent.dTag(), + cover = noteEvent.image()?.ifBlank { null }, + media = noteEvent.streaming(), + subject = noteEvent.title()?.ifBlank { null }, + content = noteEvent.summary(), + participants = noteEvent.participants().toImmutableList(), + status = noteEvent.status(), + starts = noteEvent.starts(), + ) + } - LiveActivityCard( - id = noteEvent?.address(), - name = noteEvent?.dTag() ?: "", - cover = noteEvent?.image()?.ifBlank { null }, - media = noteEvent?.streaming(), - subject = noteEvent?.title()?.ifBlank { null }, - content = noteEvent?.summary(), - participants = noteEvent?.participants()?.toImmutableList() ?: persistentListOf(), - status = noteEvent?.status(), - starts = noteEvent?.starts(), - ) + is MeetingRoomEvent -> { + LiveActivityCard( + id = noteEvent.address(), + name = noteEvent.dTag(), + cover = noteEvent.image()?.ifBlank { null }, + media = noteEvent.streaming(), + subject = noteEvent.title()?.ifBlank { null }, + content = noteEvent.summary(), + participants = noteEvent.participants().toImmutableList(), + status = noteEvent.status(), + starts = noteEvent.starts(), + ) + } + + is MeetingSpaceEvent -> { + LiveActivityCard( + id = noteEvent.address(), + name = noteEvent.dTag(), + cover = noteEvent.image()?.ifBlank { null }, + media = null, + subject = noteEvent.room()?.ifBlank { null }, + content = noteEvent.summary(), + participants = noteEvent.participants().toImmutableList(), + status = + when (noteEvent.status()) { + com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.OPEN -> StatusTag.STATUS.LIVE + com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.PRIVATE -> StatusTag.STATUS.PLANNED + com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.CLOSED -> StatusTag.STATUS.ENDED + else -> null + }, + starts = null, + ) + } + + else -> { + LiveActivityCard( + id = null, + name = "", + cover = null, + media = null, + subject = null, + content = null, + participants = persistentListOf(), + status = null, + starts = null, + ) + } + } } RenderLiveActivityThumb( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAllCommunities.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAllCommunities.kt index d3bfb9a6c..02bdb35f6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAllCommunities.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAllCommunities.kt @@ -27,6 +27,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent @@ -47,7 +49,7 @@ fun filterLiveActivitiesAllCommunities( tags = mapOf( "a" to communityList, - "k" to listOf(LiveActivitiesChatMessageEvent.KIND.toString(), LiveActivitiesEvent.KIND.toString()), + "k" to listOf(LiveActivitiesChatMessageEvent.KIND.toString(), LiveActivitiesEvent.KIND.toString(), MeetingSpaceEvent.KIND.toString(), MeetingRoomEvent.KIND.toString()), ), limit = 300, since = since, @@ -59,7 +61,7 @@ fun filterLiveActivitiesAllCommunities( filter = Filter( tags = mapOf("k" to listOf("5300"), "a" to communityList), - kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), limit = 300, since = since, ), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAuthors.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAuthors.kt index 7f9beacd3..1f576d430 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAuthors.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByAuthors.kt @@ -28,6 +28,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent fun filterLiveActivitiesAuthors( @@ -42,7 +44,7 @@ fun filterLiveActivitiesAuthors( filter = Filter( authors = authorList, - kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), limit = 300, since = since, ), @@ -53,7 +55,7 @@ fun filterLiveActivitiesAuthors( filter = Filter( tags = mapOf("p" to authorList), - kinds = listOf(LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), limit = 100, since = since, ), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByCommunity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByCommunity.kt index b966a2c05..402d49088 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByCommunity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByCommunity.kt @@ -26,6 +26,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent @@ -47,7 +49,7 @@ fun filterLiveActivitiesByCommunity( tags = mapOf( "a" to listOf(community), - "k" to listOf(LiveActivitiesChatMessageEvent.KIND.toString(), LiveActivitiesEvent.KIND.toString()), + "k" to listOf(LiveActivitiesChatMessageEvent.KIND.toString(), LiveActivitiesEvent.KIND.toString(), MeetingSpaceEvent.KIND.toString(), MeetingRoomEvent.KIND.toString()), ), limit = 300, since = since, @@ -60,7 +62,7 @@ fun filterLiveActivitiesByCommunity( Filter( authors = authors, tags = mapOf("k" to listOf("5300"), "a" to listOf(community)), - kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), limit = 300, since = since, ), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByGeohash.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByGeohash.kt index d28012de3..7e5b1d032 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByGeohash.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByGeohash.kt @@ -26,6 +26,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent fun filterLiveActivitiesByGeohash( @@ -42,7 +44,7 @@ fun filterLiveActivitiesByGeohash( relay = relay, filter = Filter( - kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), tags = mapOf("g" to geoHashes), limit = 300, since = since, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByHashtag.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByHashtag.kt index cf39ea8aa..4ecad2cc7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByHashtag.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesByHashtag.kt @@ -27,6 +27,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent fun filterLiveActivitiesByHashtag( @@ -43,7 +45,7 @@ fun filterLiveActivitiesByHashtag( relay = relay, filter = Filter( - kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesChatMessageEvent.KIND, LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), tags = mapOf("t" to hashtags), limit = 300, since = since, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesGlobal.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesGlobal.kt index 9388597ba..2a0ac27a2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesGlobal.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/subassemblies/FilterLiveActivitiesGlobal.kt @@ -25,6 +25,8 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.utils.TimeUtils @@ -43,7 +45,7 @@ fun filterLiveActivitiesGlobal( relay = it.key, filter = Filter( - kinds = listOf(LiveActivitiesEvent.KIND), + kinds = listOf(LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), limit = 30, since = since ?: TimeUtils.oneWeekAgo(), ), diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 866522906..d91096bbf 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -919,6 +919,9 @@ Livestream is Offline Livestream Ended + OPEN + PRIVATE + CLOSED Logging out deletes all your local information. Make sure to have your private keys backed up to avoid losing your account. Do you want to continue? Followed Tags diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/KindRegistry.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/KindRegistry.kt index e914be294..eb6bf79b7 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/KindRegistry.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/KindRegistry.kt @@ -26,6 +26,8 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent @@ -56,7 +58,7 @@ object KindRegistry { "repost" to listOf(RepostEvent.KIND), "profile" to listOf(MetadataEvent.KIND), "channel" to listOf(ChannelCreateEvent.KIND, ChannelMetadataEvent.KIND), - "live" to listOf(LiveActivitiesEvent.KIND), + "live" to listOf(LiveActivitiesEvent.KIND, MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND), "community" to listOf(CommunityDefinitionEvent.KIND), "wiki" to listOf(WikiNoteEvent.KIND), "classified" to listOf(ClassifiedsEvent.KIND), diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt index 03a93d569..a56c7c0b7 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt @@ -37,6 +37,7 @@ enum class ROLE( HOST("host"), MODERATOR("moderator"), SPEAKER("speaker"), + PARTICIPANT("participant"), } @Immutable @@ -58,7 +59,7 @@ data class ParticipantTag( ensure(tag.has(3)) { return false } ensure(tag[0] == TAG_NAME) { return false } ensure(tag[1].length == 64) { return false } - ensure(tag[3] == ROLE.HOST.code) { return false } + ensure(tag[3].equals(ROLE.HOST.code, ignoreCase = true)) { return false } return true } @@ -76,7 +77,7 @@ data class ParticipantTag( ensure(tag.has(3)) { return null } ensure(tag[0] == TAG_NAME) { return null } ensure(tag[1].length == 64) { return null } - ensure(tag[3] == ROLE.HOST.code) { return null } + ensure(tag[3].equals(ROLE.HOST.code, ignoreCase = true)) { return null } val hint = RelayUrlNormalizer.normalizeOrNull(tag[2])