Makes the new Video events non replaceable, while keeping the old ones in the replaceable

This commit is contained in:
Vitor Pamplona
2025-09-03 17:44:40 -04:00
parent 8131f3db39
commit 8d73bf2cc4
11 changed files with 186 additions and 63 deletions

View File

@@ -1044,13 +1044,13 @@ object LocalCache : ILocalCache {
event: VideoNormalEvent, event: VideoNormalEvent,
relay: NormalizedRelayUrl?, relay: NormalizedRelayUrl?,
wasVerified: Boolean, wasVerified: Boolean,
) = consumeBaseReplaceable(event, relay, wasVerified) ) = consumeRegularEvent(event, relay, wasVerified)
private fun consume( private fun consume(
event: VideoShortEvent, event: VideoShortEvent,
relay: NormalizedRelayUrl?, relay: NormalizedRelayUrl?,
wasVerified: Boolean, wasVerified: Boolean,
) = consumeBaseReplaceable(event, relay, wasVerified) ) = consumeRegularEvent(event, relay, wasVerified)
fun consume( fun consume(
event: StatusEvent, event: StatusEvent,

View File

@@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.imageModifier import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
@@ -75,18 +76,20 @@ fun VideoDisplay(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val event = (note.event as? VideoEvent) ?: return val videoEvent = (note.event as? VideoEvent) ?: return
val imeta = event.imetaTags().firstOrNull() ?: return val event = (videoEvent as? Event) ?: return
val title = event.title() val imeta = videoEvent.imetaTags().firstOrNull() ?: return
val summary = event.content.ifBlank { null }?.takeIf { title != it }
val title = videoEvent.title()
val summary = videoEvent.content.ifBlank { null }?.takeIf { title != it }
val image = imeta.image.firstOrNull() val image = imeta.image.firstOrNull()
val isYouTube = imeta.url.contains("youtube.com") || imeta.url.contains("youtu.be") val isYouTube = imeta.url.contains("youtube.com") || imeta.url.contains("youtu.be")
val tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList } val tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList }
val content by val content by
remember(note) { remember(note) {
val description = event.content.ifBlank { null } ?: event.alt() val description = videoEvent.content.ifBlank { null } ?: event.alt()
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url) val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
val uri = note.toNostrUri() val uri = note.toNostrUri()
@@ -187,11 +190,11 @@ fun VideoDisplay(
nav = nav, nav = nav,
) )
if (event.hasHashtags()) { if (videoEvent.hasHashtags()) {
Row( Row(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
) { ) {
DisplayUncitedHashtags(event, summary, callbackUri, accountViewModel, nav) DisplayUncitedHashtags(videoEvent, summary, callbackUri, accountViewModel, nav)
} }
} }
} }

View File

