From a4793ebba53803aa7c483c429cfa94dfe02b8114 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 10 Aug 2026 14:38:17 -0300 Subject: [PATCH] Add discard changes warning dialog to media-send module. --- .../mediasend/v3/MediaSendV3Activity.kt | 5 +--- .../mediasend/MediaSendFlowViewModel.kt | 22 ++++++++++++-- .../signal/mediasend/MediaSendNavigation.kt | 30 +++++++++++++++++++ .../src/main/res/values/strings.xml | 4 +++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt index 84e6337f58..626e2a3c70 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Activity.kt @@ -228,10 +228,7 @@ class MediaSendV3Activity : .show(supportFragmentManager) } - is MediaSendFlowHudCommand.CloseScreen -> { - // TODO [media-send] warning dialog - finish() - } + is MediaSendFlowHudCommand.CloseScreen -> finish() } } ) diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendFlowViewModel.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendFlowViewModel.kt index 95b9ac7d6d..1e908cd356 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendFlowViewModel.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendFlowViewModel.kt @@ -128,6 +128,7 @@ class MediaSendFlowViewModel( internal val usernameScannedDialog = DialogController() internal val linkedDeviceScannedDialog = DialogController() internal val saveToStorageDialog = DialogController() + internal val discardMediaDialog = DialogController() internal val addToGroupStoryDialog = DialogController() internal val writeStoragePermission = PermissionController( @@ -260,6 +261,23 @@ class MediaSendFlowViewModel( } } + /** + * Leaves the flow at the user's request, confirming first if that would throw a selection away. Closing for reasons + * of our own emits [MediaSendFlowHudCommand.CloseScreen] directly instead. + */ + internal fun onCloseRequested() { + if (state.value.selectedMedia.isEmpty()) { + sendHudCommand(MediaSendFlowHudCommand.CloseScreen) + return + } + + viewModelScope.launch { + if (discardMediaDialog.show(Unit) == DialogResult.POSITIVE) { + sendHudCommand(MediaSendFlowHudCommand.CloseScreen) + } + } + } + /** * Backs out of a select screen while nothing is selected, stepping over an editor that would have nothing to edit * and no toolbar to leave by. Decided here rather than when the selection empties, so that re-selecting something @@ -269,7 +287,7 @@ class MediaSendFlowViewModel( val destination = backStack.dropLast(1).dropLastWhile { it == MediaSendRoute.Edit } if (destination.isEmpty()) { - sendHudCommand(MediaSendFlowHudCommand.CloseScreen) + onCloseRequested() return } @@ -291,7 +309,7 @@ class MediaSendFlowViewModel( private fun onCameraXScreenEvent(event: CameraXScreenEvents) { when (event) { - CameraXScreenEvents.CameraCloseClicked -> sendHudCommand(MediaSendFlowHudCommand.CloseScreen) + CameraXScreenEvents.CameraCloseClicked -> onCloseRequested() CameraXScreenEvents.GalleryClicked -> backStack.goToFolders() is CameraXScreenEvents.ImageCaptured -> handleImageCaptured(event) is CameraXScreenEvents.VideoCaptured -> handleVideoCaptured(event) diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavigation.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavigation.kt index 0c98343df4..e976f2dcc7 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavigation.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavigation.kt @@ -1,6 +1,7 @@ package org.signal.mediasend import android.annotation.SuppressLint +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -17,6 +18,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator @@ -29,6 +31,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import org.signal.core.ui.compose.DialogController import org.signal.core.ui.compose.Dialogs import org.signal.core.ui.compose.Snackbars import org.signal.core.ui.compose.showSnackbar @@ -129,6 +132,13 @@ internal fun MediaSendNavigation( } } + // NavDisplay only consumes back while there is somewhere left to go back to. Composed after it to catch the press + // at the root, which would otherwise fall through to the activity and finish it. + BackHandler(enabled = viewModel.backStack.size == 1) { + viewModel.onCloseRequested() + } + + DiscardMediaDialog(viewModel.discardMediaDialog) Snackbar(viewModel.snackbarEvents) Toast(viewModel.toastEvents) SendProgress(viewModel.state) @@ -138,6 +148,26 @@ internal fun MediaSendNavigation( private val TOAST_DURATION = 3.seconds private val SEND_PROGRESS_DELAY = 300.milliseconds +/** + * Dialog displayed when the user tries to close out of media send, to warn them that they'll discard media. + */ +@Composable +private fun DiscardMediaDialog( + controller: DialogController +) { + controller.Content { _, onDismissRequest, onConfirm, _, onDeny -> + Dialogs.SimpleAlertDialog( + title = stringResource(R.string.MediaSendDialogs__discard_media), + body = stringResource(R.string.MediaSendDialogs__you_will_lose_any_media), + confirm = stringResource(R.string.MediaSendDialogs__discard), + dismiss = stringResource(android.R.string.cancel), + onConfirm = onConfirm, + onDeny = onDeny, + onDismissRequest = onDismissRequest + ) + } +} + /** * Covers the whole flow while a send is in flight, so that the media on its way out cannot be edited or resent. Sends * that resolve immediately never show it. diff --git a/feature/media-send/src/main/res/values/strings.xml b/feature/media-send/src/main/res/values/strings.xml index fe54c023ec..af63c5b715 100644 --- a/feature/media-send/src/main/res/values/strings.xml +++ b/feature/media-send/src/main/res/values/strings.xml @@ -6,6 +6,10 @@ You\'ll lose any changes you\'ve made to this photo. Discard + + Discard media? + + You will lose any media you captured and changes you made. Save to phone?