From b24999d78ca7b45bc97209108dbd219bc9d6bbaf Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 31 Jul 2024 14:43:10 -0400 Subject: [PATCH] Basic refactoring to minimize the need for user-space params in the VideoView --- .../amethyst/ui/components/VideoView.kt | 101 ++++++++++-------- .../ui/components/ZoomableContentDialog.kt | 29 ++++- .../ui/components/ZoomableContentView.kt | 81 +++++++------- 3 files changed, 116 insertions(+), 95 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 264bbd6bd..faf018590 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -30,6 +30,7 @@ import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.VideoView import androidx.annotation.OptIn import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.LinearEasing @@ -40,9 +41,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape @@ -217,6 +218,37 @@ fun VideoView( onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, accountViewModel: AccountViewModel, alwaysShowVideo: Boolean = false, +) { + val borderModifier = + if (roundedCorner) { + MaterialTheme.colorScheme.imageModifier + } else if (gallery) { + MaterialTheme.colorScheme.videoGalleryModifier + } else { + Modifier + } + + VideoView(videoUri, mimeType, title, thumb, borderModifier, isFiniteHeight, waveform, artworkUri, authorName, dimensions, blurhash, nostrUriCallback, onDialog, onControllerVisibilityChanged, accountViewModel, alwaysShowVideo) +} + +@Composable +fun VideoView( + videoUri: String, + mimeType: String?, + title: String? = null, + thumb: VideoThumb? = null, + borderModifier: Modifier, + isFiniteHeight: Boolean, + waveform: ImmutableList? = null, + artworkUri: String? = null, + authorName: String? = null, + dimensions: String? = null, + blurhash: String? = null, + nostrUriCallback: String? = null, + onDialog: ((Boolean) -> Unit)? = null, + onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, + accountViewModel: AccountViewModel, + alwaysShowVideo: Boolean = false, ) { val defaultToStart by remember(videoUri) { mutableStateOf(DEFAULT_MUTED_SETTING.value) } @@ -248,8 +280,7 @@ fun VideoView( defaultToStart = defaultToStart, title = title, thumb = thumb, - roundedCorner = roundedCorner, - gallery = gallery, + borderModifier = borderModifier, isFiniteHeight = isFiniteHeight, waveform = waveform, artworkUri = artworkUri, @@ -273,19 +304,12 @@ fun VideoView( } Box(modifier, contentAlignment = Alignment.Center) { - val image = - if (roundedCorner) { - MaterialTheme.colorScheme.imageModifier - } else { - Modifier.fillMaxWidth() - } - // Always displays Blurharh to avoid size flickering DisplayBlurHash( blurhash, null, if (isFiniteHeight) ContentScale.FillWidth else ContentScale.FillWidth, - if (ratio != null) image.aspectRatio(ratio) else modifier, + if (ratio != null) borderModifier.aspectRatio(ratio) else borderModifier, ) if (!automaticallyStartPlayback.value) { @@ -302,7 +326,7 @@ fun VideoView( defaultToStart = defaultToStart, title = title, thumb = thumb, - roundedCorner = roundedCorner, + borderModifier = borderModifier, isFiniteHeight = isFiniteHeight, waveform = waveform, artworkUri = artworkUri, @@ -326,9 +350,9 @@ fun VideoViewInner( defaultToStart: Boolean = false, title: String? = null, thumb: VideoThumb? = null, - roundedCorner: Boolean, - gallery: Boolean = false, + showControls: Boolean = false, isFiniteHeight: Boolean, + borderModifier: Modifier, waveform: ImmutableList? = null, artworkUri: String? = null, authorName: String? = null, @@ -338,7 +362,7 @@ fun VideoViewInner( onDialog: ((Boolean) -> Unit)? = null, accountViewModel: AccountViewModel, ) { - VideoPlayerActiveMutex(videoUri) { modifier, activeOnScreen -> + VideoPlayerActiveMutex(videoUri) { videoModifier, activeOnScreen -> GetMediaItem(videoUri, title, artworkUri, authorName) { mediaItem -> GetVideoController( mediaItem = mediaItem, @@ -351,15 +375,15 @@ fun VideoViewInner( mimeType = mimeType, controller = controller, thumbData = thumb, - roundedCorner = roundedCorner, - gallery = gallery, + hideControls = showControls, isFiniteHeight = isFiniteHeight, nostrUriCallback = nostrUriCallback, waveform = waveform, keepPlaying = keepPlaying, automaticallyStartPlayback = automaticallyStartPlayback, activeOnScreen = activeOnScreen, - modifier = modifier, + borderModifier = borderModifier, + videoModifier = videoModifier, onControllerVisibilityChanged = onControllerVisibilityChanged, onDialog = onDialog, accountViewModel = accountViewModel, @@ -655,9 +679,9 @@ fun VideoPlayerActiveMutex( onDispose { trackingVideos.remove(myCache) } } - val myModifier = + val videoModifier = remember(videoUri) { - Modifier.fillMaxWidth().defaultMinSize(minHeight = 70.dp).onVisiblePositionChanges { distanceToCenter -> + Modifier.fillMaxWidth().heightIn(min = 100.dp).onVisiblePositionChanges { distanceToCenter -> myCache.distanceToCenter = distanceToCenter if (distanceToCenter != null) { @@ -684,7 +708,7 @@ fun VideoPlayerActiveMutex( } } - inner(myModifier, active) + inner(videoModifier, active) } @Stable @@ -699,15 +723,15 @@ private fun RenderVideoPlayer( mimeType: String?, controller: MediaController, thumbData: VideoThumb?, - roundedCorner: Boolean, - gallery: Boolean = false, + hideControls: Boolean = false, isFiniteHeight: Boolean, nostrUriCallback: String?, waveform: ImmutableList? = null, keepPlaying: MutableState, automaticallyStartPlayback: State, activeOnScreen: MutableState, - modifier: Modifier, + borderModifier: Modifier, + videoModifier: Modifier, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, onDialog: ((Boolean) -> Unit)?, accountViewModel: AccountViewModel, @@ -716,27 +740,9 @@ private fun RenderVideoPlayer( val controllerVisible = remember(controller) { mutableStateOf(false) } - Box { - val borders = MaterialTheme.colorScheme.imageModifier - val bordersSquare = MaterialTheme.colorScheme.videoGalleryModifier - val myModifier = - remember(controller) { - if (roundedCorner) { - modifier.then( - borders.defaultMinSize(minHeight = 75.dp).align(Alignment.Center), - ) - } else if (gallery) { - Modifier - modifier.then( - bordersSquare.defaultMinSize(minHeight = 75.dp).align(Alignment.Center), - ) - } else { - modifier.fillMaxWidth().defaultMinSize(minHeight = 75.dp).align(Alignment.Center) - } - } - + Box(modifier = borderModifier) { AndroidView( - modifier = myModifier, + modifier = videoModifier, factory = { context: Context -> PlayerView(context).apply { player = controller @@ -748,7 +754,7 @@ private fun RenderVideoPlayer( setBackgroundColor(Color.Transparent.toArgb()) setShutterBackgroundColor(Color.Transparent.toArgb()) controllerAutoShow = false - useController = !gallery + useController = !hideControls thumbData?.thumb?.let { defaultArtwork = it } hideController() resizeMode = @@ -757,7 +763,7 @@ private fun RenderVideoPlayer( } else { AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH } - if (!gallery) { + if (!hideControls) { onDialog?.let { innerOnDialog -> setFullscreenButtonClickListener { controller.pause() @@ -776,7 +782,8 @@ private fun RenderVideoPlayer( ) waveform?.let { Waveform(it, controller, remember { Modifier.align(Alignment.Center) }) } - if (!gallery) { + + if (!hideControls) { val startingMuteState = remember(controller) { controller.volume < 0.001 } MuteButton( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt index 445045dd1..c1efd263d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt @@ -89,6 +89,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size15dp import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size5dp +import com.vitorpamplona.amethyst.ui.theme.imageModifier import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers @@ -388,6 +389,12 @@ private fun RenderImageOrVideo( accountViewModel: AccountViewModel, ) { val automaticallyStartPlayback = remember { mutableStateOf(true) } + val contentScale = + if (isFiniteHeight) { + ContentScale.Fit + } else { + ContentScale.FillWidth + } Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { if (content is MediaUrlImage) { @@ -405,21 +412,28 @@ private fun RenderImageOrVideo( UrlImageView( content = content, + contentScale = contentScale, mainImageModifier = mainModifier, loadedImageModifier = Modifier.fillMaxWidth(), - isFiniteHeight = isFiniteHeight, controllerVisible = controllerVisible, accountViewModel = accountViewModel, alwayShowImage = true, ) } else if (content is MediaUrlVideo) { + val borderModifier = + if (roundedCorner) { + MaterialTheme.colorScheme.imageModifier + } else { + Modifier.fillMaxWidth() + } + VideoViewInner( videoUri = content.url, mimeType = content.mimeType, title = content.description, artworkUri = content.artworkUri, authorName = content.authorName, - roundedCorner = roundedCorner, + borderModifier = borderModifier, isFiniteHeight = isFiniteHeight, automaticallyStartPlayback = automaticallyStartPlayback, onControllerVisibilityChanged = onControllerVisibilityChanged, @@ -440,14 +454,21 @@ private fun RenderImageOrVideo( LocalImageView( content = content, + contentScale = contentScale, mainImageModifier = mainModifier, loadedImageModifier = Modifier.fillMaxWidth(), - isFiniteHeight = isFiniteHeight, controllerVisible = controllerVisible, accountViewModel = accountViewModel, alwayShowImage = true, ) } else if (content is MediaLocalVideo) { + val borderModifier = + if (roundedCorner) { + MaterialTheme.colorScheme.imageModifier + } else { + Modifier.fillMaxWidth() + } + content.localFile?.let { VideoViewInner( videoUri = it.toUri().toString(), @@ -455,7 +476,7 @@ private fun RenderImageOrVideo( title = content.description, artworkUri = content.artworkUri, authorName = content.authorName, - roundedCorner = roundedCorner, + borderModifier = borderModifier, isFiniteHeight = isFiniteHeight, automaticallyStartPlayback = automaticallyStartPlayback, onControllerVisibilityChanged = onControllerVisibilityChanged, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 5917bcf97..eefbde261 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -79,6 +79,7 @@ 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.service.BlurHashRequester +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.InformationDialog import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation import com.vitorpamplona.amethyst.ui.note.BlankNote @@ -94,6 +95,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size30dp import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.hashVerifierMark import com.vitorpamplona.amethyst.ui.theme.imageModifier +import com.vitorpamplona.amethyst.ui.theme.videoGalleryModifier import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.encoders.Nip19Bech32 import com.vitorpamplona.quartz.encoders.toHexKey @@ -116,6 +118,13 @@ fun ZoomableContentView( ) { var dialogOpen by remember(content) { mutableStateOf(false) } + val contentScale = + if (isFiniteHeight) { + ContentScale.Fit + } else { + ContentScale.FillWidth + } + when (content) { is MediaUrlImage -> SensitivityWarning(content.contentWarning != null, accountViewModel) { @@ -123,7 +132,7 @@ fun ZoomableContentView( val mainImageModifier = Modifier.fillMaxWidth().clickable { dialogOpen = true } val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() - UrlImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel) + UrlImageView(content, contentScale, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel) } } is MediaUrlVideo -> @@ -150,7 +159,7 @@ fun ZoomableContentView( val mainImageModifier = Modifier.fillMaxWidth().clickable { dialogOpen = true } val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() - LocalImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel) + LocalImageView(content, contentScale, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel) } is MediaLocalVideo -> content.localFile?.let { @@ -190,7 +199,7 @@ fun GalleryContentView( val mainImageModifier = Modifier.fillMaxWidth() val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() - UrlImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel, gallery = true) + UrlImageView(content, ContentScale.Crop, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel) } } is MediaUrlVideo -> @@ -201,11 +210,10 @@ fun GalleryContentView( mimeType = content.mimeType, title = content.description, artworkUri = content.artworkUri, - gallery = true, + borderModifier = MaterialTheme.colorScheme.videoGalleryModifier, authorName = content.authorName, dimensions = content.dim, blurhash = content.blurhash, - roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = content.uri, accountViewModel = accountViewModel, @@ -217,7 +225,7 @@ fun GalleryContentView( val mainImageModifier = Modifier.fillMaxWidth() val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() - LocalImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel) + LocalImageView(content, ContentScale.Crop, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel) } is MediaLocalVideo -> content.localFile?.let { @@ -228,8 +236,7 @@ fun GalleryContentView( title = content.description, artworkUri = content.artworkUri, authorName = content.authorName, - gallery = true, - roundedCorner = roundedCorner, + borderModifier = MaterialTheme.colorScheme.videoGalleryModifier, isFiniteHeight = isFiniteHeight, nostrUriCallback = content.uri, accountViewModel = accountViewModel, @@ -257,30 +264,24 @@ fun TwoSecondController( @Composable fun LocalImageView( content: MediaLocalImage, + contentScale: ContentScale, mainImageModifier: Modifier, loadedImageModifier: Modifier, - isFiniteHeight: Boolean, controllerVisible: MutableState, accountViewModel: AccountViewModel, alwayShowImage: Boolean = false, ) { if (content.localFileExists()) { - Box(contentAlignment = Alignment.Center) { - val showImage = - remember { - mutableStateOf( - if (alwayShowImage) true else accountViewModel.settings.showImages.value, - ) - } + val showImage = + remember { + mutableStateOf( + if (alwayShowImage) true else accountViewModel.settings.showImages.value, + ) + } - val contentScale = - remember { - if (isFiniteHeight) ContentScale.Fit else ContentScale.FillWidth - } - - val ratio = remember(content) { aspectRatio(content.dim) } - - if (showImage.value) { + val ratio = remember(content) { aspectRatio(content.dim) } + CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) { + if (it) { SubcomposeAsyncImage( model = content.localFile, contentDescription = content.description, @@ -361,36 +362,28 @@ fun LocalImageView( @Composable fun UrlImageView( content: MediaUrlImage, + contentScale: ContentScale, mainImageModifier: Modifier, loadedImageModifier: Modifier, - isFiniteHeight: Boolean, controllerVisible: MutableState, accountViewModel: AccountViewModel, - gallery: Boolean = false, alwayShowImage: Boolean = false, ) { - Box(contentAlignment = Alignment.Center) { - val showImage = - remember { - mutableStateOf( - if (alwayShowImage) true else accountViewModel.settings.showImages.value, - ) - } + val ratio = remember(content) { aspectRatio(content.dim) } - val ratio = remember(content) { aspectRatio(content.dim) } + val showImage = + remember { + mutableStateOf( + if (alwayShowImage) true else accountViewModel.settings.showImages.value, + ) + } - if (showImage.value) { + CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) { + if (it) { SubcomposeAsyncImage( model = content.url, contentDescription = content.description, - contentScale = - if (gallery) { - ContentScale.Crop - } else if (isFiniteHeight) { - ContentScale.Fit - } else { - ContentScale.FillWidth - }, + contentScale = contentScale, modifier = mainImageModifier, ) { when (painter.state) { @@ -493,7 +486,7 @@ fun ImageUrlWithDownloadButton( val inlineContent = mapOf("inlineContent" to InlineDownloadIcon(showImage)) - val pressIndicator = remember { Modifier.clickable { runCatching { uri.openUri(url) } } } + val pressIndicator = remember { Modifier.fillMaxWidth().clickable { runCatching { uri.openUri(url) } } } Text( text = annotatedTermsString,