mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 08:20:51 +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 zapAmountChoices: List<Long> = DefaultZapAmounts,
|
||||
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 defaultHomeFollowList: MutableStateFlow<String> = MutableStateFlow(KIND3_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.StatusEvent
|
||||
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.persistentSetOf
|
||||
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: RelaySetEvent, 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?) {
|
||||
val version = getOrCreateNote(event.id)
|
||||
val note = getOrCreateAddressableNote(event.address())
|
||||
@ -1593,7 +1598,8 @@ object LocalCache {
|
||||
}
|
||||
is StatusEvent -> consume(event, relay)
|
||||
is TextNoteEvent -> consume(event, relay)
|
||||
|
||||
is VideoHorizontalEvent -> consume(event, relay)
|
||||
is VideoVerticalEvent -> consume(event, relay)
|
||||
else -> {
|
||||
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.channelNotePictureModifier
|
||||
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.newItemBackgroundColor
|
||||
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.TextNoteEvent
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@ -636,7 +640,7 @@ fun LongCommunityHeader(
|
||||
)
|
||||
}
|
||||
|
||||
if (noteEvent.hasHashtags()) {
|
||||
if (summary != null && noteEvent.hasHashtags()) {
|
||||
DisplayUncitedHashtags(
|
||||
remember(noteEvent) { noteEvent.hashtags().toImmutableList() },
|
||||
summary ?: "",
|
||||
@ -1171,6 +1175,14 @@ private fun RenderNoteRow(
|
||||
FileHeaderDisplay(baseNote, true, accountViewModel)
|
||||
}
|
||||
|
||||
is VideoHorizontalEvent -> {
|
||||
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is VideoVerticalEvent -> {
|
||||
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is FileStorageHeaderEvent -> {
|
||||
FileStorageHeaderDisplay(baseNote, true, accountViewModel)
|
||||
}
|
||||
@ -2384,7 +2396,21 @@ private fun ReplyRow(
|
||||
}
|
||||
|
||||
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) {
|
||||
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
|
||||
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
|
||||
fun FileStorageHeaderDisplay(baseNote: Note, roundedCorner: Boolean, accountViewModel: AccountViewModel) {
|
||||
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.RenderRepost
|
||||
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.timeAgo
|
||||
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.RelaySetEvent
|
||||
import com.vitorpamplona.quartz.events.RepostEvent
|
||||
import com.vitorpamplona.quartz.events.VideoEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -423,6 +425,8 @@ fun NoteMaster(
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
} else if (noteEvent is VideoEvent) {
|
||||
VideoDisplay(baseNote, false, true, backgroundColor, accountViewModel, nav)
|
||||
} else if (noteEvent is FileHeaderEvent) {
|
||||
FileHeaderDisplay(baseNote, true, accountViewModel)
|
||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||
|
@ -23,6 +23,13 @@ open class BaseTextNoteEvent(
|
||||
fun mentions() = taggedUsers()
|
||||
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
|
||||
private var citedUsersCache: Set<HexKey>? = null
|
||||
|
||||
|
@ -39,7 +39,7 @@ class GoalEvent(
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (GoalEvent) -> Unit
|
||||
) {
|
||||
var tags = mutableListOf(
|
||||
val tags = mutableListOf(
|
||||
arrayOf(AMOUNT, amount.toString()),
|
||||
arrayOf("relays") + relays,
|
||||
arrayOf("alt", alt)
|
||||
|
@ -28,6 +28,11 @@ abstract class VideoEvent(
|
||||
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 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 }
|
||||
|
||||
companion object {
|
||||
|
Loading…
Reference in New Issue
Block a user