@@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip71Video.VideoEvent import com.vitorpamplona.quartz.nip71Video.VideoEvent
@@ -43,8 +44,10 @@ fun JustVideoDisplay(
contentScale: ContentScale, contentScale: ContentScale,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
val event = (note.event as? VideoEvent) ?: return val videoEvent = (note.event as? VideoEvent) ?: return
val imeta = event.imetaTags().getOrNull(0) ?: return val event = (videoEvent as? Event) ?: return
val imeta = videoEvent.imetaTags().getOrNull(0) ?: return
val content by val content by
remember(note) { remember(note) {

View File

@@ -237,6 +237,9 @@ open class BasicRelayClient(
text: String, text: String,
onConnected: () -> Unit, onConnected: () -> Unit,
) { ) {
if (text.contains("2e9244f75b36d47116b8c8bd5c4ea4d29fb1a3d64688d2524156af34c6124dc3")) {
Log.d(logTag, "AABBCC Receiving: $text")
}
// Log.d(logTag, "Receiving: $text") // Log.d(logTag, "Receiving: $text")
stats.addBytesReceived(text.bytesUsedInMemory()) stats.addBytesReceived(text.bytesUsedInMemory())

View File

@@ -0,0 +1,73 @@
/**
* 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.quartz.nip71Video
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
import com.vitorpamplona.quartz.nip71Video.tags.DurationTag
import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag
@Immutable
abstract class RegularVideoEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, kind, tags, content, sig),
PublishedAtProvider,
VideoEvent,
RootScope {
@Transient var iMetas: List<VideoMeta>? = null
override fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
override fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse)
override fun textTrack() = tags.mapNotNull(ETag::parse)
override fun segments() = tags.mapNotNull(SegmentTag::parse)
override fun participants() = tags.mapNotNull(PTag::parse)
override fun hashtags() = tags.hashtags()
override fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse)
override fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse)
override fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it }
}

View File

@@ -0,0 +1,73 @@
/**
* 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.quartz.nip71Video
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
import com.vitorpamplona.quartz.nip71Video.tags.DurationTag
import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag
@Immutable
abstract class ReplaceableVideoEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseReplaceableEvent(id, pubKey, createdAt, kind, tags, content, sig),
PublishedAtProvider,
VideoEvent,
RootScope {
@Transient var iMetas: List<VideoMeta>? = null
override fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
override fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse)
override fun textTrack() = tags.mapNotNull(ETag::parse)
override fun segments() = tags.mapNotNull(SegmentTag::parse)
override fun participants() = tags.mapNotNull(PTag::parse)
override fun hashtags() = tags.hashtags()
override fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse)
override fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse)
override fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it }
}

View File

@@ -21,52 +21,30 @@
package com.vitorpamplona.quartz.nip71Video package com.vitorpamplona.quartz.nip71Video
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent import com.vitorpamplona.quartz.nip01Core.core.IEvent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
import com.vitorpamplona.quartz.nip71Video.tags.DurationTag
import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag
@Immutable @Immutable
abstract class VideoEvent( interface VideoEvent : IEvent {
id: HexKey, fun title(): String?
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig),
PublishedAtProvider,
RootScope {
@Transient var iMetas: List<VideoMeta>? = null
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) fun publishedAt(): Long?
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse) fun duration(): Int?
fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse) fun textTrack(): List<ETag>
fun textTrack() = tags.mapNotNull(ETag::parse) fun segments(): List<SegmentTag>
fun segments() = tags.mapNotNull(SegmentTag::parse) fun participants(): List<PTag>
fun participants() = tags.mapNotNull(PTag::parse) fun hashtags(): List<String>
fun hashtags() = tags.hashtags() fun mimeType(): String?
private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) fun hash(): String?
private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) fun imetaTags(): List<VideoMeta>
fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it }
} }

View File

@@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip71Video.videoIMetas
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID import java.util.UUID
@@ -38,7 +39,7 @@ class VideoHorizontalEvent(
tags: Array<Array<String>>, tags: Array<Array<String>>,
content: String, content: String,
sig: HexKey, sig: HexKey,
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), ) : ReplaceableVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope { RootScope {
companion object { companion object {
const val KIND = 34235 const val KIND = 34235

View File

@@ -24,11 +24,9 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
@Immutable @Immutable
class VideoNormalEvent( class VideoNormalEvent(
@@ -38,7 +36,7 @@ class VideoNormalEvent(
tags: Array<Array<String>>, tags: Array<Array<String>>,
content: String, content: String,
sig: HexKey, sig: HexKey,
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), ) : RegularVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope { RootScope {
companion object { companion object {
const val KIND = 21 const val KIND = 21
@@ -47,10 +45,9 @@ class VideoNormalEvent(
fun build( fun build(
video: VideoMeta, video: VideoMeta,
description: String, description: String,
dTag: String = UUID.randomUUID().toString(),
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {}, initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {},
) = build(description, dTag, createdAt) { ) = build(description, createdAt) {
videoIMeta(video) videoIMeta(video)
initializer() initializer()
} }
@@ -58,21 +55,18 @@ class VideoNormalEvent(
fun build( fun build(
video: List<VideoMeta>, video: List<VideoMeta>,
description: String, description: String,
dTag: String = UUID.randomUUID().toString(),
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {}, initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {},
) = build(description, dTag, createdAt) { ) = build(description, createdAt) {
videoIMetas(video) videoIMetas(video)
initializer() initializer()
} }
fun build( fun build(
description: String, description: String,
dTag: String = UUID.randomUUID().toString(),
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {}, initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {},
) = eventTemplate(KIND, description, createdAt) { ) = eventTemplate(KIND, description, createdAt) {
dTag(dTag)
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
initializer() initializer()
} }

View File

@@ -24,11 +24,9 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
@Immutable @Immutable
class VideoShortEvent( class VideoShortEvent(
@@ -38,7 +36,7 @@ class VideoShortEvent(
tags: Array<Array<String>>, tags: Array<Array<String>>,
content: String, content: String,
sig: HexKey, sig: HexKey,
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), ) : RegularVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope { RootScope {
companion object { companion object {
const val KIND = 22 const val KIND = 22
@@ -47,21 +45,18 @@ class VideoShortEvent(
fun build( fun build(
video: VideoMeta, video: VideoMeta,
description: String, description: String,
dTag: String = UUID.randomUUID().toString(),
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<VideoShortEvent>.() -> Unit = {}, initializer: TagArrayBuilder<VideoShortEvent>.() -> Unit = {},
) = build(description, dTag, createdAt) { ) = build(description, createdAt) {
videoIMeta(video) videoIMeta(video)
initializer() initializer()
} }
fun build( fun build(
description: String, description: String,
dTag: String = UUID.randomUUID().toString(),
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<VideoShortEvent>.() -> Unit = {}, initializer: TagArrayBuilder<VideoShortEvent>.() -> Unit = {},
) = eventTemplate(KIND, description, createdAt) { ) = eventTemplate(KIND, description, createdAt) {
dTag(dTag)
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
initializer() initializer()
} }

View File

@@ -38,7 +38,7 @@ class VideoVerticalEvent(
tags: Array<Array<String>>, tags: Array<Array<String>>,
content: String, content: String,
sig: HexKey, sig: HexKey,
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), ) : ReplaceableVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope { RootScope {
companion object { companion object {
const val KIND = 34236 const val KIND = 34236