Media: align attachement source picker design with Figma
This commit is contained in:
parent
2c3fb8a549
commit
7c7f167692
7 changed files with 48 additions and 76 deletions
|
|
@ -18,7 +18,6 @@ package io.element.android.features.messages.impl
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentSourcePicker
|
|
||||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemContent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemContent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
||||||
|
|
@ -33,8 +32,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aMessagesState(),
|
aMessagesState(),
|
||||||
aMessagesState().copy(hasNetworkConnection = false),
|
aMessagesState().copy(hasNetworkConnection = false),
|
||||||
aMessagesState().copy(composerState = aMessageComposerState().copy(attachmentSourcePicker = AttachmentSourcePicker.AllMedia)),
|
aMessagesState().copy(composerState = aMessageComposerState().copy(showAttachmentSourcePicker = true)),
|
||||||
aMessagesState().copy(composerState = aMessageComposerState().copy(attachmentSourcePicker = AttachmentSourcePicker.Camera)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ import androidx.compose.material.ListItem
|
||||||
import androidx.compose.material.ModalBottomSheetValue
|
import androidx.compose.material.ModalBottomSheetValue
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.AttachFile
|
||||||
|
import androidx.compose.material.icons.filled.Collections
|
||||||
|
import androidx.compose.material.icons.filled.LocalLibrary
|
||||||
|
import androidx.compose.material.icons.filled.PhotoCamera
|
||||||
|
import androidx.compose.material.icons.filled.Videocam
|
||||||
import androidx.compose.material.rememberModalBottomSheetState
|
import androidx.compose.material.rememberModalBottomSheetState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
|
@ -61,7 +66,6 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListView
|
import io.element.android.features.messages.impl.actionlist.ActionListView
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.attachments.Attachment
|
import io.element.android.features.messages.impl.attachments.Attachment
|
||||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentSourcePicker
|
|
||||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
||||||
|
|
@ -102,7 +106,7 @@ fun MessagesView(
|
||||||
initialValue = ModalBottomSheetValue.Hidden,
|
initialValue = ModalBottomSheetValue.Hidden,
|
||||||
)
|
)
|
||||||
val composerState = state.composerState
|
val composerState = state.composerState
|
||||||
val initialBottomSheetState = if (LocalInspectionMode.current && composerState.attachmentSourcePicker != null) {
|
val initialBottomSheetState = if (LocalInspectionMode.current && composerState.showAttachmentSourcePicker != null) {
|
||||||
ModalBottomSheetValue.Expanded
|
ModalBottomSheetValue.Expanded
|
||||||
} else {
|
} else {
|
||||||
ModalBottomSheetValue.Hidden
|
ModalBottomSheetValue.Hidden
|
||||||
|
|
@ -154,8 +158,8 @@ fun MessagesView(
|
||||||
state.eventSink(MessagesEvents.HandleAction(action, event))
|
state.eventSink(MessagesEvents.HandleAction(action, event))
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(composerState.attachmentSourcePicker) {
|
LaunchedEffect(composerState.showAttachmentSourcePicker) {
|
||||||
if (composerState.attachmentSourcePicker != null) {
|
if (composerState.showAttachmentSourcePicker) {
|
||||||
// We need to use this instead of `LocalFocusManager.clearFocus()` to hide the keyboard when focus is on an Android View
|
// We need to use this instead of `LocalFocusManager.clearFocus()` to hide the keyboard when focus is on an Android View
|
||||||
localView.hideKeyboard()
|
localView.hideKeyboard()
|
||||||
bottomSheetState.show()
|
bottomSheetState.show()
|
||||||
|
|
@ -173,8 +177,7 @@ fun MessagesView(
|
||||||
sheetState = bottomSheetState,
|
sheetState = bottomSheetState,
|
||||||
displayHandle = true,
|
displayHandle = true,
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
MediaPickerMenu(
|
AttachmentSourcePickerMenu(
|
||||||
addAttachmentSourcePicker = composerState.attachmentSourcePicker,
|
|
||||||
eventSink = composerState.eventSink
|
eventSink = composerState.eventSink
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -305,50 +308,33 @@ fun MessagesViewTopBar(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun MediaPickerMenu(
|
|
||||||
addAttachmentSourcePicker: AttachmentSourcePicker?,
|
|
||||||
eventSink: (MessageComposerEvents) -> Unit,
|
|
||||||
) {
|
|
||||||
when (addAttachmentSourcePicker) {
|
|
||||||
null -> return
|
|
||||||
AttachmentSourcePicker.AllMedia -> AllMediaSourcePickerMenu(eventSink = eventSink)
|
|
||||||
AttachmentSourcePicker.Camera -> CameraSourcePickerMenu(eventSink = eventSink)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AllMediaSourcePickerMenu(
|
internal fun AttachmentSourcePickerMenu(
|
||||||
eventSink: (MessageComposerEvents) -> Unit,
|
eventSink: (MessageComposerEvents) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier) {
|
Column(modifier) {
|
||||||
ListItem(Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) }) {
|
ListItem(
|
||||||
Text(stringResource(R.string.screen_room_attachment_source_gallery))
|
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) },
|
||||||
}
|
icon = { Icon(Icons.Default.Collections, null) },
|
||||||
ListItem(Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) }) {
|
text = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) },
|
||||||
Text(stringResource(R.string.screen_room_attachment_source_files))
|
)
|
||||||
}
|
ListItem(
|
||||||
ListItem(Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromCamera) }) {
|
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) },
|
||||||
Text(stringResource(R.string.screen_room_attachment_source_camera))
|
icon = { Icon(Icons.Default.AttachFile, null) },
|
||||||
}
|
text = { Text(stringResource(R.string.screen_room_attachment_source_files)) },
|
||||||
}
|
)
|
||||||
}
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) },
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
icon = { Icon(Icons.Default.PhotoCamera, null) },
|
||||||
@Composable
|
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) },
|
||||||
internal fun CameraSourcePickerMenu(
|
)
|
||||||
eventSink: (MessageComposerEvents) -> Unit,
|
ListItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) },
|
||||||
) {
|
icon = { Icon(Icons.Default.Videocam, null) },
|
||||||
Column(modifier) {
|
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) },
|
||||||
ListItem(Modifier.clickable { eventSink(MessageComposerEvents.PickCameraAttachmentSource.Photo) }) {
|
)
|
||||||
Text(stringResource(R.string.screen_room_attachment_source_camera_photo))
|
|
||||||
}
|
|
||||||
ListItem(Modifier.clickable { eventSink(MessageComposerEvents.PickCameraAttachmentSource.Video) }) {
|
|
||||||
Text(stringResource(R.string.screen_room_attachment_source_camera_video))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,8 @@ sealed interface MessageComposerEvents {
|
||||||
object DismissAttachmentMenu : MessageComposerEvents
|
object DismissAttachmentMenu : MessageComposerEvents
|
||||||
sealed interface PickAttachmentSource : MessageComposerEvents {
|
sealed interface PickAttachmentSource : MessageComposerEvents {
|
||||||
object FromGallery : PickAttachmentSource
|
object FromGallery : PickAttachmentSource
|
||||||
object FromCamera : PickAttachmentSource
|
|
||||||
object FromFiles : PickAttachmentSource
|
object FromFiles : PickAttachmentSource
|
||||||
}
|
object PhotoFromCamera : PickAttachmentSource
|
||||||
sealed interface PickCameraAttachmentSource : MessageComposerEvents {
|
object VideoFromCamera : PickAttachmentSource
|
||||||
object Photo : PickCameraAttachmentSource
|
|
||||||
object Video : PickCameraAttachmentSource
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
mutableStateOf(MessageComposerMode.Normal(""))
|
mutableStateOf(MessageComposerMode.Normal(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
var attachmentSourcePicker: AttachmentSourcePicker? by remember { mutableStateOf(null) }
|
var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(composerMode.value) {
|
LaunchedEffect(composerMode.value) {
|
||||||
when (val modeValue = composerMode.value) {
|
when (val modeValue = composerMode.value) {
|
||||||
|
|
@ -135,26 +135,23 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage(event.message, composerMode, text)
|
is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage(event.message, composerMode, text)
|
||||||
is MessageComposerEvents.SetMode -> composerMode.value = event.composerMode
|
is MessageComposerEvents.SetMode -> composerMode.value = event.composerMode
|
||||||
MessageComposerEvents.AddAttachment -> localCoroutineScope.ifMediaPickersEnabled {
|
MessageComposerEvents.AddAttachment -> localCoroutineScope.ifMediaPickersEnabled {
|
||||||
attachmentSourcePicker = AttachmentSourcePicker.AllMedia
|
showAttachmentSourcePicker = true
|
||||||
}
|
}
|
||||||
MessageComposerEvents.DismissAttachmentMenu -> attachmentSourcePicker = null
|
MessageComposerEvents.DismissAttachmentMenu -> showAttachmentSourcePicker = false
|
||||||
MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.ifMediaPickersEnabled {
|
MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.ifMediaPickersEnabled {
|
||||||
attachmentSourcePicker = null
|
showAttachmentSourcePicker = false
|
||||||
galleryMediaPicker.launch()
|
galleryMediaPicker.launch()
|
||||||
}
|
}
|
||||||
MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.ifMediaPickersEnabled {
|
MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.ifMediaPickersEnabled {
|
||||||
attachmentSourcePicker = null
|
showAttachmentSourcePicker = false
|
||||||
filesPicker.launch()
|
filesPicker.launch()
|
||||||
}
|
}
|
||||||
MessageComposerEvents.PickAttachmentSource.FromCamera -> localCoroutineScope.ifMediaPickersEnabled {
|
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.ifMediaPickersEnabled {
|
||||||
attachmentSourcePicker = AttachmentSourcePicker.Camera
|
showAttachmentSourcePicker = false
|
||||||
}
|
|
||||||
MessageComposerEvents.PickCameraAttachmentSource.Photo -> localCoroutineScope.ifMediaPickersEnabled {
|
|
||||||
attachmentSourcePicker = null
|
|
||||||
cameraPhotoPicker.launch()
|
cameraPhotoPicker.launch()
|
||||||
}
|
}
|
||||||
MessageComposerEvents.PickCameraAttachmentSource.Video -> localCoroutineScope.ifMediaPickersEnabled {
|
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.ifMediaPickersEnabled {
|
||||||
attachmentSourcePicker = null
|
showAttachmentSourcePicker = false
|
||||||
cameraVideoPicker.launch()
|
cameraVideoPicker.launch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +161,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
text = text.value,
|
text = text.value,
|
||||||
isFullScreen = isFullScreen.value,
|
isFullScreen = isFullScreen.value,
|
||||||
mode = composerMode.value,
|
mode = composerMode.value,
|
||||||
attachmentSourcePicker = attachmentSourcePicker,
|
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||||
attachmentsState = attachmentsState.value,
|
attachmentsState = attachmentsState.value,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ data class MessageComposerState(
|
||||||
val text: StableCharSequence?,
|
val text: StableCharSequence?,
|
||||||
val isFullScreen: Boolean,
|
val isFullScreen: Boolean,
|
||||||
val mode: MessageComposerMode,
|
val mode: MessageComposerMode,
|
||||||
val attachmentSourcePicker: AttachmentSourcePicker?,
|
val showAttachmentSourcePicker: Boolean,
|
||||||
val attachmentsState: AttachmentsState,
|
val attachmentsState: AttachmentsState,
|
||||||
val eventSink: (MessageComposerEvents) -> Unit
|
val eventSink: (MessageComposerEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -40,8 +40,3 @@ sealed interface AttachmentsState {
|
||||||
data class Previewing(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
data class Previewing(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
||||||
data class Sending(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
data class Sending(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface AttachmentSourcePicker {
|
|
||||||
object AllMedia : AttachmentSourcePicker
|
|
||||||
object Camera : AttachmentSourcePicker
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ fun aMessageComposerState() = MessageComposerState(
|
||||||
text = StableCharSequence(""),
|
text = StableCharSequence(""),
|
||||||
isFullScreen = false,
|
isFullScreen = false,
|
||||||
mode = MessageComposerMode.Normal(content = ""),
|
mode = MessageComposerMode.Normal(content = ""),
|
||||||
attachmentSourcePicker = null,
|
showAttachmentSourcePicker = false,
|
||||||
attachmentsState = AttachmentsState.None,
|
attachmentsState = AttachmentsState.None,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
import io.element.android.libraries.mediapickers.test.FakePickerProvider
|
import io.element.android.libraries.mediapickers.test.FakePickerProvider
|
||||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
|
||||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||||
import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo
|
import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo
|
||||||
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
||||||
|
|
@ -259,7 +258,7 @@ class MessageComposerPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
||||||
|
|
||||||
assertThat(awaitItem().attachmentSourcePicker).isEqualTo(AttachmentSourcePicker.AllMedia)
|
assertThat(awaitItem().showAttachmentSourcePicker).isEqualTo(AttachmentSourcePicker.AllMedia)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +271,7 @@ class MessageComposerPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromCamera)
|
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromCamera)
|
||||||
|
|
||||||
assertThat(awaitItem().attachmentSourcePicker).isEqualTo(AttachmentSourcePicker.Camera)
|
assertThat(awaitItem().showAttachmentSourcePicker).isEqualTo(AttachmentSourcePicker.Camera)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,7 +286,7 @@ class MessageComposerPresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
initialState.eventSink(MessageComposerEvents.DismissAttachmentMenu)
|
initialState.eventSink(MessageComposerEvents.DismissAttachmentMenu)
|
||||||
assertThat(awaitItem().attachmentSourcePicker).isNull()
|
assertThat(awaitItem().showAttachmentSourcePicker).isNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue