mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-25 18:01:27 +02:00
Implements contextual rounded corners for images and videos
This commit is contained in:
@@ -241,6 +241,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, accountViewModel: AccountViewMo
|
||||
postViewModel.galleryUri?.let {
|
||||
VideoView(
|
||||
videoUri = it.toString(),
|
||||
roundedCorner = false,
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
}
|
||||
|
@@ -385,7 +385,7 @@ fun NewPostView(
|
||||
)
|
||||
)
|
||||
} else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||
VideoView(myUrlPreview, accountViewModel = accountViewModel)
|
||||
VideoView(myUrlPreview, roundedCorner = true, accountViewModel = accountViewModel)
|
||||
} else {
|
||||
UrlPreview(myUrlPreview, myUrlPreview, accountViewModel)
|
||||
}
|
||||
@@ -1365,7 +1365,7 @@ fun ImageVideoDescription(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
VideoView(uri.toString(), accountViewModel = accountViewModel)
|
||||
VideoView(uri.toString(), roundedCorner = true, accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -287,7 +287,7 @@ private fun ZoomableContentView(
|
||||
) {
|
||||
state.imagesForPager[word]?.let {
|
||||
Box(modifier = HalfVertPadding) {
|
||||
ZoomableContentView(it, state.imageList, accountViewModel)
|
||||
ZoomableContentView(it, state.imageList, roundedCorner = true, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
@@ -71,6 +72,7 @@ import com.vitorpamplona.amethyst.ui.theme.PinBottomIconSize
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -88,6 +90,7 @@ fun LoadThumbAndThenVideoView(
|
||||
title: String? = null,
|
||||
thumbUri: String,
|
||||
authorName: String? = null,
|
||||
roundedCorner: Boolean,
|
||||
nostrUriCallback: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
onDialog: ((Boolean) -> Unit)? = null
|
||||
@@ -119,6 +122,7 @@ fun LoadThumbAndThenVideoView(
|
||||
videoUri = videoUri,
|
||||
title = title,
|
||||
thumb = VideoThumb(loadingFinished.second),
|
||||
roundedCorner = roundedCorner,
|
||||
artworkUri = thumbUri,
|
||||
authorName = authorName,
|
||||
nostrUriCallback = nostrUriCallback,
|
||||
@@ -130,6 +134,7 @@ fun LoadThumbAndThenVideoView(
|
||||
videoUri = videoUri,
|
||||
title = title,
|
||||
thumb = null,
|
||||
roundedCorner = roundedCorner,
|
||||
artworkUri = thumbUri,
|
||||
authorName = authorName,
|
||||
nostrUriCallback = nostrUriCallback,
|
||||
@@ -145,6 +150,7 @@ fun VideoView(
|
||||
videoUri: String,
|
||||
title: String? = null,
|
||||
thumb: VideoThumb? = null,
|
||||
roundedCorner: Boolean,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
artworkUri: String? = null,
|
||||
authorName: String? = null,
|
||||
@@ -160,6 +166,7 @@ fun VideoView(
|
||||
defaultToStart,
|
||||
title,
|
||||
thumb,
|
||||
roundedCorner,
|
||||
waveform,
|
||||
artworkUri,
|
||||
authorName,
|
||||
@@ -177,6 +184,7 @@ fun VideoViewInner(
|
||||
defaultToStart: Boolean = false,
|
||||
title: String? = null,
|
||||
thumb: VideoThumb? = null,
|
||||
roundedCorner: Boolean,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
artworkUri: String? = null,
|
||||
authorName: String? = null,
|
||||
@@ -231,7 +239,16 @@ fun VideoViewInner(
|
||||
defaultToStart = defaultToStart,
|
||||
nostrUriCallback = nostrUriCallback
|
||||
) { controller, keepPlaying ->
|
||||
RenderVideoPlayer(controller, thumb, waveform, keepPlaying, automaticallyStartPlayback, activeOnScreen, onDialog)
|
||||
RenderVideoPlayer(
|
||||
controller = controller,
|
||||
thumbData = thumb,
|
||||
roundedCorner = roundedCorner,
|
||||
waveform = waveform,
|
||||
keepPlaying = keepPlaying,
|
||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||
activeOnScreen = activeOnScreen,
|
||||
onDialog = onDialog
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,6 +500,7 @@ data class VideoThumb(
|
||||
private fun RenderVideoPlayer(
|
||||
controller: MediaController,
|
||||
thumbData: VideoThumb?,
|
||||
roundedCorner: Boolean,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
keepPlaying: MutableState<Boolean>,
|
||||
automaticallyStartPlayback: MutableState<Boolean>,
|
||||
@@ -499,10 +517,15 @@ private fun RenderVideoPlayer(
|
||||
|
||||
BoxWithConstraints() {
|
||||
AndroidView(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 100.dp)
|
||||
.align(Alignment.Center),
|
||||
modifier = if (roundedCorner) {
|
||||
MaterialTheme.colors.imageModifier
|
||||
.defaultMinSize(minHeight = 100.dp)
|
||||
.align(Alignment.Center)
|
||||
} else {
|
||||
Modifier.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 100.dp)
|
||||
.align(Alignment.Center)
|
||||
},
|
||||
factory = {
|
||||
PlayerView(context).apply {
|
||||
player = controller
|
||||
@@ -620,7 +643,7 @@ fun Waveform(
|
||||
@Composable
|
||||
fun DrawWaveform(waveform: ImmutableList<Int>, waveformProgress: MutableState<Float>, align: Modifier) {
|
||||
AudioWaveformReadOnly(
|
||||
modifier = align,
|
||||
modifier = align.padding(start = 10.dp, end = 10.dp),
|
||||
amplitudes = waveform,
|
||||
progress = waveformProgress.value,
|
||||
progressBrush = Brush.infiniteLinearGradient(
|
||||
|
@@ -181,6 +181,7 @@ fun figureOutMimeType(fullUrl: String): ZoomableContent {
|
||||
fun ZoomableContentView(
|
||||
content: ZoomableContent,
|
||||
images: ImmutableList<ZoomableContent> = listOf(content).toImmutableList(),
|
||||
roundedCorner: Boolean,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
@@ -190,7 +191,11 @@ fun ZoomableContentView(
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
var mainImageModifier = MaterialTheme.colors.imageModifier
|
||||
var mainImageModifier = if (roundedCorner) {
|
||||
MaterialTheme.colors.imageModifier
|
||||
} else {
|
||||
Modifier.fillMaxWidth()
|
||||
}
|
||||
|
||||
if (content is ZoomableUrlContent) {
|
||||
mainImageModifier = mainImageModifier.combinedClickable(
|
||||
@@ -215,6 +220,7 @@ fun ZoomableContentView(
|
||||
title = content.description,
|
||||
artworkUri = content.artworkUri,
|
||||
authorName = content.authorName,
|
||||
roundedCorner = roundedCorner,
|
||||
nostrUriCallback = content.uri,
|
||||
onDialog = { dialogOpen = true },
|
||||
accountViewModel = accountViewModel
|
||||
@@ -228,6 +234,7 @@ fun ZoomableContentView(
|
||||
title = content.description,
|
||||
artworkUri = content.artworkUri,
|
||||
authorName = content.authorName,
|
||||
roundedCorner = roundedCorner,
|
||||
nostrUriCallback = content.uri,
|
||||
onDialog = { dialogOpen = true },
|
||||
accountViewModel = accountViewModel
|
||||
@@ -624,11 +631,11 @@ fun ZoomableImageDialog(
|
||||
pagerState = pagerState,
|
||||
itemsCount = allImages.size,
|
||||
itemContent = { index ->
|
||||
RenderImageOrVideo(allImages[index], accountViewModel)
|
||||
RenderImageOrVideo(allImages[index], false, accountViewModel)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
RenderImageOrVideo(imageUrl, accountViewModel)
|
||||
RenderImageOrVideo(imageUrl, false, accountViewModel)
|
||||
}
|
||||
|
||||
Row(
|
||||
@@ -656,7 +663,7 @@ fun ZoomableImageDialog(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderImageOrVideo(content: ZoomableContent, accountViewModel: AccountViewModel) {
|
||||
private fun RenderImageOrVideo(content: ZoomableContent, roundedCorner: Boolean, accountViewModel: AccountViewModel) {
|
||||
val mainModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zoomable(rememberZoomState())
|
||||
@@ -670,6 +677,7 @@ private fun RenderImageOrVideo(content: ZoomableContent, accountViewModel: Accou
|
||||
title = content.description,
|
||||
artworkUri = content.artworkUri,
|
||||
authorName = content.authorName,
|
||||
roundedCorner = roundedCorner,
|
||||
accountViewModel = accountViewModel,
|
||||
alwaysShowVideo = true
|
||||
)
|
||||
@@ -684,6 +692,7 @@ private fun RenderImageOrVideo(content: ZoomableContent, accountViewModel: Accou
|
||||
title = content.description,
|
||||
artworkUri = content.artworkUri,
|
||||
authorName = content.authorName,
|
||||
roundedCorner = roundedCorner,
|
||||
accountViewModel = accountViewModel,
|
||||
alwaysShowVideo = true
|
||||
)
|
||||
|
@@ -40,7 +40,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
||||
it.event !is BadgeDefinitionEvent &&
|
||||
it.event !is BadgeProfilesEvent &&
|
||||
it.event !is GiftWrapEvent &&
|
||||
it.author !== loggedInUser &&
|
||||
(it.event is LnZapEvent || it.author !== loggedInUser) &&
|
||||
(isGlobal || it.author?.pubkeyHex in followingKeySet) &&
|
||||
it.event?.isTaggedUser(loggedInUserHex) ?: false &&
|
||||
(isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
|
||||
|
@@ -481,8 +481,8 @@ fun NormalNote(
|
||||
)
|
||||
}
|
||||
is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote)
|
||||
is FileHeaderEvent -> FileHeaderDisplay(baseNote, accountViewModel)
|
||||
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, accountViewModel)
|
||||
is FileHeaderEvent -> FileHeaderDisplay(baseNote, isQuotedNote, accountViewModel)
|
||||
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, isQuotedNote, accountViewModel)
|
||||
else ->
|
||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
|
||||
CheckNewAndRenderNote(
|
||||
@@ -3194,7 +3194,7 @@ private fun RenderBadge(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FileHeaderDisplay(note: Note, accountViewModel: AccountViewModel) {
|
||||
fun FileHeaderDisplay(note: Note, isQuotedNote: Boolean, accountViewModel: AccountViewModel) {
|
||||
val event = (note.event as? FileHeaderEvent) ?: return
|
||||
val fullUrl = event.url() ?: return
|
||||
|
||||
@@ -3230,18 +3230,18 @@ fun FileHeaderDisplay(note: Note, accountViewModel: AccountViewModel) {
|
||||
}
|
||||
|
||||
SensitivityWarning(note = note, accountViewModel = accountViewModel) {
|
||||
ZoomableContentView(content = content, accountViewModel = accountViewModel)
|
||||
ZoomableContentView(content = content, roundedCorner = isQuotedNote, accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FileStorageHeaderDisplay(baseNote: Note, accountViewModel: AccountViewModel) {
|
||||
fun FileStorageHeaderDisplay(baseNote: Note, isQuotedNote: Boolean, accountViewModel: AccountViewModel) {
|
||||
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
||||
val dataEventId = eventHeader.dataEventId() ?: return
|
||||
|
||||
LoadNote(baseNoteHex = dataEventId) { contentNote ->
|
||||
if (contentNote != null) {
|
||||
ObserverAndRenderNIP95(baseNote, contentNote, accountViewModel)
|
||||
ObserverAndRenderNIP95(baseNote, contentNote, isQuotedNote, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3250,6 +3250,7 @@ fun FileStorageHeaderDisplay(baseNote: Note, accountViewModel: AccountViewModel)
|
||||
private fun ObserverAndRenderNIP95(
|
||||
header: Note,
|
||||
content: Note,
|
||||
isQuotedNote: Boolean,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val eventHeader = (header.event as? FileStorageHeaderEvent) ?: return
|
||||
@@ -3296,7 +3297,7 @@ private fun ObserverAndRenderNIP95(
|
||||
Crossfade(targetState = content) {
|
||||
if (it != null) {
|
||||
SensitivityWarning(note = header, accountViewModel = accountViewModel) {
|
||||
ZoomableContentView(content = it, accountViewModel = accountViewModel)
|
||||
ZoomableContentView(content = it, roundedCorner = isQuotedNote, accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3367,6 +3368,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, accountViewModel: A
|
||||
title = noteEvent.subject(),
|
||||
thumbUri = cover,
|
||||
authorName = note.author?.toBestDisplayName(),
|
||||
roundedCorner = true,
|
||||
nostrUriCallback = "nostr:${note.toNEvent()}",
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
@@ -3375,6 +3377,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, accountViewModel: A
|
||||
videoUri = media,
|
||||
title = noteEvent.subject(),
|
||||
authorName = note.author?.toBestDisplayName(),
|
||||
roundedCorner = true,
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
}
|
||||
@@ -3387,9 +3390,23 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, accountViewModel: A
|
||||
fun AudioHeader(noteEvent: AudioHeaderEvent, note: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val media = remember { noteEvent.stream() ?: noteEvent.download() }
|
||||
val waveform = remember { noteEvent.wavefrom()?.toImmutableList()?.ifEmpty { null } }
|
||||
val subject = remember { noteEvent.subject()?.ifBlank { null } }
|
||||
val content = remember { noteEvent.content().ifBlank { null } }
|
||||
|
||||
val defaultBackground = MaterialTheme.colors.background
|
||||
val background = remember { mutableStateOf(defaultBackground) }
|
||||
val tags = remember(noteEvent) { noteEvent?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() }
|
||||
|
||||
val eventContent = remember(note.event) {
|
||||
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
|
||||
val body = accountViewModel.decrypt(note)
|
||||
|
||||
if (!subject.isNullOrBlank() && body?.split("\n")?.get(0)?.contains(subject) == false) {
|
||||
"## $subject\n$body"
|
||||
} else {
|
||||
body
|
||||
}
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.padding(top = 5.dp)) {
|
||||
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
media?.let { media ->
|
||||
@@ -3401,42 +3418,30 @@ fun AudioHeader(noteEvent: AudioHeaderEvent, note: Note, accountViewModel: Accou
|
||||
waveform = waveform,
|
||||
title = noteEvent.subject(),
|
||||
authorName = note.author?.toBestDisplayName(),
|
||||
roundedCorner = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nostrUriCallback = note.toNostrUri()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row() {
|
||||
subject?.let {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)) {
|
||||
Text(
|
||||
text = it,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
content?.let {
|
||||
Row(verticalAlignment = CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 5.dp)) {
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = true,
|
||||
tags = tags,
|
||||
backgroundColor = background,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row() {
|
||||
content?.let {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)) {
|
||||
Text(
|
||||
text = it,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
val hashtags = remember(noteEvent) { noteEvent.hashtags().toImmutableList() }
|
||||
DisplayUncitedHashtags(hashtags, content ?: "", nav)
|
||||
}
|
||||
|
||||
val hashtags = remember(noteEvent) { noteEvent.hashtags().toImmutableList() }
|
||||
DisplayUncitedHashtags(hashtags, content ?: "", nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3559,6 +3564,7 @@ fun RenderLiveActivityEventInner(baseNote: Note, accountViewModel: AccountViewMo
|
||||
title = subject,
|
||||
artworkUri = cover,
|
||||
authorName = baseNote.author?.toBestDisplayName(),
|
||||
roundedCorner = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nostrUriCallback = "nostr:${baseNote.toNEvent()}"
|
||||
)
|
||||
|
@@ -377,9 +377,9 @@ fun NoteMaster(
|
||||
nav = nav
|
||||
)
|
||||
} else if (noteEvent is FileHeaderEvent) {
|
||||
FileHeaderDisplay(baseNote, accountViewModel)
|
||||
FileHeaderDisplay(baseNote, true, accountViewModel)
|
||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||
FileStorageHeaderDisplay(baseNote, accountViewModel)
|
||||
FileStorageHeaderDisplay(baseNote, true, accountViewModel)
|
||||
} else if (noteEvent is PeopleListEvent) {
|
||||
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
||||
} else if (noteEvent is AudioTrackEvent) {
|
||||
|
@@ -640,6 +640,7 @@ fun ShowVideoStreaming(
|
||||
|
||||
ZoomableContentView(
|
||||
content = zoomableUrlVideo,
|
||||
roundedCorner = false,
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
}
|
||||
|
@@ -302,9 +302,9 @@ private fun RenderVideoOrPictureNote(
|
||||
Row(remember { Modifier.weight(1f) }, verticalAlignment = Alignment.CenterVertically) {
|
||||
val noteEvent = remember { note.event }
|
||||
if (noteEvent is FileHeaderEvent) {
|
||||
FileHeaderDisplay(note, accountViewModel)
|
||||
FileHeaderDisplay(note, false, accountViewModel)
|
||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||
FileStorageHeaderDisplay(note, accountViewModel)
|
||||
FileStorageHeaderDisplay(note, false, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user