Add delete function for editor thumbs.

This commit is contained in:
Alex Hart
2026-07-29 09:57:47 -03:00
parent b92d5140db
commit 3c249e8998
5 changed files with 87 additions and 30 deletions
@@ -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)
}
@@ -298,6 +298,10 @@ class MediaSendViewModel(
MediaEditScreenEvent.ToggleMediaQuality -> {
setSentMediaQuality(state.value.sentMediaQuality.next())
}
is MediaEditScreenEvent.RemoveMedia -> {
removeMedia(mediaEditScreenEvent.media)
}
}
}
@@ -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 ->
@@ -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
@@ -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<Media> {
return remember(count) {