mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 16:30:49 +00:00
Adds support for displaying video events.
This commit is contained in:
parent
57430c4366
commit
2de3d19a34
@ -130,7 +130,7 @@ class Account(
|
|||||||
var translateTo: String = Locale.getDefault().language,
|
var translateTo: String = Locale.getDefault().language,
|
||||||
var zapAmountChoices: List<Long> = DefaultZapAmounts,
|
var zapAmountChoices: List<Long> = DefaultZapAmounts,
|
||||||
var reactionChoices: List<String> = DefaultReactions,
|
var reactionChoices: List<String> = DefaultReactions,
|
||||||
var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE,
|
var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PUBLIC,
|
||||||
var defaultFileServer: Nip96MediaServers.ServerName = Nip96MediaServers.DEFAULT[0],
|
var defaultFileServer: Nip96MediaServers.ServerName = Nip96MediaServers.DEFAULT[0],
|
||||||
var defaultHomeFollowList: MutableStateFlow<String> = MutableStateFlow(KIND3_FOLLOWS),
|
var defaultHomeFollowList: MutableStateFlow<String> = MutableStateFlow(KIND3_FOLLOWS),
|
||||||
var defaultStoriesFollowList: MutableStateFlow<String> = MutableStateFlow(GLOBAL_FOLLOWS),
|
var defaultStoriesFollowList: MutableStateFlow<String> = MutableStateFlow(GLOBAL_FOLLOWS),
|
||||||
|
@ -72,6 +72,8 @@ import com.vitorpamplona.quartz.events.RepostEvent
|
|||||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
||||||
import com.vitorpamplona.quartz.events.StatusEvent
|
import com.vitorpamplona.quartz.events.StatusEvent
|
||||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||||
|
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@ -438,6 +440,9 @@ object LocalCache {
|
|||||||
private fun consume(event: PinListEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
private fun consume(event: PinListEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||||
private fun consume(event: RelaySetEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
private fun consume(event: RelaySetEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||||
private fun consume(event: AudioTrackEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
private fun consume(event: AudioTrackEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||||
|
private fun consume(event: VideoVerticalEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||||
|
private fun consume(event: VideoHorizontalEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||||
|
|
||||||
fun consume(event: StatusEvent, relay: Relay?) {
|
fun consume(event: StatusEvent, relay: Relay?) {
|
||||||
val version = getOrCreateNote(event.id)
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
@ -1593,7 +1598,8 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
is StatusEvent -> consume(event, relay)
|
is StatusEvent -> consume(event, relay)
|
||||||
is TextNoteEvent -> consume(event, relay)
|
is TextNoteEvent -> consume(event, relay)
|
||||||
|
is VideoHorizontalEvent -> consume(event, relay)
|
||||||
|
is VideoVerticalEvent -> consume(event, relay)
|
||||||
else -> {
|
else -> {
|
||||||
Log.w("Event Not Supported", event.toJson())
|
Log.w("Event Not Supported", event.toJson())
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,7 @@ import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifier
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.boostedNoteModifier
|
import com.vitorpamplona.amethyst.ui.theme.boostedNoteModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier
|
import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||||
import com.vitorpamplona.amethyst.ui.theme.normalNoteModifier
|
import com.vitorpamplona.amethyst.ui.theme.normalNoteModifier
|
||||||
@ -203,6 +204,9 @@ import com.vitorpamplona.quartz.events.ReportEvent
|
|||||||
import com.vitorpamplona.quartz.events.RepostEvent
|
import com.vitorpamplona.quartz.events.RepostEvent
|
||||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||||
import com.vitorpamplona.quartz.events.UserMetadata
|
import com.vitorpamplona.quartz.events.UserMetadata
|
||||||
|
import com.vitorpamplona.quartz.events.VideoEvent
|
||||||
|
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
||||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -636,7 +640,7 @@ fun LongCommunityHeader(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteEvent.hasHashtags()) {
|
if (summary != null && noteEvent.hasHashtags()) {
|
||||||
DisplayUncitedHashtags(
|
DisplayUncitedHashtags(
|
||||||
remember(noteEvent) { noteEvent.hashtags().toImmutableList() },
|
remember(noteEvent) { noteEvent.hashtags().toImmutableList() },
|
||||||
summary ?: "",
|
summary ?: "",
|
||||||
@ -1171,6 +1175,14 @@ private fun RenderNoteRow(
|
|||||||
FileHeaderDisplay(baseNote, true, accountViewModel)
|
FileHeaderDisplay(baseNote, true, accountViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VideoHorizontalEvent -> {
|
||||||
|
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VideoVerticalEvent -> {
|
||||||
|
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
is FileStorageHeaderEvent -> {
|
is FileStorageHeaderEvent -> {
|
||||||
FileStorageHeaderDisplay(baseNote, true, accountViewModel)
|
FileStorageHeaderDisplay(baseNote, true, accountViewModel)
|
||||||
}
|
}
|
||||||
@ -2384,7 +2396,21 @@ private fun ReplyRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showReply) {
|
if (showReply) {
|
||||||
val replyingDirectlyTo = remember { note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.kind } }
|
val replyingDirectlyTo = remember(note) {
|
||||||
|
if (noteEvent is BaseTextNoteEvent) {
|
||||||
|
val replyingTo = noteEvent.replyingTo()
|
||||||
|
if (replyingTo != null) {
|
||||||
|
note.replyTo?.firstOrNull() {
|
||||||
|
// important to test both ids in case it's a replaceable event.
|
||||||
|
it.idHex == replyingTo || it.event?.id() == replyingTo
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.kind }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.kind }
|
||||||
|
}
|
||||||
|
}
|
||||||
if (replyingDirectlyTo != null && unPackReply) {
|
if (replyingDirectlyTo != null && unPackReply) {
|
||||||
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
|
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
|
||||||
Spacer(modifier = StdVertSpacer)
|
Spacer(modifier = StdVertSpacer)
|
||||||
@ -3028,6 +3054,129 @@ fun FileHeaderDisplay(note: Note, roundedCorner: Boolean, accountViewModel: Acco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoDisplay(
|
||||||
|
note: Note,
|
||||||
|
makeItShort: Boolean,
|
||||||
|
canPreview: Boolean,
|
||||||
|
backgroundColor: MutableState<Color>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val event = (note.event as? VideoEvent) ?: return
|
||||||
|
val fullUrl = event.url() ?: return
|
||||||
|
|
||||||
|
val title = event.title()
|
||||||
|
val summary = event.content.ifBlank { null }?.takeIf { title != it }
|
||||||
|
val image = event.thumb() ?: event.image()
|
||||||
|
val isYouTube = fullUrl.contains("youtube.com") || fullUrl.contains("youtu.be")
|
||||||
|
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
||||||
|
|
||||||
|
val content by remember(note) {
|
||||||
|
val blurHash = event.blurhash()
|
||||||
|
val hash = event.hash()
|
||||||
|
val dimensions = event.dimensions()
|
||||||
|
val description = event.alt() ?: event.content
|
||||||
|
val isImage = imageExtensions.any {
|
||||||
|
removeQueryParamsForExtensionComparison(fullUrl).lowercase().endsWith(it)
|
||||||
|
}
|
||||||
|
val uri = note.toNostrUri()
|
||||||
|
|
||||||
|
mutableStateOf<ZoomableContent>(
|
||||||
|
if (isImage) {
|
||||||
|
ZoomableUrlImage(
|
||||||
|
url = fullUrl,
|
||||||
|
description = description,
|
||||||
|
hash = hash,
|
||||||
|
blurhash = blurHash,
|
||||||
|
dim = dimensions,
|
||||||
|
uri = uri
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ZoomableUrlVideo(
|
||||||
|
url = fullUrl,
|
||||||
|
description = description,
|
||||||
|
hash = hash,
|
||||||
|
dim = dimensions,
|
||||||
|
uri = uri,
|
||||||
|
authorName = note.author?.toBestDisplayName(),
|
||||||
|
artworkUri = event.thumb() ?: event.image()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SensitivityWarning(note = note, accountViewModel = accountViewModel) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 5.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
if (isYouTube) {
|
||||||
|
val uri = LocalUriHandler.current
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.clickable { runCatching { uri.openUri(fullUrl) } }
|
||||||
|
) {
|
||||||
|
image?.let {
|
||||||
|
AsyncImage(
|
||||||
|
model = it,
|
||||||
|
contentDescription = stringResource(
|
||||||
|
R.string.preview_card_image_for,
|
||||||
|
it
|
||||||
|
),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = MaterialTheme.colorScheme.imageModifier
|
||||||
|
)
|
||||||
|
} ?: CreateImageHeader(note, accountViewModel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ZoomableContentView(
|
||||||
|
content = content,
|
||||||
|
roundedCorner = true,
|
||||||
|
accountViewModel = accountViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
title?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
summary?.let {
|
||||||
|
TranslatableRichTextViewer(
|
||||||
|
content = it,
|
||||||
|
canPreview = canPreview && !makeItShort,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
tags = tags,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.hasHashtags()) {
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
DisplayUncitedHashtags(
|
||||||
|
remember(event) { event.hashtags().toImmutableList() },
|
||||||
|
summary ?: "",
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FileStorageHeaderDisplay(baseNote: Note, roundedCorner: Boolean, accountViewModel: AccountViewModel) {
|
fun FileStorageHeaderDisplay(baseNote: Note, roundedCorner: Boolean, accountViewModel: AccountViewModel) {
|
||||||
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
||||||
|
@ -96,6 +96,7 @@ import com.vitorpamplona.amethyst.ui.note.RenderPoll
|
|||||||
import com.vitorpamplona.amethyst.ui.note.RenderPostApproval
|
import com.vitorpamplona.amethyst.ui.note.RenderPostApproval
|
||||||
import com.vitorpamplona.amethyst.ui.note.RenderRepost
|
import com.vitorpamplona.amethyst.ui.note.RenderRepost
|
||||||
import com.vitorpamplona.amethyst.ui.note.RenderTextEvent
|
import com.vitorpamplona.amethyst.ui.note.RenderTextEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.VideoDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@ -130,6 +131,7 @@ import com.vitorpamplona.quartz.events.PinListEvent
|
|||||||
import com.vitorpamplona.quartz.events.PollNoteEvent
|
import com.vitorpamplona.quartz.events.PollNoteEvent
|
||||||
import com.vitorpamplona.quartz.events.RelaySetEvent
|
import com.vitorpamplona.quartz.events.RelaySetEvent
|
||||||
import com.vitorpamplona.quartz.events.RepostEvent
|
import com.vitorpamplona.quartz.events.RepostEvent
|
||||||
|
import com.vitorpamplona.quartz.events.VideoEvent
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -423,6 +425,8 @@ fun NoteMaster(
|
|||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
|
} else if (noteEvent is VideoEvent) {
|
||||||
|
VideoDisplay(baseNote, false, true, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is FileHeaderEvent) {
|
} else if (noteEvent is FileHeaderEvent) {
|
||||||
FileHeaderDisplay(baseNote, true, accountViewModel)
|
FileHeaderDisplay(baseNote, true, accountViewModel)
|
||||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||||
|
@ -23,6 +23,13 @@ open class BaseTextNoteEvent(
|
|||||||
fun mentions() = taggedUsers()
|
fun mentions() = taggedUsers()
|
||||||
open fun replyTos() = taggedEvents()
|
open fun replyTos() = taggedEvents()
|
||||||
|
|
||||||
|
fun replyingTo(): HexKey? {
|
||||||
|
val oldStylePositional = tags.lastOrNull() { it.size > 1 && it[0] == "e" }?.get(1)
|
||||||
|
val newStyle = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1)
|
||||||
|
|
||||||
|
return newStyle ?: oldStylePositional
|
||||||
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var citedUsersCache: Set<HexKey>? = null
|
private var citedUsersCache: Set<HexKey>? = null
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class GoalEvent(
|
|||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
onReady: (GoalEvent) -> Unit
|
onReady: (GoalEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
var tags = mutableListOf(
|
val tags = mutableListOf(
|
||||||
arrayOf(AMOUNT, amount.toString()),
|
arrayOf(AMOUNT, amount.toString()),
|
||||||
arrayOf("relays") + relays,
|
arrayOf("relays") + relays,
|
||||||
arrayOf("alt", alt)
|
arrayOf("alt", alt)
|
||||||
|
@ -28,6 +28,11 @@ abstract class VideoEvent(
|
|||||||
fun torrentInfoHash() = tags.firstOrNull { it.size > 1 && it[0] == TORRENT_INFOHASH }?.get(1)
|
fun torrentInfoHash() = tags.firstOrNull { it.size > 1 && it[0] == TORRENT_INFOHASH }?.get(1)
|
||||||
fun blurhash() = tags.firstOrNull { it.size > 1 && it[0] == BLUR_HASH }?.get(1)
|
fun blurhash() = tags.firstOrNull { it.size > 1 && it[0] == BLUR_HASH }?.get(1)
|
||||||
|
|
||||||
|
fun title() = tags.firstOrNull { it.size > 1 && it[0] == TITLE }?.get(1)
|
||||||
|
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == SUMMARY }?.get(1)
|
||||||
|
fun image() = tags.firstOrNull { it.size > 1 && it[0] == IMAGE }?.get(1)
|
||||||
|
fun thumb() = tags.firstOrNull { it.size > 1 && it[0] == THUMB }?.get(1)
|
||||||
|
|
||||||
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
|
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
Loading…
Reference in New Issue
Block a user