MediaSelectScreen rework.
This commit is contained in:
parent
a7b4a5d93d
commit
aecd17b2f0
@ -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,
|
||||
|
||||
@ -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<NavKey>,
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<MediaSendViewModel>(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,
|
||||
|
||||
@ -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<NavKey> 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"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<NavKey>.goToEdit() {
|
||||
if (contains(MediaSendNavKey.Edit)) {
|
||||
@ -24,6 +25,20 @@ internal fun NavBackStack<NavKey>.goToSend() {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun NavBackStack<NavKey>.goToFiles(mediaFolder: MediaFolder) {
|
||||
add(MediaSendNavKey.Select.Files(mediaFolder))
|
||||
}
|
||||
|
||||
internal fun NavBackStack<NavKey>.goToTextStory() {
|
||||
if (!contains(MediaSendNavKey.Capture.TextStory)) {
|
||||
add(MediaSendNavKey.Capture.TextStory)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun NavBackStack<NavKey>.goToCamera() {
|
||||
remove(MediaSendNavKey.Capture.TextStory)
|
||||
}
|
||||
|
||||
internal fun NavBackStack<NavKey>.pop() {
|
||||
if (isNotEmpty()) {
|
||||
removeAt(size - 1)
|
||||
|
||||
@ -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<NavKey>,
|
||||
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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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<NavKey>,
|
||||
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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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<Media>
|
||||
|
||||
data class Folders(
|
||||
val mediaFolders: List<MediaFolder>,
|
||||
override val selectedMedia: List<Media>
|
||||
) : MediaSelectScreenState
|
||||
|
||||
data class Files(
|
||||
val selectedMediaFolder: MediaFolder,
|
||||
val selectedMediaFolderItems: List<Media>,
|
||||
override val selectedMedia: List<Media>
|
||||
) : MediaSelectScreenState
|
||||
}
|
||||
@ -18,4 +18,12 @@
|
||||
<string name="SentMediaQuality__high">High</string>
|
||||
<!-- Setting option that can be selected to default media to be sent as standard quality by default -->
|
||||
<string name="SentMediaQuality__standard">Standard</string>
|
||||
<!-- Title of the screen where the user browses their device gallery to pick media to send. -->
|
||||
<string name="MediaSelectScreen__gallery">Gallery</string>
|
||||
<!-- Accessibility description for the button that advances from media selection to the next step in the send flow. -->
|
||||
<string name="MediaSelectScreen__next">Next</string>
|
||||
<!-- Label for the button that switches the capture screen to the camera. -->
|
||||
<string name="MediaCaptureScreen__camera">Camera</string>
|
||||
<!-- Label for the button that switches the capture screen to the text story editor. -->
|
||||
<string name="MediaCaptureScreen__text_story">Text Story</string>
|
||||
</resources>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user