- Migrates Gallery to not play videos and simply display a tumbnail when it is a video
- Fixes incorrect aspect ratios of the AutoNonLazyGrid when multiple images exist for a single entry - Adds Play buttons when the image fails to load so that people click and the post appears.
This commit is contained in:
+7
-9
@@ -24,10 +24,12 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
@@ -45,7 +47,6 @@ fun GalleryCardCompose(
|
||||
modifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel, nav, shortPreview = true) {
|
||||
CheckHiddenFeedWatchBlockAndReport(
|
||||
@@ -66,15 +67,15 @@ fun GalleryCardCompose(
|
||||
}
|
||||
|
||||
if (redirectToEventId != null) {
|
||||
LoadNote(baseNoteHex = redirectToEventId, accountViewModel = accountViewModel) { sourceNote ->
|
||||
if (sourceNote != null) {
|
||||
LoadNote(baseNoteHex = redirectToEventId, accountViewModel = accountViewModel) { baseSourceNote ->
|
||||
if (baseSourceNote != null) {
|
||||
val sourceNote by observeNote(baseSourceNote, accountViewModel)
|
||||
RedirectableGalleryCard(
|
||||
galleryNote = baseNote,
|
||||
sourceNote = sourceNote,
|
||||
sourceNote = sourceNote.note,
|
||||
modifier = modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
ratio = ratio,
|
||||
)
|
||||
} else {
|
||||
RedirectableGalleryCard(
|
||||
@@ -83,7 +84,6 @@ fun GalleryCardCompose(
|
||||
modifier = modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
ratio = ratio,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,6 @@ fun GalleryCardCompose(
|
||||
modifier = modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
ratio = ratio,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +107,6 @@ fun RedirectableGalleryCard(
|
||||
modifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
QuickActionGallery(baseNote = galleryNote, accountViewModel = accountViewModel) { showPopup ->
|
||||
ClickableNote(
|
||||
@@ -127,7 +125,7 @@ fun RedirectableGalleryCard(
|
||||
note = galleryNote,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
GalleryThumbnail(galleryNote, accountViewModel, nav, ratio = ratio)
|
||||
GalleryThumbnail(galleryNote, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
-114
@@ -20,63 +20,58 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayCircleOutline
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.PlayerView
|
||||
import coil3.compose.AsyncImagePainter
|
||||
import coil3.compose.SubcomposeAsyncImage
|
||||
import coil3.compose.SubcomposeAsyncImageContent
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser.Companion.isVideoUrl
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayBlurHash
|
||||
import com.vitorpamplona.amethyst.ui.components.ImageUrlWithDownloadButton
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.WatchAuthor
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Composable
|
||||
fun GalleryThumbnail(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
val noteState by observeNote(baseNote, accountViewModel)
|
||||
val noteEvent = noteState.note.event ?: return
|
||||
@@ -137,7 +132,7 @@ fun GalleryThumbnail(
|
||||
emptyList()
|
||||
}
|
||||
|
||||
InnerRenderGalleryThumb(content, baseNote, accountViewModel, ratio)
|
||||
InnerRenderGalleryThumb(content, baseNote, accountViewModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -145,10 +140,9 @@ fun InnerRenderGalleryThumb(
|
||||
content: List<MediaUrlContent>,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
if (content.isNotEmpty()) {
|
||||
GalleryContentView(content, accountViewModel, ratio = ratio)
|
||||
GalleryContentView(content, accountViewModel)
|
||||
} else {
|
||||
DisplayGalleryAuthorBanner(note, accountViewModel)
|
||||
}
|
||||
@@ -160,28 +154,30 @@ fun DisplayGalleryAuthorBanner(
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
WatchAuthor(note, accountViewModel) { author ->
|
||||
BannerImage(author, Modifier.fillMaxSize().clip(QuoteBorder), accountViewModel)
|
||||
BannerImage(
|
||||
author,
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.clip(QuoteBorder),
|
||||
accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.annotation.OptIn(UnstableApi::class)
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
fun GalleryContentView(
|
||||
contentList: List<MediaUrlContent>,
|
||||
accountViewModel: AccountViewModel,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
AutoNonlazyGrid(contentList.size) { contentIndex ->
|
||||
AutoNonlazyGrid(contentList.size, modifier = Modifier.fillMaxSize()) { contentIndex ->
|
||||
when (val content = contentList[contentIndex]) {
|
||||
is MediaUrlImage -> {
|
||||
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
||||
UrlImageView(content, accountViewModel, ratio = ratio)
|
||||
}
|
||||
}
|
||||
|
||||
is MediaUrlVideo -> {
|
||||
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
||||
UrlVideoView(content, accountViewModel, ratio = ratio)
|
||||
is MediaUrlContent -> {
|
||||
val hasSensitiveContent =
|
||||
(content is MediaUrlVideo && content.contentWarning != null) ||
|
||||
(content is MediaUrlImage && content.contentWarning != null)
|
||||
SensitivityWarning(hasSensitiveContent, accountViewModel) {
|
||||
UrlImageView(content, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,17 +186,15 @@ fun GalleryContentView(
|
||||
|
||||
@Composable
|
||||
fun UrlImageView(
|
||||
content: MediaUrlImage,
|
||||
content: MediaUrlContent,
|
||||
accountViewModel: AccountViewModel,
|
||||
alwayShowImage: Boolean = false,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
val defaultModifier = Modifier.fillMaxSize().aspectRatio(ratio)
|
||||
val defaultModifier = Modifier.fillMaxSize()
|
||||
|
||||
val showImage =
|
||||
remember {
|
||||
mutableStateOf(
|
||||
if (alwayShowImage) true else accountViewModel.settings.showImages(),
|
||||
accountViewModel.settings.showImages(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -231,7 +225,14 @@ fun UrlImageView(
|
||||
}
|
||||
|
||||
is AsyncImagePainter.State.Error -> {
|
||||
ClickableUrl(urlText = "${content.url} ", url = content.url)
|
||||
Box(defaultModifier, contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayCircleOutline,
|
||||
contentDescription = stringRes(id = R.string.play),
|
||||
modifier = Size50Modifier,
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is AsyncImagePainter.State.Success -> {
|
||||
@@ -249,86 +250,19 @@ fun UrlImageView(
|
||||
ContentScale.Crop,
|
||||
defaultModifier.clickable { showImage.value = true },
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.size(Size75dp),
|
||||
onClick = { showImage.value = true },
|
||||
) {
|
||||
DownloadForOfflineIcon(Size75dp, Color.White)
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayCircleOutline,
|
||||
contentDescription = stringRes(id = R.string.play),
|
||||
modifier = Size50Modifier,
|
||||
tint = Color.White,
|
||||
)
|
||||
} else {
|
||||
ImageUrlWithDownloadButton(content.url, showImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
fun UrlVideoView(
|
||||
content: MediaUrlVideo,
|
||||
accountViewModel: AccountViewModel,
|
||||
ratio: Float = 1.0f,
|
||||
) {
|
||||
val defaultModifier = Modifier.fillMaxSize().aspectRatio(ratio)
|
||||
|
||||
val automaticallyStartPlayback =
|
||||
remember(content) {
|
||||
mutableStateOf<Boolean>(accountViewModel.settings.startVideoPlayback())
|
||||
}
|
||||
|
||||
Box(defaultModifier, contentAlignment = Alignment.Center) {
|
||||
if (content.blurhash != null) {
|
||||
// Always displays Blurharh to avoid size flickering
|
||||
DisplayBlurHash(
|
||||
content.blurhash,
|
||||
null,
|
||||
ContentScale.Crop,
|
||||
defaultModifier,
|
||||
)
|
||||
}
|
||||
|
||||
if (!automaticallyStartPlayback.value) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(Size75dp),
|
||||
onClick = { automaticallyStartPlayback.value = true },
|
||||
) {
|
||||
DownloadForOfflineIcon(Size75dp, Color.White)
|
||||
}
|
||||
} else {
|
||||
GetMediaItem(
|
||||
videoUri = content.url,
|
||||
title = content.description,
|
||||
artworkUri = content.artworkUri,
|
||||
authorName = content.authorName,
|
||||
callbackUri = content.uri,
|
||||
mimeType = content.mimeType,
|
||||
aspectRatio = ratio,
|
||||
proxyPort = accountViewModel.httpClientBuilder.proxyPortForVideo(content.url),
|
||||
) { mediaItem ->
|
||||
GetVideoController(
|
||||
mediaItem = mediaItem,
|
||||
muted = true,
|
||||
) { controller ->
|
||||
AndroidView(
|
||||
modifier = Modifier,
|
||||
factory = { context: Context ->
|
||||
PlayerView(context).apply {
|
||||
clipToOutline = true
|
||||
player = controller.controller
|
||||
setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
|
||||
|
||||
controllerAutoShow = false
|
||||
useController = false
|
||||
|
||||
hideController()
|
||||
|
||||
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
|
||||
|
||||
controller.controller?.playWhenReady = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayCircleOutline,
|
||||
contentDescription = stringRes(id = R.string.play),
|
||||
modifier = Size50Modifier,
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -115,7 +115,6 @@ private fun GalleryFeedLoaded(
|
||||
.animateItem(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
ratio = ratio,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplaye
|
||||
androidx-media3-exoplayer-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "media3" }
|
||||
androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
|
||||
androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }
|
||||
androidx-media3-ui-compose-material3 = { group = "androidx.media3", name = "media3-ui-compose-material3", version.ref = "media3" }
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||
androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
|
||||
androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "securityCryptoKtx" }
|
||||
@@ -114,6 +115,7 @@ coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref
|
||||
coil-gif = { group = "io.coil-kt.coil3", name = "coil-gif", version.ref = "coil" }
|
||||
coil-svg = { group = "io.coil-kt.coil3", name = "coil-svg", version.ref = "coil" }
|
||||
coil-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
|
||||
coil-video = { group = "io.coil-kt.coil3", name = "coil-video", version.ref = "coil" }
|
||||
drfonfon-geohash = { group = "com.github.drfonfon", name = "android-kotlin-geohash", version.ref = "androidKotlinGeohash" }
|
||||
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
|
||||
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }
|
||||
|
||||
Reference in New Issue
Block a user