diff --git a/core/models/src/main/java/org/signal/core/models/media/MediaFolder.kt b/core/models/src/main/java/org/signal/core/models/media/MediaFolder.kt index 0327545108..568a654b71 100644 --- a/core/models/src/main/java/org/signal/core/models/media/MediaFolder.kt +++ b/core/models/src/main/java/org/signal/core/models/media/MediaFolder.kt @@ -8,13 +8,16 @@ package org.signal.core.models.media import android.net.Uri import android.os.Parcelable import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +import org.signal.core.models.UriSerializer /** * Represents a folder that's shown in a media selector, containing [Media] items. */ @Parcelize +@Serializable data class MediaFolder( - val thumbnailUri: Uri, + @Serializable(with = UriSerializer::class) val thumbnailUri: Uri, val title: String, val itemCount: Int, val bucketId: String, diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreen.kt index 580a12499b..486ae5be58 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import org.signal.core.ui.compose.Buttons @@ -23,15 +24,14 @@ import org.signal.core.ui.compose.Buttons @Composable fun MediaCaptureScreen( backStack: NavBackStack, + onEvent: (MediaCaptureScreenEvent) -> Unit, cameraSlot: @Composable () -> Unit, textStoryEditorSlot: @Composable () -> Unit ) { Box(modifier = Modifier.fillMaxSize()) { - val top = backStack.last() - - when (top) { - is MediaSendNavKey.Capture.Camera -> cameraSlot() + when (backStack.last()) { is MediaSendNavKey.Capture.TextStory -> textStoryEditorSlot() + else -> cameraSlot() } Row( @@ -39,20 +39,12 @@ fun MediaCaptureScreen( .fillMaxWidth() .align(Alignment.BottomCenter) ) { - Buttons.Small(onClick = { - if (top == MediaSendNavKey.Capture.TextStory) { - backStack.remove(top) - } - }) { - Text(text = "Camera") + Buttons.Small(onClick = { onEvent(MediaCaptureScreenEvent.ShowCamera) }) { + Text(text = stringResource(R.string.MediaCaptureScreen__camera)) } - Buttons.Small(onClick = { - if (top == MediaSendNavKey.Capture.Camera) { - backStack.add(MediaSendNavKey.Capture.TextStory) - } - }) { - Text(text = "Text Story") + Buttons.Small(onClick = { onEvent(MediaCaptureScreenEvent.ShowTextStory) }) { + Text(text = stringResource(R.string.MediaCaptureScreen__text_story)) } } } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreenEvent.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreenEvent.kt new file mode 100644 index 0000000000..1dad345219 --- /dev/null +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaCaptureScreenEvent.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend + +sealed interface MediaCaptureScreenEvent { + data object ShowCamera : MediaCaptureScreenEvent + data object ShowTextStory : MediaCaptureScreenEvent +} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendEvent.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendEvent.kt index b0abb81bec..50cebb9632 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendEvent.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendEvent.kt @@ -11,10 +11,12 @@ import org.signal.mediasend.select.MediaSelectScreenEvent interface MediaSendEventHandler { fun onMediaSelectScreenEvent(mediaSelectScreenEvent: MediaSelectScreenEvent) fun onMediaEditScreenEvent(mediaEditScreenEvent: MediaEditScreenEvent) + fun onMediaCaptureScreenEvent(mediaCaptureScreenEvent: MediaCaptureScreenEvent) object Empty : MediaSendEventHandler { override fun onMediaSelectScreenEvent(mediaSelectScreenEvent: MediaSelectScreenEvent) = Unit override fun onMediaEditScreenEvent(mediaEditScreenEvent: MediaEditScreenEvent) = Unit + override fun onMediaCaptureScreenEvent(mediaCaptureScreenEvent: MediaCaptureScreenEvent) = Unit } } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavDisplay.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavDisplay.kt index 454db38330..66704363d4 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavDisplay.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavDisplay.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -22,6 +23,7 @@ import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Previews import org.signal.mediasend.edit.MediaEditScreen import org.signal.mediasend.select.MediaSelectScreen +import org.signal.mediasend.select.MediaSelectScreenState /** * Enforces the following flow of: @@ -48,16 +50,39 @@ fun MediaSendNavDisplay( is MediaSendNavKey.Capture -> NavEntry(MediaSendNavKey.Capture.Chrome) { MediaCaptureScreen( backStack = backStack, + onEvent = eventHandler::onMediaCaptureScreenEvent, cameraSlot = cameraSlot, textStoryEditorSlot = textStoryEditorSlot ) } - MediaSendNavKey.Select -> NavEntry(key) { + MediaSendNavKey.Select.Folders -> NavEntry(key) { val state by stateFlow.collectAsStateWithLifecycle() + val screenState = remember(state.mediaFolders, state.selectedMedia) { + MediaSelectScreenState.Folders( + mediaFolders = state.mediaFolders, + selectedMedia = state.selectedMedia + ) + } + MediaSelectScreen( - state = state, - backStack = backStack, + state = screenState, + onEvent = eventHandler::onMediaSelectScreenEvent + ) + } + + is MediaSendNavKey.Select.Files -> NavEntry(key) { + val state by stateFlow.collectAsStateWithLifecycle() + val screenState = remember(state.selectedMedia, state.selectedMediaFolderItems) { + MediaSelectScreenState.Files( + selectedMediaFolder = key.folder, + selectedMediaFolderItems = state.selectedMediaFolderItems, + selectedMedia = state.selectedMedia + ) + } + + MediaSelectScreen( + state = screenState, onEvent = eventHandler::onMediaSelectScreenEvent ) } @@ -66,7 +91,6 @@ fun MediaSendNavDisplay( val state by stateFlow.collectAsStateWithLifecycle() MediaEditScreen( state = state, - backStack = backStack, videoEditorSlot = videoEditorSlot, onEvent = eventHandler::onMediaEditScreenEvent ) diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavKey.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavKey.kt index 9eb1081420..cb083fa806 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavKey.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendNavKey.kt @@ -7,6 +7,7 @@ package org.signal.mediasend import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable +import org.signal.core.models.media.MediaFolder /** * Nav3 keys @@ -14,7 +15,13 @@ import kotlinx.serialization.Serializable @Serializable sealed interface MediaSendNavKey : NavKey { @Serializable - data object Select : MediaSendNavKey + sealed interface Select : MediaSendNavKey { + @Serializable + data object Folders : Select + + @Serializable + data class Files(val folder: MediaFolder) : Select + } @Serializable sealed interface Capture : MediaSendNavKey { diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendScreen.kt index 160082ff75..b40a00768a 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendScreen.kt @@ -10,11 +10,8 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigationevent.NavigationEventDispatcherOwner import androidx.navigationevent.compose.LocalNavigationEventDispatcherOwner import org.signal.core.ui.compose.theme.SignalTheme @@ -31,11 +28,6 @@ fun MediaSendScreen( ) { val viewModel = viewModel(factory = MediaSendViewModel.Factory(args = contractArgs)) - val state by viewModel.state.collectAsStateWithLifecycle() - val backStack = rememberNavBackStack( - if (state.isCameraFirst) MediaSendNavKey.Capture.Camera else MediaSendNavKey.Select - ) - LaunchedEffect(viewModel) { viewModel.hudCommands.collect { command -> onExternalHudCommand(command) @@ -47,7 +39,7 @@ fun MediaSendScreen( Surface { MediaSendNavDisplay( stateFlow = viewModel.state, - backStack = backStack, + backStack = viewModel.backStack, eventHandler = viewModel, modifier = modifier, cameraSlot = cameraSlot, 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 099285da95..9d29644d17 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 @@ -11,8 +11,13 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.serialization.saved import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.CreationExtras +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.serialization.NavBackStackSerializer +import androidx.navigation3.runtime.serialization.NavKeySerializer import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener @@ -75,6 +80,13 @@ class MediaSendViewModel( sendType = args.sendType ) + val backStack: NavBackStack by savedStateHandle.saved( + serializer = NavBackStackSerializer(NavKeySerializer()), + key = KEY_BACK_STACK + ) { + NavBackStack(if (args.isCameraFirst) MediaSendNavKey.Capture.Camera else MediaSendNavKey.Select.Folders) + } + /** * Main UI state. Backed by [SavedStateHandle] for automatic process death survival. * Writes to this flow are automatically persisted. @@ -155,12 +167,21 @@ class MediaSendViewModel( is MediaSelectScreenEvent.FolderClick -> onFolderClick(mediaSelectScreenEvent.mediaFolder) is MediaSelectScreenEvent.MediaClick -> onMediaClick(mediaSelectScreenEvent.media) is MediaSelectScreenEvent.SetFocusedMedia -> setFocusedMedia(mediaSelectScreenEvent.media) + MediaSelectScreenEvent.NavigateToEdit -> backStack.goToEdit() + } + } + + override fun onMediaCaptureScreenEvent(mediaCaptureScreenEvent: MediaCaptureScreenEvent) { + when (mediaCaptureScreenEvent) { + MediaCaptureScreenEvent.ShowCamera -> backStack.goToCamera() + MediaCaptureScreenEvent.ShowTextStory -> backStack.goToTextStory() } } override fun onMediaEditScreenEvent(mediaEditScreenEvent: MediaEditScreenEvent) { when (mediaEditScreenEvent) { is MediaEditScreenEvent.FocusedMediaChanged -> setFocusedMedia(mediaEditScreenEvent.media) + MediaEditScreenEvent.NavigateToSend -> backStack.goToSend() is MediaEditScreenEvent.AddMessageClick -> { val snapshot: MediaSendState = state.value @@ -176,6 +197,10 @@ class MediaSendViewModel( } private fun onFolderClick(mediaFolder: MediaFolder?) { + if (mediaFolder != null) { + backStack.goToFiles(mediaFolder) + } + viewModelScope.launch { if (mediaFolder != null) { val media = repository.getMedia(mediaFolder.bucketId) @@ -753,6 +778,7 @@ class MediaSendViewModel( private const val KEY_IDENTITY_CHANGES_SINCE = "media_send_vm_identity_changes_since" private const val KEY_STATE = "media_send_vm_state" private const val KEY_EDITED_VIDEO_URIS = "media_send_vm_edited_video_uris" + private const val KEY_BACK_STACK = "media_send_vm_back_stack" } /** diff --git a/feature/media-send/src/main/java/org/signal/mediasend/NavBackStackExtensions.kt b/feature/media-send/src/main/java/org/signal/mediasend/NavBackStackExtensions.kt index 15ac8082f8..9e18859ae6 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/NavBackStackExtensions.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/NavBackStackExtensions.kt @@ -7,6 +7,7 @@ package org.signal.mediasend import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey +import org.signal.core.models.media.MediaFolder internal fun NavBackStack.goToEdit() { if (contains(MediaSendNavKey.Edit)) { @@ -24,6 +25,20 @@ internal fun NavBackStack.goToSend() { } } +internal fun NavBackStack.goToFiles(mediaFolder: MediaFolder) { + add(MediaSendNavKey.Select.Files(mediaFolder)) +} + +internal fun NavBackStack.goToTextStory() { + if (!contains(MediaSendNavKey.Capture.TextStory)) { + add(MediaSendNavKey.Capture.TextStory) + } +} + +internal fun NavBackStack.goToCamera() { + remove(MediaSendNavKey.Capture.TextStory) +} + internal fun NavBackStack.pop() { if (isNotEmpty()) { removeAt(size - 1) 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 81db72224f..d72df1ed56 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 @@ -25,9 +25,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.dp -import androidx.navigation3.runtime.NavBackStack -import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.rememberNavBackStack import kotlinx.coroutines.launch import org.signal.core.ui.WindowBreakpoint import org.signal.core.ui.compose.AllDevicePreviews @@ -35,15 +32,12 @@ import org.signal.core.ui.compose.Previews import org.signal.core.ui.rememberWindowBreakpoint import org.signal.imageeditor.core.model.EditorModel import org.signal.mediasend.EditorState -import org.signal.mediasend.MediaSendNavKey import org.signal.mediasend.MediaSendState -import org.signal.mediasend.goToSend @Composable fun MediaEditScreen( state: MediaSendState, onEvent: (MediaEditScreenEvent) -> Unit, - backStack: NavBackStack, videoEditorSlot: @Composable () -> Unit = {} ) { val scope = rememberCoroutineScope() @@ -143,7 +137,7 @@ fun MediaEditScreen( AddAMessageRow( message = state.message, onEvent = onEvent, - onNextClick = { backStack.goToSend() }, + onNextClick = { onEvent(MediaEditScreenEvent.NavigateToSend) }, modifier = Modifier .widthIn(max = 624.dp) .padding(horizontal = 16.dp) @@ -179,7 +173,6 @@ private fun MediaEditScreenPreview() { ) ), onEvent = {}, - backStack = rememberNavBackStack(MediaSendNavKey.Edit), videoEditorSlot = { Box( modifier = Modifier 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 cc9bb01ff1..dd2deb197e 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 @@ -10,4 +10,5 @@ import org.signal.core.models.media.Media sealed interface MediaEditScreenEvent { data class FocusedMediaChanged(val media: Media) : MediaEditScreenEvent data class AddMessageClick(val startWithEmojiKeyboard: Boolean = false) : MediaEditScreenEvent + data object NavigateToSend : MediaEditScreenEvent } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreen.kt index 359f586de1..06b923c1fa 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreen.kt @@ -5,6 +5,7 @@ package org.signal.mediasend.select +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState @@ -50,15 +51,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.core.net.toUri -import androidx.navigation3.runtime.NavBackStack -import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.rememberNavBackStack import androidx.window.core.layout.WindowSizeClass import org.signal.core.models.media.Media import org.signal.core.models.media.MediaFolder @@ -70,11 +69,8 @@ import org.signal.core.ui.compose.Scaffolds import org.signal.core.ui.compose.ensureWidthIsAtLeastHeight import org.signal.glide.compose.GlideImage import org.signal.mediasend.MediaSendMetrics -import org.signal.mediasend.MediaSendNavKey -import org.signal.mediasend.MediaSendState +import org.signal.mediasend.R import org.signal.mediasend.edit.rememberPreviewMedia -import org.signal.mediasend.goToEdit -import org.signal.mediasend.pop /** * Allows user to select one or more pieces of content to add to the @@ -82,22 +78,19 @@ import org.signal.mediasend.pop */ @Composable internal fun MediaSelectScreen( - state: MediaSendState, - backStack: NavBackStack, + state: MediaSelectScreenState, onEvent: (MediaSelectScreenEvent) -> Unit ) { - val gridConfiguration = rememberGridConfiguration(state.selectedMediaFolder == null) + val gridConfiguration = rememberGridConfiguration(state is MediaSelectScreenState.Folders) + val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher Scaffolds.Settings( - title = state.selectedMediaFolder?.title ?: "Gallery", + title = when (state) { + is MediaSelectScreenState.Folders -> stringResource(R.string.MediaSelectScreen__gallery) + is MediaSelectScreenState.Files -> state.selectedMediaFolder.title + }, navigationIcon = ImageVector.vectorResource(org.signal.core.ui.R.drawable.symbol_arrow_start_24), - onNavigationClick = { - if (state.selectedMediaFolder != null) { - onEvent(MediaSelectScreenEvent.FolderClick(null)) - } else { - backStack.pop() - } - } + onNavigationClick = { backDispatcher?.onBackPressed() } ) { paddingValues -> Column( modifier = Modifier @@ -112,13 +105,17 @@ internal fun MediaSelectScreen( .padding(horizontal = gridConfiguration.horizontalMargin) .weight(1f) ) { - if (state.selectedMediaFolder == null) { - items(state.mediaFolders, key = { it.bucketId }) { - MediaFolderTile(it, onEvent) + when (state) { + is MediaSelectScreenState.Folders -> { + items(state.mediaFolders, key = { it.bucketId }) { + MediaFolderTile(it, onEvent) + } } - } else { - items(state.selectedMediaFolderItems, key = { it.uri }) { media -> - MediaTile(media = media, state.selectedMedia.indexOfFirst { it.uri == media.uri }, onEvent = onEvent) + + is MediaSelectScreenState.Files -> { + items(state.selectedMediaFolderItems, key = { it.uri }) { media -> + MediaTile(media = media, state.selectedMedia.indexOfFirst { it.uri == media.uri }, onEvent = onEvent) + } } } } @@ -149,13 +146,13 @@ internal fun MediaSelectScreen( items(state.selectedMedia, key = { it.uri }) { media -> MediaThumbnail(media, modifier = Modifier.animateItem()) { onEvent(MediaSelectScreenEvent.SetFocusedMedia(media)) - backStack.goToEdit() + onEvent(MediaSelectScreenEvent.NavigateToEdit) } } } NextButton(state.selectedMedia.size) { - backStack.goToEdit() + onEvent(MediaSelectScreenEvent.NavigateToEdit) } } } @@ -374,7 +371,7 @@ private fun NextButton(mediaSelectionCount: Int, onClick: () -> Unit) { Icon( imageVector = ImageVector.vectorResource(org.signal.core.ui.R.drawable.symbol_chevron_right_24), - contentDescription = "Next" + contentDescription = stringResource(R.string.MediaSelectScreen__next) ) } } @@ -407,10 +404,10 @@ private fun MediaThumbnail( private fun MediaSelectScreenFolderPreview() { Previews.Preview { MediaSelectScreen( - state = MediaSendState( - mediaFolders = rememberPreviewMediaFolders(20) + state = MediaSelectScreenState.Folders( + mediaFolders = rememberPreviewMediaFolders(20), + selectedMedia = emptyList() ), - backStack = rememberNavBackStack(MediaSendNavKey.Edit), onEvent = {} ) } @@ -425,13 +422,11 @@ private fun MediaSelectScreenMediaPreview() { Previews.Preview { MediaSelectScreen( - state = MediaSendState( - mediaFolders = folders, + state = MediaSelectScreenState.Files( selectedMediaFolder = folders.first(), selectedMediaFolderItems = media, selectedMedia = selectedMedia ), - backStack = rememberNavBackStack(MediaSendNavKey.Edit), onEvent = { if (it is MediaSelectScreenEvent.MediaClick) { if (it.media in selectedMedia) { diff --git a/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenEvent.kt b/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenEvent.kt index 13d5b70efb..62b0269c9d 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenEvent.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenEvent.kt @@ -12,4 +12,5 @@ sealed interface MediaSelectScreenEvent { data class FolderClick(val mediaFolder: MediaFolder?) : MediaSelectScreenEvent data class MediaClick(val media: Media) : MediaSelectScreenEvent data class SetFocusedMedia(val media: Media) : MediaSelectScreenEvent + data object NavigateToEdit : MediaSelectScreenEvent } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenState.kt b/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenState.kt new file mode 100644 index 0000000000..c9bc60576c --- /dev/null +++ b/feature/media-send/src/main/java/org/signal/mediasend/select/MediaSelectScreenState.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend.select + +import org.signal.core.models.media.Media +import org.signal.core.models.media.MediaFolder + +sealed interface MediaSelectScreenState { + + val selectedMedia: List + + data class Folders( + val mediaFolders: List, + override val selectedMedia: List + ) : MediaSelectScreenState + + data class Files( + val selectedMediaFolder: MediaFolder, + val selectedMediaFolderItems: List, + override val selectedMedia: List + ) : MediaSelectScreenState +} diff --git a/feature/media-send/src/main/res/values/strings.xml b/feature/media-send/src/main/res/values/strings.xml index 227797302e..c99c0a239f 100644 --- a/feature/media-send/src/main/res/values/strings.xml +++ b/feature/media-send/src/main/res/values/strings.xml @@ -18,4 +18,12 @@ High Standard + + Gallery + + Next + + Camera + + Text Story