diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendMetrics.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendMetrics.kt index b382bbcd5a..a4385c6e7d 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendMetrics.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendMetrics.kt @@ -5,9 +5,11 @@ package org.signal.mediasend +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp object MediaSendMetrics { val SelectedMediaPreviewSize = DpSize(44.dp, 44.dp) + val SelectedMediaPreviewShape = RoundedCornerShape(8.dp) } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt index 9d31e8a154..81ac4f4b4f 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt @@ -298,6 +298,10 @@ class MediaSendViewModel( MediaEditScreenEvent.ToggleMediaQuality -> { setSentMediaQuality(state.value.sentMediaQuality.next()) } + + is MediaEditScreenEvent.RemoveMedia -> { + removeMedia(mediaEditScreenEvent.media) + } } } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt index 9e5def8133..bbb791c24d 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt @@ -186,8 +186,7 @@ fun MediaEditScreen( .align(Alignment.BottomCenter) .then(if (isTextEditing) Modifier.imePadding() else Modifier) ) { - // A document is always the whole selection and has no thumbnail to show, so the rail stays hidden for it. - if (state.selectedMedia.isNotEmpty() && !isInteracting && focusedEditorState !is EditorState.Document) { + if (state.selectedMedia.size > 1 && !isInteracting) { ThumbnailRow( selectedMedia = state.selectedMedia, pagerState = pagerState, @@ -195,8 +194,12 @@ fun MediaEditScreen( onEvent(MediaEditScreenEvent.FocusedMediaChanged(it)) }, onThumbnailClick = { index -> - scope.launch { - pagerState.animateScrollToPage(index) + if (pagerState.currentPage == index) { + onEvent(MediaEditScreenEvent.RemoveMedia(state.selectedMedia[index])) + } else { + scope.launch { + pagerState.animateScrollToPage(index) + } } }, onReorder = { fromIndex, toIndex -> diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreenEvent.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreenEvent.kt index 6827180afc..2b824dd8a4 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreenEvent.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreenEvent.kt @@ -9,6 +9,7 @@ import org.signal.core.models.media.Media import org.signal.mediasend.edit.video.VideoTrimData sealed interface MediaEditScreenEvent { + data class RemoveMedia(val media: Media) : MediaEditScreenEvent data class FocusedMediaChanged(val media: Media) : MediaEditScreenEvent data class ReorderSelectedMedia(val fromIndex: Int, val toIndex: Int) : MediaEditScreenEvent data class AddMessageClick(val startWithEmojiKeyboard: Boolean = false) : MediaEditScreenEvent diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/SelectedMediaRow.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/ThumbnailRow.kt similarity index 79% rename from feature/media-send/src/main/java/org/signal/mediasend/edit/SelectedMediaRow.kt rename to feature/media-send/src/main/java/org/signal/mediasend/edit/ThumbnailRow.kt index 56c73904c2..27fff34ce2 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/SelectedMediaRow.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/ThumbnailRow.kt @@ -22,7 +22,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -31,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -47,6 +48,7 @@ import kotlinx.coroutines.launch import org.signal.core.models.media.Media import org.signal.core.ui.compose.DayNightPreviews import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.SignalIcons import org.signal.core.ui.compose.list.ReorderableItem import org.signal.core.ui.compose.list.rememberReorderBuffer import org.signal.core.ui.compose.list.rememberReorderableListState @@ -152,25 +154,27 @@ internal fun ThumbnailRow( } BoxWithConstraints( - modifier = Modifier.fillMaxWidth().draggable( - state = draggableState, - orientation = Orientation.Horizontal, - enabled = !isReordering, - onDragStopped = { velocity -> - scope.launch { - val targetPage = when { - velocity > 500f -> (pagerState.currentPage - 1).coerceAtLeast(0) - velocity < -500f -> (pagerState.currentPage + 1).coerceAtMost(selectedMedia.lastIndex) - else -> pagerState.currentPage - } - pagerState.animateScrollToPage(targetPage) + modifier = Modifier + .fillMaxWidth() + .draggable( + state = draggableState, + orientation = Orientation.Horizontal, + enabled = !isReordering, + onDragStopped = { velocity -> + scope.launch { + val targetPage = when { + velocity > 500f -> (pagerState.currentPage - 1).coerceAtLeast(0) + velocity < -500f -> (pagerState.currentPage + 1).coerceAtMost(selectedMedia.lastIndex) + else -> pagerState.currentPage + } + pagerState.animateScrollToPage(targetPage) - if (targetPage in selectedMedia.indices) { - onFocusedMediaChange(selectedMedia[targetPage]) + if (targetPage in selectedMedia.indices) { + onFocusedMediaChange(selectedMedia[targetPage]) + } } } - } - ) + ) ) { val itemWidth = MediaSendMetrics.SelectedMediaPreviewSize.width @@ -194,13 +198,21 @@ internal fun ThumbnailRow( } } - ReorderableItem(reorderableListState, index) { - Thumbnail( - media = media, - modifier = Modifier - .padding(horizontal = padding) - .clickable { onThumbnailClick(index) } - ) + ReorderableItem( + reorderableListState = reorderableListState, + index = index, + modifier = Modifier.clip(MediaSendMetrics.SelectedMediaPreviewShape) + ) { + DeleteBox( + enabled = pagerState.currentPage == index + ) { + Thumbnail( + media = media, + modifier = Modifier + .padding(horizontal = padding) + .clickable { onThumbnailClick(index) } + ) + } } } } @@ -211,6 +223,29 @@ private fun lerp(start: Dp, stop: Dp, fraction: Float): Dp { return start + (stop - start) * fraction } +@Composable +private fun DeleteBox( + enabled: Boolean, + content: @Composable () -> Unit +) { + Box { + content() + + if (enabled) { + Icon( + imageVector = SignalIcons.Trash.imageVector, + tint = Color.White, + contentDescription = null, + modifier = Modifier + .background(color = Color.Black.copy(alpha = 0.32f), shape = MediaSendMetrics.SelectedMediaPreviewShape) + .size(MediaSendMetrics.SelectedMediaPreviewSize) + .padding(10.dp) + .align(Alignment.Center) + ) + } + } +} + @Composable private fun Thumbnail(media: Media, modifier: Modifier = Modifier) { if (!LocalInspectionMode.current) { @@ -219,13 +254,13 @@ private fun Thumbnail(media: Media, modifier: Modifier = Modifier) { imageSize = MediaSendMetrics.SelectedMediaPreviewSize, modifier = modifier .size(MediaSendMetrics.SelectedMediaPreviewSize) - .clip(shape = RoundedCornerShape(8.dp)) + .clip(shape = MediaSendMetrics.SelectedMediaPreviewShape) ) } else { Box( modifier = modifier .size(MediaSendMetrics.SelectedMediaPreviewSize) - .background(color = Color.Gray, shape = RoundedCornerShape(8.dp)) + .background(color = Color.Gray, shape = MediaSendMetrics.SelectedMediaPreviewShape) ) } } @@ -255,6 +290,18 @@ private fun ThumbnailPreview() { } } +@DayNightPreviews +@Composable +private fun DeleteBoxPreview() { + Previews.Preview { + DeleteBox(enabled = true) { + Thumbnail( + media = rememberPreviewMedia(1).first() + ) + } + } +} + @Composable internal fun rememberPreviewMedia(count: Int): List { return remember(count) {