feat(live-chat): surface stream clips in the profile gallery tab
Clips are authored by viewers but carry a `p` tag identifying the
stream host, so they naturally belong on the host's profile. Extend
the profile media subscription with `{kinds:[1313], #p:[pubkey]}`,
accept LiveActivitiesClipEvent in UserProfileGalleryFeedFilter when
the clip references a stream hosted by this user, and render the clip's
`r`-tag URL as a MediaUrlVideo tile in GalleryThumbnail.
Also plug LiveActivitiesClipEvent into NoteCompose via the existing
RenderChatClip so tapping a clip tile opens a working thread view
instead of an empty one.
https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
This commit is contained in:
@@ -172,6 +172,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderZapPoll
|
||||
import com.vitorpamplona.amethyst.ui.note.types.ReplyRenderType
|
||||
import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderChatClip
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.RenderPublicChatChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
@@ -250,6 +251,7 @@ 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.clip.LiveActivitiesClipEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
@@ -938,6 +940,10 @@ private fun RenderNoteRow(
|
||||
RenderLnZap(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is LiveActivitiesClipEvent -> {
|
||||
RenderChatClip(baseNote, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is FhirResourceEvent -> {
|
||||
RenderFhirResource(baseNote, accountViewModel, nav)
|
||||
}
|
||||
|
||||
+25
-10
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||
@@ -51,16 +52,30 @@ fun filterUserProfileMedia(
|
||||
?: user.allUsedRelaysOrNull()
|
||||
?: LocalCache.relayHints.hintsForKey(user.pubkeyHex)
|
||||
|
||||
return relays.map { relay ->
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = UserProfileMediaKinds,
|
||||
authors = listOf(user.pubkeyHex),
|
||||
limit = 200,
|
||||
since = since?.get(relay)?.time,
|
||||
),
|
||||
return relays.flatMap { relay ->
|
||||
listOf(
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = UserProfileMediaKinds,
|
||||
authors = listOf(user.pubkeyHex),
|
||||
limit = 200,
|
||||
since = since?.get(relay)?.time,
|
||||
),
|
||||
),
|
||||
// Clips are authored by viewers but carry a `p` tag identifying the stream host.
|
||||
// Fetch clips OF this profile's streams so they surface in their gallery.
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(LiveActivitiesClipEvent.KIND),
|
||||
tags = mapOf("p" to listOf(user.pubkeyHex)),
|
||||
limit = 100,
|
||||
since = since?.get(relay)?.time,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
||||
|
||||
@@ -130,6 +131,21 @@ fun GalleryThumbnail(
|
||||
thumbhash = imeta.thumbhash,
|
||||
)
|
||||
}
|
||||
} else if (noteEvent is LiveActivitiesClipEvent) {
|
||||
noteEvent.videoUrl()?.let { url ->
|
||||
listOf(
|
||||
MediaUrlVideo(
|
||||
url = url,
|
||||
description = noteEvent.title() ?: noteEvent.content,
|
||||
hash = null,
|
||||
blurhash = null,
|
||||
dim = null,
|
||||
uri = null,
|
||||
mimeType = null,
|
||||
thumbhash = null,
|
||||
),
|
||||
)
|
||||
} ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
+18
-11
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.RegularVideoEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.ReplaceableVideoEvent
|
||||
@@ -75,17 +76,23 @@ class UserProfileGalleryFeedFilter(
|
||||
user: User,
|
||||
): Boolean {
|
||||
val noteEvent = it.event
|
||||
return (
|
||||
(
|
||||
it.event?.pubKey == user.pubkeyHex &&
|
||||
(
|
||||
noteEvent is PictureEvent ||
|
||||
noteEvent is RegularVideoEvent ||
|
||||
(noteEvent is ReplaceableVideoEvent && it is AddressableNote) ||
|
||||
(noteEvent is ProfileGalleryEntryEvent && noteEvent.hasUrl() && noteEvent.hasFromEvent())
|
||||
)
|
||||
) // && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))
|
||||
) &&
|
||||
val authoredByUser =
|
||||
it.event?.pubKey == user.pubkeyHex &&
|
||||
(
|
||||
noteEvent is PictureEvent ||
|
||||
noteEvent is RegularVideoEvent ||
|
||||
(noteEvent is ReplaceableVideoEvent && it is AddressableNote) ||
|
||||
(noteEvent is ProfileGalleryEntryEvent && noteEvent.hasUrl() && noteEvent.hasFromEvent())
|
||||
)
|
||||
|
||||
// Clips are authored by viewers, not the host — accept them when they reference
|
||||
// a stream hosted by this user AND carry a playable URL.
|
||||
val clipOfUsersStream =
|
||||
noteEvent is LiveActivitiesClipEvent &&
|
||||
noteEvent.host() == user.pubkeyHex &&
|
||||
!noteEvent.videoUrl().isNullOrBlank()
|
||||
|
||||
return (authoredByUser || clipOfUsersStream) &&
|
||||
params.match(noteEvent, it.relays) &&
|
||||
account.isAcceptable(it)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user