diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c7c06c0ee..5f864048c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -217,6 +217,7 @@ dependencies { implementation(libs.activity.compose) implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.storage) runtimeOnly(libs.androidx.appcompat) implementation(platform(libs.compose.bom)) diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/attachment/NoteMediaAttachmentsHorizontalPager.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/attachment/NoteMediaAttachmentsHorizontalPager.kt index 8fe2aa67d..77be6c077 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/attachment/NoteMediaAttachmentsHorizontalPager.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/attachment/NoteMediaAttachmentsHorizontalPager.kt @@ -3,13 +3,20 @@ package net.primal.android.notes.feed.note.ui.attachment import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -23,6 +30,11 @@ import net.primal.android.core.compose.attachment.model.NoteAttachmentUi import net.primal.android.notes.feed.note.ui.events.MediaClickEvent import net.primal.android.theme.AppTheme +const val SINGLE_IMAGE = 1 +const val TWO_IMAGES = 2 +const val THREE_IMAGES = 3 +const val FOUR_IMAGES = 4 + @ExperimentalFoundationApi @Composable fun NoteMediaAttachmentsHorizontalPager( @@ -37,45 +49,63 @@ fun NoteMediaAttachmentsHorizontalPager( val pagerState = rememberPagerState { imagesCount } - if (imagesCount == 1) { - val attachment = mediaAttachments.first() - NoteMediaAttachment( - attachment = attachment, - blossoms = blossoms, - imageSizeDp = imageSizeDp, - onClick = { positionMs -> - onMediaClick( - MediaClickEvent( - noteId = attachment.noteId, - noteAttachmentType = attachment.type, - mediaUrl = attachment.url, - positionMs = positionMs, - ), - ) - }, - ) - } else { - HorizontalPager(state = pagerState, pageSpacing = 12.dp) { - val attachment = mediaAttachments[it] - NoteMediaAttachment( - attachment = attachment, + when (imagesCount) { + SINGLE_IMAGE -> { + SingleImageGallery( + mediaAttachments = mediaAttachments, blossoms = blossoms, imageSizeDp = imageSizeDp, - onClick = { positionMs -> - onMediaClick( - MediaClickEvent( - noteId = attachment.noteId, - noteAttachmentType = attachment.type, - mediaUrl = attachment.url, - positionMs = positionMs, - ), - ) - }, + onMediaClick = onMediaClick, ) } + TWO_IMAGES -> { + TwoImageGallery( + mediaAttachments = mediaAttachments, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onMediaClick = onMediaClick, + ) + } + THREE_IMAGES -> { + ThreeImageGallery( + mediaAttachments = mediaAttachments, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onMediaClick = onMediaClick, + ) + } + FOUR_IMAGES -> { + FourImageGallery( + mediaAttachments = mediaAttachments, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onMediaClick = onMediaClick, + ) + } + else -> { + HorizontalPager(state = pagerState, pageSpacing = 12.dp) { + val attachment = mediaAttachments[it] + NoteMediaAttachment( + modifier = Modifier.clip(AppTheme.shapes.medium), + attachment = attachment, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onClick = { positionMs -> + onMediaClick( + MediaClickEvent( + noteId = attachment.noteId, + noteAttachmentType = attachment.type, + mediaUrl = attachment.url, + positionMs = positionMs, + ), + ) + }, + ) + } + } } - if (imagesCount > 1) { + if (imagesCount > FOUR_IMAGES) { Box( modifier = Modifier .align(Alignment.BottomCenter) @@ -95,17 +125,222 @@ fun NoteMediaAttachmentsHorizontalPager( } } +@Composable +private fun FourImageGallery( + mediaAttachments: List, + blossoms: List, + imageSizeDp: DpSize, + onMediaClick: (MediaClickEvent) -> Unit, +) { + val shapeMatrix = listOf( + RoundedCornerShape(AppTheme.shapes.medium.topStart, CornerSize(0.dp), CornerSize(0.dp), CornerSize(0.dp)), + RoundedCornerShape(CornerSize(0.dp), AppTheme.shapes.medium.topEnd, CornerSize(0.dp), CornerSize(0.dp)), + RoundedCornerShape(CornerSize(0.dp), CornerSize(0.dp), CornerSize(0.dp), AppTheme.shapes.medium.bottomEnd), + RoundedCornerShape(CornerSize(0.dp), CornerSize(0.dp), AppTheme.shapes.medium.bottomStart, CornerSize(0.dp)), + ) + + Column(modifier = Modifier.fillMaxWidth()) { + mediaAttachments.chunked(2).forEachIndexed { rowIndex, rowAttachments -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + rowAttachments.forEachIndexed { index, attachment -> + NoteMediaAttachment( + modifier = Modifier + .weight(1f) + .aspectRatio(1f) + .clip(shapeMatrix[rowIndex * 2 + index]), + attachment = attachment, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onClick = { positionMs -> + onMediaClick( + MediaClickEvent( + noteId = attachment.noteId, + noteAttachmentType = attachment.type, + mediaUrl = attachment.url, + positionMs = positionMs, + ), + ) + }, + ) + } + } + } + } +} + +@Composable +private fun ThreeImageGallery( + mediaAttachments: List, + blossoms: List, + imageSizeDp: DpSize, + onMediaClick: (MediaClickEvent) -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + NoteMediaAttachment( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(2f / 1f) + .clip( + RoundedCornerShape( + topStart = AppTheme.shapes.medium.topStart, + topEnd = AppTheme.shapes.medium.topEnd, + bottomStart = CornerSize(0.dp), + bottomEnd = CornerSize(0.dp), + ), + ), + attachment = mediaAttachments[0], + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onClick = { positionMs -> + onMediaClick( + MediaClickEvent( + noteId = mediaAttachments[0].noteId, + noteAttachmentType = mediaAttachments[0].type, + mediaUrl = mediaAttachments[0].url, + positionMs = positionMs, + ), + ) + }, + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + mediaAttachments.drop(1).take(2).forEachIndexed { index, attachment -> + NoteMediaAttachment( + modifier = Modifier + .weight(1f) + .aspectRatio(1f) + .clip( + if (index == 0) { + RoundedCornerShape( + topStart = CornerSize(0.dp), + topEnd = CornerSize(0.dp), + bottomStart = AppTheme.shapes.medium.bottomStart, + bottomEnd = CornerSize(0.dp), + ) + } else { + RoundedCornerShape( + topStart = CornerSize(0.dp), + topEnd = CornerSize(0.dp), + bottomStart = CornerSize(0.dp), + bottomEnd = AppTheme.shapes.medium.bottomEnd, + ) + }, + ), + attachment = attachment, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onClick = { positionMs -> + onMediaClick( + MediaClickEvent( + noteId = attachment.noteId, + noteAttachmentType = attachment.type, + mediaUrl = attachment.url, + positionMs = positionMs, + ), + ) + }, + ) + } + } + } +} + +@Composable +private fun TwoImageGallery( + mediaAttachments: List, + blossoms: List, + imageSizeDp: DpSize, + onMediaClick: (MediaClickEvent) -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + mediaAttachments.take(2).forEachIndexed { index, attachment -> + NoteMediaAttachment( + modifier = Modifier + .weight(1f) + .aspectRatio(1f) + .clip( + if (index == 0) { + RoundedCornerShape( + topStart = AppTheme.shapes.medium.topStart, + topEnd = CornerSize(0.dp), + bottomStart = AppTheme.shapes.medium.bottomStart, + bottomEnd = CornerSize(0.dp), + ) + } else { + RoundedCornerShape( + topStart = CornerSize(0.dp), + topEnd = AppTheme.shapes.medium.topEnd, + bottomStart = CornerSize(0.dp), + bottomEnd = AppTheme.shapes.medium.bottomEnd, + ) + }, + ), + attachment = attachment, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onClick = { positionMs -> + onMediaClick( + MediaClickEvent( + noteId = attachment.noteId, + noteAttachmentType = attachment.type, + mediaUrl = attachment.url, + positionMs = positionMs, + ), + ) + }, + ) + } + } +} + +@Composable +private fun SingleImageGallery( + mediaAttachments: List, + blossoms: List, + imageSizeDp: DpSize, + onMediaClick: (MediaClickEvent) -> Unit, +) { + val attachment = mediaAttachments.first() + NoteMediaAttachment( + modifier = Modifier.clip(AppTheme.shapes.medium), + attachment = attachment, + blossoms = blossoms, + imageSizeDp = imageSizeDp, + onClick = { positionMs -> + onMediaClick( + MediaClickEvent( + noteId = attachment.noteId, + noteAttachmentType = attachment.type, + mediaUrl = attachment.url, + positionMs = positionMs, + ), + ) + }, + ) +} + @Composable private fun NoteMediaAttachment( attachment: NoteAttachmentUi, blossoms: List, imageSizeDp: DpSize, onClick: (positionMs: Long) -> Unit, + modifier: Modifier = Modifier, ) { BoxWithConstraints( - modifier = Modifier - .padding(vertical = 4.dp) - .clip(AppTheme.shapes.medium), + modifier = modifier + .padding(vertical = 4.dp), contentAlignment = Alignment.Center, ) { when (attachment.type) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06e72ad5b..8eb366932 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,6 +58,7 @@ telephoto = "0.14.0" timber = "5.0.1" url-detector = "0.1.23" webkit = "1.12.1" +storage = "1.5.0" [libraries] # Core libs @@ -193,6 +194,7 @@ androidx-camera-core = { group = "androidx.camera", name = "camera-core", versio androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camera" } androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camera" } androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camera" } +androidx-storage = { group = "androidx.test.services", name = "storage", version.ref = "storage" } [bundles] hilt = ["hilt-android", "navigation-hilt"]