Improves the rendering of inline metadata
This commit is contained in:
@@ -104,8 +104,8 @@ class RichTextParser() {
|
|||||||
url = fullUrl,
|
url = fullUrl,
|
||||||
description = frags["alt"]?.let { URLDecoder.decode(it, "UTF-8") },
|
description = frags["alt"]?.let { URLDecoder.decode(it, "UTF-8") },
|
||||||
hash = frags["x"]?.let { URLDecoder.decode(it, "UTF-8") },
|
hash = frags["x"]?.let { URLDecoder.decode(it, "UTF-8") },
|
||||||
dim = frags["blurhash"]?.let { URLDecoder.decode(it, "UTF-8") },
|
blurhash = frags["blurhash"]?.let { URLDecoder.decode(it, "UTF-8") },
|
||||||
uri = frags["dim"]?.let { URLDecoder.decode(it, "UTF-8") }
|
dim = frags["dim"]?.let { URLDecoder.decode(it, "UTF-8") }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ package com.vitorpamplona.amethyst.service
|
|||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.ImageDecoder
|
||||||
|
import android.media.MediaDataSource
|
||||||
|
import android.media.MediaMetadataRetriever
|
||||||
|
import android.media.MediaMetadataRetriever.BitmapParams
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
|
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
|
||||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
import io.trbl.blurhash.BlurHash
|
import io.trbl.blurhash.BlurHash
|
||||||
|
import java.io.IOException
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class FileHeader(
|
class FileHeader(
|
||||||
@@ -76,8 +82,42 @@ class FileHeader(
|
|||||||
} else {
|
} else {
|
||||||
Pair(BlurHash.encode(intArray, mBitmap.width, mBitmap.height, 4, 4), dim)
|
Pair(BlurHash.encode(intArray, mBitmap.width, mBitmap.height, 4, 4), dim)
|
||||||
}
|
}
|
||||||
|
} else if (mimeType?.startsWith("video/") == true) {
|
||||||
|
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||||
|
mediaMetadataRetriever.setDataSource(ByteArrayMediaDataSource(data))
|
||||||
|
|
||||||
|
val newDim = mediaMetadataRetriever.prepareDimFromVideo() ?: dimPrecomputed
|
||||||
|
|
||||||
|
val blurhash = mediaMetadataRetriever.getThumbnail()?.let { thumbnail ->
|
||||||
|
val aspectRatio = (thumbnail.width).toFloat() / (thumbnail.height).toFloat()
|
||||||
|
|
||||||
|
val intArray = IntArray(thumbnail.width * thumbnail.height)
|
||||||
|
thumbnail.getPixels(
|
||||||
|
intArray,
|
||||||
|
0,
|
||||||
|
thumbnail.width,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
thumbnail.width,
|
||||||
|
thumbnail.height
|
||||||
|
)
|
||||||
|
|
||||||
|
if (aspectRatio > 1) {
|
||||||
|
BlurHash.encode(intArray, thumbnail.width, thumbnail.height, 9, (9 * (1 / aspectRatio)).roundToInt())
|
||||||
|
} else if (aspectRatio < 1) {
|
||||||
|
BlurHash.encode(intArray, thumbnail.width, thumbnail.height, (9 * aspectRatio).roundToInt(), 9)
|
||||||
|
} else {
|
||||||
|
BlurHash.encode(intArray, thumbnail.width, thumbnail.height, 4, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newDim != "0x0") {
|
||||||
|
Pair(blurhash, newDim)
|
||||||
|
} else {
|
||||||
|
Pair(blurhash, null)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Pair(null, dimPrecomputed)
|
Pair(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
onReady(FileHeader(mimeType, hash, size, dim, blurHash))
|
onReady(FileHeader(mimeType, hash, size, dim, blurHash))
|
||||||
@@ -88,3 +128,79 @@ class FileHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MediaMetadataRetriever.getThumbnail(): Bitmap? {
|
||||||
|
val raw: ByteArray? = getEmbeddedPicture()
|
||||||
|
if (raw != null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
val params = BitmapParams()
|
||||||
|
params.preferredConfig = Bitmap.Config.ARGB_8888
|
||||||
|
|
||||||
|
// Fall back to middle of video
|
||||||
|
// Note: METADATA_KEY_DURATION unit is in ms, not us.
|
||||||
|
val thumbnailTimeUs: Long = (extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0) * 1000 / 2
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
getFrameAtTime(thumbnailTimeUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, params)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MediaMetadataRetriever.prepareDimFromVideo(): String? {
|
||||||
|
val width = prepareVideoWidth() ?: return null
|
||||||
|
val height = prepareVideoHeight() ?: return null
|
||||||
|
|
||||||
|
return "${width}x$height"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MediaMetadataRetriever.prepareVideoWidth(): Int? {
|
||||||
|
val widthData = extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
|
||||||
|
return if (widthData.isNullOrEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
widthData.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MediaMetadataRetriever.prepareVideoHeight(): Int? {
|
||||||
|
val heightData = extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
|
||||||
|
return if (heightData.isNullOrEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
heightData.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ByteArrayMediaDataSource(var imageData: ByteArray) : MediaDataSource() {
|
||||||
|
override fun getSize(): Long {
|
||||||
|
return imageData.size.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
|
||||||
|
if (position >= imageData.size) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
val newSize = if (position + size > imageData.size) {
|
||||||
|
size - ((position.toInt() + size) - imageData.size)
|
||||||
|
} else {
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
imageData.copyInto(buffer, offset, position.toInt(), position.toInt() + newSize)
|
||||||
|
|
||||||
|
return newSize
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun close() {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -44,6 +45,8 @@ import androidx.compose.ui.composed
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.LayoutCoordinates
|
import androidx.compose.ui.layout.LayoutCoordinates
|
||||||
import androidx.compose.ui.layout.boundsInWindow
|
import androidx.compose.ui.layout.boundsInWindow
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
@@ -160,6 +163,8 @@ fun VideoView(
|
|||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
artworkUri: String? = null,
|
artworkUri: String? = null,
|
||||||
authorName: String? = null,
|
authorName: String? = null,
|
||||||
|
dimensions: String? = null,
|
||||||
|
blurhash: String? = null,
|
||||||
nostrUriCallback: String? = null,
|
nostrUriCallback: String? = null,
|
||||||
onDialog: ((Boolean) -> Unit)? = null,
|
onDialog: ((Boolean) -> Unit)? = null,
|
||||||
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
||||||
@@ -168,22 +173,42 @@ fun VideoView(
|
|||||||
) {
|
) {
|
||||||
val defaultToStart by remember(videoUri) { mutableStateOf(DefaultMutedSetting.value) }
|
val defaultToStart by remember(videoUri) { mutableStateOf(DefaultMutedSetting.value) }
|
||||||
|
|
||||||
VideoViewInner(
|
val automaticallyStartPlayback = remember {
|
||||||
videoUri = videoUri,
|
mutableStateOf<Boolean>(
|
||||||
defaultToStart = defaultToStart,
|
if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback.value
|
||||||
title = title,
|
)
|
||||||
thumb = thumb,
|
}
|
||||||
roundedCorner = roundedCorner,
|
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
if (!automaticallyStartPlayback.value) {
|
||||||
waveform = waveform,
|
ImageUrlWithDownloadButton(url = videoUri, showImage = automaticallyStartPlayback)
|
||||||
artworkUri = artworkUri,
|
} else {
|
||||||
authorName = authorName,
|
val ratio = aspectRatio(dimensions)
|
||||||
nostrUriCallback = nostrUriCallback,
|
val modifier = if (ratio != null && roundedCorner) {
|
||||||
alwaysShowVideo = alwaysShowVideo,
|
Modifier.aspectRatio(ratio)
|
||||||
accountViewModel = accountViewModel,
|
} else {
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
Modifier
|
||||||
onDialog = onDialog
|
}
|
||||||
)
|
|
||||||
|
Box(modifier) {
|
||||||
|
VideoViewInner(
|
||||||
|
videoUri = videoUri,
|
||||||
|
defaultToStart = defaultToStart,
|
||||||
|
title = title,
|
||||||
|
thumb = thumb,
|
||||||
|
roundedCorner = roundedCorner,
|
||||||
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
|
waveform = waveform,
|
||||||
|
artworkUri = artworkUri,
|
||||||
|
authorName = authorName,
|
||||||
|
dimensions = dimensions,
|
||||||
|
blurhash = blurhash,
|
||||||
|
nostrUriCallback = nostrUriCallback,
|
||||||
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
|
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
||||||
|
onDialog = onDialog
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -198,68 +223,61 @@ fun VideoViewInner(
|
|||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
artworkUri: String? = null,
|
artworkUri: String? = null,
|
||||||
authorName: String? = null,
|
authorName: String? = null,
|
||||||
|
dimensions: String? = null,
|
||||||
|
blurhash: String? = null,
|
||||||
nostrUriCallback: String? = null,
|
nostrUriCallback: String? = null,
|
||||||
alwaysShowVideo: Boolean = false,
|
automaticallyStartPlayback: State<Boolean>,
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
||||||
onDialog: ((Boolean) -> Unit)? = null
|
onDialog: ((Boolean) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val automaticallyStartPlayback = remember {
|
VideoPlayerActiveMutex(videoUri) { modifier, activeOnScreen ->
|
||||||
mutableStateOf<Boolean>(
|
val mediaItem = remember(videoUri) {
|
||||||
if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback.value
|
mutableStateOf(
|
||||||
)
|
MediaItem.Builder()
|
||||||
}
|
.setMediaId(videoUri)
|
||||||
|
.setUri(videoUri)
|
||||||
if (!automaticallyStartPlayback.value) {
|
.setMediaMetadata(
|
||||||
ImageUrlWithDownloadButton(url = videoUri, showImage = automaticallyStartPlayback)
|
MediaMetadata.Builder()
|
||||||
} else {
|
.setArtist(authorName?.ifBlank { null })
|
||||||
VideoPlayerActiveMutex(videoUri) { modifier, activeOnScreen ->
|
.setTitle(title?.ifBlank { null } ?: videoUri)
|
||||||
val mediaItem = remember(videoUri) {
|
.setArtworkUri(
|
||||||
mutableStateOf(
|
try {
|
||||||
MediaItem.Builder()
|
if (artworkUri != null) {
|
||||||
.setMediaId(videoUri)
|
Uri.parse(artworkUri)
|
||||||
.setUri(videoUri)
|
} else {
|
||||||
.setMediaMetadata(
|
|
||||||
MediaMetadata.Builder()
|
|
||||||
.setArtist(authorName?.ifBlank { null })
|
|
||||||
.setTitle(title?.ifBlank { null } ?: videoUri)
|
|
||||||
.setArtworkUri(
|
|
||||||
try {
|
|
||||||
if (artworkUri != null) {
|
|
||||||
Uri.parse(artworkUri)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
)
|
} catch (e: Exception) {
|
||||||
.build()
|
null
|
||||||
)
|
}
|
||||||
.build()
|
)
|
||||||
)
|
.build()
|
||||||
}
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
GetVideoController(
|
GetVideoController(
|
||||||
mediaItem = mediaItem,
|
mediaItem = mediaItem,
|
||||||
videoUri = videoUri,
|
videoUri = videoUri,
|
||||||
defaultToStart = defaultToStart,
|
defaultToStart = defaultToStart,
|
||||||
nostrUriCallback = nostrUriCallback
|
nostrUriCallback = nostrUriCallback
|
||||||
) { controller, keepPlaying ->
|
) { controller, keepPlaying ->
|
||||||
RenderVideoPlayer(
|
RenderVideoPlayer(
|
||||||
controller = controller,
|
controller = controller,
|
||||||
thumbData = thumb,
|
thumbData = thumb,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
dimensions = dimensions,
|
||||||
waveform = waveform,
|
blurhash = blurhash,
|
||||||
keepPlaying = keepPlaying,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
waveform = waveform,
|
||||||
activeOnScreen = activeOnScreen,
|
keepPlaying = keepPlaying,
|
||||||
modifier = modifier,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
activeOnScreen = activeOnScreen,
|
||||||
onDialog = onDialog
|
modifier = modifier,
|
||||||
)
|
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
||||||
}
|
onDialog = onDialog
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -534,6 +552,8 @@ private fun RenderVideoPlayer(
|
|||||||
controller: MediaController,
|
controller: MediaController,
|
||||||
thumbData: VideoThumb?,
|
thumbData: VideoThumb?,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
dimensions: String? = null,
|
||||||
|
blurhash: String? = null,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
keepPlaying: MutableState<Boolean>,
|
keepPlaying: MutableState<Boolean>,
|
||||||
@@ -586,6 +606,8 @@ private fun RenderVideoPlayer(
|
|||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
|
setBackgroundColor(Color.Transparent.toArgb())
|
||||||
|
setShutterBackgroundColor(Color.Transparent.toArgb())
|
||||||
controllerAutoShow = false
|
controllerAutoShow = false
|
||||||
thumbData?.thumb?.let { defaultArtwork = it }
|
thumbData?.thumb?.let { defaultArtwork = it }
|
||||||
hideController()
|
hideController()
|
||||||
@@ -609,6 +631,19 @@ private fun RenderVideoPlayer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ratio = remember {
|
||||||
|
aspectRatio(dimensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ratio != null) {
|
||||||
|
DisplayBlurHash(
|
||||||
|
blurhash,
|
||||||
|
null,
|
||||||
|
ContentScale.Crop,
|
||||||
|
myModifier.aspectRatio(ratio)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = myModifier,
|
modifier = myModifier,
|
||||||
factory = factory
|
factory = factory
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
@@ -77,6 +79,7 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import coil.annotation.ExperimentalCoilApi
|
import coil.annotation.ExperimentalCoilApi
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil.compose.AsyncImagePainter
|
||||||
@@ -145,7 +148,8 @@ class ZoomableUrlVideo(
|
|||||||
dim: String? = null,
|
dim: String? = null,
|
||||||
uri: String? = null,
|
uri: String? = null,
|
||||||
val artworkUri: String? = null,
|
val artworkUri: String? = null,
|
||||||
val authorName: String? = null
|
val authorName: String? = null,
|
||||||
|
val blurhash: String? = null
|
||||||
) : ZoomableUrlContent(url, description, hash, dim, uri)
|
) : ZoomableUrlContent(url, description, hash, dim, uri)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -246,6 +250,8 @@ fun ZoomableContentView(
|
|||||||
title = content.description,
|
title = content.description,
|
||||||
artworkUri = content.artworkUri,
|
artworkUri = content.artworkUri,
|
||||||
authorName = content.authorName,
|
authorName = content.authorName,
|
||||||
|
dimensions = content.dim,
|
||||||
|
blurhash = content.blurhash,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
nostrUriCallback = content.uri,
|
nostrUriCallback = content.uri,
|
||||||
onDialog = { dialogOpen = true },
|
onDialog = { dialogOpen = true },
|
||||||
@@ -569,8 +575,9 @@ private fun AddedImageFeatures(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aspectRatio(dim: String?): Float? {
|
fun aspectRatio(dim: String?): Float? {
|
||||||
if (dim == null) return null
|
if (dim == null) return null
|
||||||
|
if (dim == "0x0") return null
|
||||||
|
|
||||||
val parts = dim.split("x")
|
val parts = dim.split("x")
|
||||||
if (parts.size != 2) return null
|
if (parts.size != 2) return null
|
||||||
@@ -578,7 +585,12 @@ private fun aspectRatio(dim: String?): Float? {
|
|||||||
return try {
|
return try {
|
||||||
val width = parts[0].toFloat()
|
val width = parts[0].toFloat()
|
||||||
val height = parts[1].toFloat()
|
val height = parts[1].toFloat()
|
||||||
width / height
|
|
||||||
|
if (width < 0.1 || height < 0.1) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
width / height
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -664,7 +676,7 @@ private fun InlineLoadingIcon() =
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DisplayBlurHash(
|
fun DisplayBlurHash(
|
||||||
blurhash: String?,
|
blurhash: String?,
|
||||||
description: String?,
|
description: String?,
|
||||||
contentScale: ContentScale,
|
contentScale: ContentScale,
|
||||||
@@ -694,7 +706,6 @@ fun ZoomableImageDialog(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val orientation = LocalConfiguration.current.orientation
|
val orientation = LocalConfiguration.current.orientation
|
||||||
println("This Log only exists to force orientation listener $orientation")
|
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
@@ -704,6 +715,8 @@ fun ZoomableImageDialog(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
val insets = ViewCompat.getRootWindowInsets(view)
|
||||||
|
|
||||||
val orientation = LocalConfiguration.current.orientation
|
val orientation = LocalConfiguration.current.orientation
|
||||||
println("This Log only exists to force orientation listener $orientation")
|
println("This Log only exists to force orientation listener $orientation")
|
||||||
|
|
||||||
@@ -810,6 +823,7 @@ private fun DialogContent(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(10.dp)
|
.padding(10.dp)
|
||||||
|
.statusBarsPadding().systemBarsPadding()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@@ -947,6 +961,10 @@ private fun RenderImageOrVideo(
|
|||||||
onToggleControllerVisibility: (() -> Unit)? = null,
|
onToggleControllerVisibility: (() -> Unit)? = null,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
|
val automaticallyStartPlayback = remember {
|
||||||
|
mutableStateOf<Boolean>(true)
|
||||||
|
}
|
||||||
|
|
||||||
if (content is ZoomableUrlImage) {
|
if (content is ZoomableUrlImage) {
|
||||||
val mainModifier = Modifier
|
val mainModifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -968,16 +986,15 @@ private fun RenderImageOrVideo(
|
|||||||
)
|
)
|
||||||
} else if (content is ZoomableUrlVideo) {
|
} else if (content is ZoomableUrlVideo) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
|
||||||
VideoView(
|
VideoViewInner(
|
||||||
videoUri = content.url,
|
videoUri = content.url,
|
||||||
title = content.description,
|
title = content.description,
|
||||||
artworkUri = content.artworkUri,
|
artworkUri = content.artworkUri,
|
||||||
authorName = content.authorName,
|
authorName = content.authorName,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
accountViewModel = accountViewModel,
|
onControllerVisibilityChanged = onControllerVisibilityChanged
|
||||||
alwaysShowVideo = true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (content is ZoomableLocalImage) {
|
} else if (content is ZoomableLocalImage) {
|
||||||
@@ -1002,16 +1019,15 @@ private fun RenderImageOrVideo(
|
|||||||
} else if (content is ZoomableLocalVideo) {
|
} else if (content is ZoomableLocalVideo) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
|
||||||
content.localFile?.let {
|
content.localFile?.let {
|
||||||
VideoView(
|
VideoViewInner(
|
||||||
videoUri = it.toUri().toString(),
|
videoUri = it.toUri().toString(),
|
||||||
title = content.description,
|
title = content.description,
|
||||||
artworkUri = content.artworkUri,
|
artworkUri = content.artworkUri,
|
||||||
authorName = content.authorName,
|
authorName = content.authorName,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
accountViewModel = accountViewModel,
|
onControllerVisibilityChanged = onControllerVisibilityChanged
|
||||||
alwaysShowVideo = true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, showHi
|
|||||||
}
|
}
|
||||||
Log.d("Rendering Metrics", "All Galeries: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed - ")
|
Log.d("Rendering Metrics", "All Galeries: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed - ")
|
||||||
|
|
||||||
Row(remember { Modifier.fillMaxWidth() }) {
|
Row(Modifier.fillMaxWidth()) {
|
||||||
Spacer(modifier = WidthAuthorPictureModifierWithPadding)
|
Spacer(modifier = WidthAuthorPictureModifierWithPadding)
|
||||||
|
|
||||||
val (value, elapsed) = measureTimedValue {
|
val (value, elapsed) = measureTimedValue {
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ private fun FeedLoaded(
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
|
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
|
||||||
val defaultModifier = remember {
|
val defaultModifier = remember {
|
||||||
Modifier
|
Modifier.fillMaxWidth().animateItemPlacement()
|
||||||
.fillMaxWidth().animateItemPlacement()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(defaultModifier) {
|
Row(defaultModifier) {
|
||||||
|
|||||||
Reference in New Issue
Block a user