From d73d1bb7416622c3133534254567eb42fbeed219 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 5 Aug 2026 14:39:18 +0000 Subject: [PATCH] Report focused media from the pager rather than the thumbnail rail in media-send. --- .../mediasend/screens/edit/MediaEditScreen.kt | 25 ++++++++++++++-- .../mediasend/screens/edit/ThumbnailRow.kt | 30 +++---------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/MediaEditScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/MediaEditScreen.kt index bf83e9f260..dde92be2b2 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/MediaEditScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/MediaEditScreen.kt @@ -30,7 +30,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,6 +42,8 @@ import androidx.compose.ui.unit.dp import androidx.fragment.compose.AndroidFragment import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import org.signal.core.ui.WindowBreakpoint import org.signal.core.ui.compose.AllDevicePreviews @@ -100,6 +104,24 @@ internal fun MediaEditScreen( } } + // Read through the latest selection rather than the one captured when the effect below started, since removals and + // reordering change which media a settled page refers to without restarting it. + val currentSelectedMedia by rememberUpdatedState(state.selectedMedia) + + // Focus belongs to the pager rather than to any one piece of chrome: the thumbnail rail is not composed for + // documents, and a swipe still has to be reported from there. + LaunchedEffect(pagerState) { + snapshotFlow { pagerState.isScrollInProgress } + .filter { !it } + .drop(1) + .collect { + val settledMedia = currentSelectedMedia.getOrNull(pagerState.currentPage) + if (settledMedia != null) { + onEvent(MediaEditScreenEvents.FocusedMediaChanged(settledMedia)) + } + } + } + // During a camera-first flow, backing out of edit when the only selection is the capture itself should discard the // capture and return to the camera rather than leaving the empty editor on the back stack. val isOnlyCameraFirstCapture = state.cameraFirstCapture != null && @@ -281,9 +303,6 @@ internal fun MediaEditScreen( selectedMedia = state.selectedMedia, pagerState = pagerState, enabled = !isInteracting, - onFocusedMediaChange = { - onEvent(MediaEditScreenEvents.FocusedMediaChanged(it)) - }, onThumbnailClick = { index -> if (pagerState.currentPage == index) { onEvent(MediaEditScreenEvents.RemoveMedia(state.selectedMedia[index])) diff --git a/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/ThumbnailRow.kt b/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/ThumbnailRow.kt index 2e0cc9416b..fb2a9c5a52 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/ThumbnailRow.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/screens/edit/ThumbnailRow.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue 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 @@ -42,8 +41,6 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import org.signal.core.models.media.Media import org.signal.core.ui.compose.DayNightPreviews @@ -67,12 +64,14 @@ private val MAX_PADDING = 8.dp /** * Horizontally scrollable thumbnail strip that syncs with [pagerState]. * Features fish-eye padding effect where the centered item has more padding. + * + * Dragging the strip scrolls the pager, and which media the settled page focuses is reported by the caller from + * [pagerState] rather than from here, since this row is only one of the chromes the pager can be swiped beneath. */ @Composable internal fun ThumbnailRow( selectedMedia: List, pagerState: PagerState, - onFocusedMediaChange: (Media) -> Unit, onThumbnailClick: (Int) -> Unit = {}, onReorder: (fromIndex: Int, toIndex: Int) -> Unit = { _, _ -> }, enabled: Boolean = true @@ -102,22 +101,6 @@ internal fun ThumbnailRow( pagerState.dispatchRawDelta(-scaledDelta) } - // Read through the latest selection rather than the one captured when the effect started, since reordering changes - // which media a settled page refers to without restarting the effect. - val currentSelectedMedia by rememberUpdatedState(selectedMedia) - - LaunchedEffect(pagerState) { - snapshotFlow { pagerState.isScrollInProgress } - .filter { !it } - .drop(1) - .collectLatest { - val settledPage = pagerState.currentPage - if (settledPage in currentSelectedMedia.indices) { - onFocusedMediaChange(currentSelectedMedia[settledPage]) - } - } - } - // The rail's scroll position belongs to the drag rather than the pager from the moment an item is picked up until the // reorder it produced has landed in state. Resuming any earlier means syncing to the pre-drag order and then having to // correct once the new order arrives. The single catch-up afterwards is animated so the rail glides to the dropped @@ -169,10 +152,6 @@ internal fun ThumbnailRow( else -> pagerState.currentPage } pagerState.animateScrollToPage(targetPage) - - if (targetPage in selectedMedia.indices) { - onFocusedMediaChange(selectedMedia[targetPage]) - } } } ) @@ -275,8 +254,7 @@ private fun ThumbnailRowPreview() { Previews.Preview { ThumbnailRow( selectedMedia = media, - pagerState = pagerState, - onFocusedMediaChange = { } + pagerState = pagerState ) } }