Basic refactoring to minimize the need for user-space params in the VideoView

This commit is contained in:
Vitor Pamplona 2024-07-31 14:43:10 -04:00
parent d1278a4477
commit b24999d78c
3 changed files with 116 additions and 95 deletions

View File

@ -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<Int>? = 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<Int>? = 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<Int>? = null,
keepPlaying: MutableState<Boolean>,
automaticallyStartPlayback: State<Boolean>,
activeOnScreen: MutableState<Boolean>,
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(

View File

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

View File

@ -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<Boolean>,
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<Boolean>,
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,