Merge pull request #1528 from davotoula/image-gallery-improve-layout
Image gallery improve layout
This commit is contained in:
@@ -22,29 +22,94 @@ package com.vitorpamplona.amethyst.ui.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.ImageGalleryParagraph
|
import com.vitorpamplona.amethyst.commons.richtext.ImageGalleryParagraph
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
||||||
|
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
|
||||||
private const val ASPECT_RATIO = 4f / 3f
|
private const val ASPECT_RATIO = 4f / 3f
|
||||||
|
private const val PORTRAIT_ASPECT_RATIO = 3f / 4f
|
||||||
private val IMAGE_SPACING: Dp = Size5dp
|
private val IMAGE_SPACING: Dp = Size5dp
|
||||||
|
|
||||||
|
private data class FirstImageOrientation(
|
||||||
|
val aspectRatio: Float,
|
||||||
|
val isLandscape: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun MediaUrlImage.resolvedAspectRatio(): Float? =
|
||||||
|
dim
|
||||||
|
?.takeIf { it.hasSize() }
|
||||||
|
?.aspectRatio()
|
||||||
|
?: MediaAspectRatioCache.get(url)
|
||||||
|
|
||||||
|
private fun MediaUrlImage?.resolveOrientation(
|
||||||
|
landscapeDefaultAspectRatio: Float,
|
||||||
|
portraitDefaultAspectRatio: Float,
|
||||||
|
defaultToLandscape: Boolean = false,
|
||||||
|
): FirstImageOrientation {
|
||||||
|
val ratio = this?.resolvedAspectRatio()
|
||||||
|
val isLandscape = ratio?.let { it >= 1f } ?: defaultToLandscape
|
||||||
|
val resolvedAspectRatio =
|
||||||
|
ratio
|
||||||
|
?.takeIf { it > 0f }
|
||||||
|
?: if (isLandscape) landscapeDefaultAspectRatio else portraitDefaultAspectRatio
|
||||||
|
|
||||||
|
return FirstImageOrientation(
|
||||||
|
aspectRatio = resolvedAspectRatio,
|
||||||
|
isLandscape = isLandscape,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun rememberFirstImageOrientation(firstImage: MediaUrlImage?): FirstImageOrientation {
|
||||||
|
val orientationState =
|
||||||
|
remember(firstImage) {
|
||||||
|
mutableStateOf(firstImage.resolveOrientation(ASPECT_RATIO, PORTRAIT_ASPECT_RATIO))
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(firstImage) {
|
||||||
|
if (firstImage == null) return@LaunchedEffect
|
||||||
|
if (firstImage.dim?.hasSize() == true) return@LaunchedEffect
|
||||||
|
|
||||||
|
while (isActive) {
|
||||||
|
val ratio = firstImage.resolvedAspectRatio()
|
||||||
|
if (ratio != null && ratio > 0f) {
|
||||||
|
orientationState.value = FirstImageOrientation(ratio, ratio >= 1f)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return orientationState.value
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GalleryImage(
|
private fun GalleryImage(
|
||||||
image: MediaUrlImage,
|
image: MediaUrlImage,
|
||||||
@@ -115,19 +180,39 @@ private fun TwoImageGallery(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
) {
|
) {
|
||||||
Row(
|
val orientation = rememberFirstImageOrientation(images.firstOrNull())
|
||||||
modifier = Modifier.aspectRatio(ASPECT_RATIO),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
if (orientation.isLandscape) {
|
||||||
) {
|
Column(
|
||||||
images.take(2).forEach { image ->
|
modifier = Modifier.fillMaxWidth(),
|
||||||
GalleryImage(
|
verticalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
||||||
image = image,
|
) {
|
||||||
allImages = images,
|
images.take(2).forEach { image ->
|
||||||
modifier = Modifier.weight(1f).fillMaxSize(),
|
GalleryImage(
|
||||||
roundedCorner = roundedCorner,
|
image = image,
|
||||||
contentScale = ContentScale.Crop,
|
allImages = images,
|
||||||
accountViewModel = accountViewModel,
|
modifier = Modifier.fillMaxWidth().aspectRatio(orientation.aspectRatio),
|
||||||
)
|
roundedCorner = roundedCorner,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
||||||
|
) {
|
||||||
|
images.take(2).forEach { image ->
|
||||||
|
GalleryImage(
|
||||||
|
image = image,
|
||||||
|
allImages = images,
|
||||||
|
modifier = Modifier.weight(1f, fill = true).aspectRatio(orientation.aspectRatio),
|
||||||
|
roundedCorner = roundedCorner,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,32 +223,74 @@ private fun ThreeImageGallery(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
) {
|
) {
|
||||||
Row(
|
val firstImage = images.first()
|
||||||
modifier = Modifier.aspectRatio(ASPECT_RATIO),
|
val orientation = rememberFirstImageOrientation(firstImage)
|
||||||
horizontalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
val remainingImages = images.drop(1)
|
||||||
) {
|
|
||||||
GalleryImage(
|
|
||||||
image = images[0],
|
|
||||||
allImages = images,
|
|
||||||
modifier = Modifier.weight(2f).fillMaxSize(),
|
|
||||||
roundedCorner = roundedCorner,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if (orientation.isLandscape) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f).fillMaxSize(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
verticalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
||||||
) {
|
) {
|
||||||
images.drop(1).forEach { image ->
|
GalleryImage(
|
||||||
|
image = firstImage,
|
||||||
|
allImages = images,
|
||||||
|
modifier = Modifier.fillMaxWidth().aspectRatio(orientation.aspectRatio),
|
||||||
|
roundedCorner = roundedCorner,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
||||||
|
) {
|
||||||
|
remainingImages.forEach { image ->
|
||||||
|
GalleryImage(
|
||||||
|
image = image,
|
||||||
|
allImages = images,
|
||||||
|
modifier = Modifier.weight(1f).aspectRatio(1f),
|
||||||
|
roundedCorner = roundedCorner,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BoxWithConstraints {
|
||||||
|
val horizontalSpacing = IMAGE_SPACING
|
||||||
|
val columnWidth = ((maxWidth - horizontalSpacing).coerceAtLeast(0.dp)) / 2
|
||||||
|
val rowHeight = (columnWidth * 2) + IMAGE_SPACING
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(rowHeight),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
|
||||||
|
) {
|
||||||
GalleryImage(
|
GalleryImage(
|
||||||
image = image,
|
image = firstImage,
|
||||||
allImages = images,
|
allImages = images,
|
||||||
modifier = Modifier.weight(1f).fillMaxSize(),
|
modifier = Modifier.width(columnWidth).fillMaxHeight(),
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.width(columnWidth).fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(IMAGE_SPACING),
|
||||||
|
) {
|
||||||
|
remainingImages.forEach { image ->
|
||||||
|
GalleryImage(
|
||||||
|
image = image,
|
||||||
|
allImages = images,
|
||||||
|
modifier = Modifier.fillMaxWidth().aspectRatio(1f),
|
||||||
|
roundedCorner = roundedCorner,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user