Implements contextual rounded corners for images and videos

This commit is contained in:
Vitor Pamplona
2023-08-19 11:08:55 -04:00
parent d8ce3b9d37
commit 63f3a6cdf9
10 changed files with 92 additions and 52 deletions

View File

@@ -241,6 +241,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, accountViewModel: AccountViewMo
postViewModel.galleryUri?.let {
VideoView(
videoUri = it.toString(),
roundedCorner = false,
accountViewModel = accountViewModel
)
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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(

View File

@@ -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
)

View File

@@ -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)) &&

View File

@@ -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()}"
)

View File

@@ -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) {

View File

@@ -640,6 +640,7 @@ fun ShowVideoStreaming(
ZoomableContentView(
content = zoomableUrlVideo,
roundedCorner = false,
accountViewModel = accountViewModel
)
}

View File

@@ -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)
}
}
}