Media: send file without preview
This commit is contained in:
parent
727cf69a39
commit
c8c2cb8ff3
3 changed files with 77 additions and 11 deletions
|
|
@ -72,6 +72,7 @@ import io.element.android.features.messages.impl.timeline.TimelineView
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
|
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
|
||||||
import io.element.android.libraries.androidutils.ui.hideKeyboard
|
import io.element.android.libraries.androidutils.ui.hideKeyboard
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
|
@ -87,6 +88,8 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
import io.element.android.libraries.ui.strings.R as StringsR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessagesView(
|
fun MessagesView(
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
|
|
@ -109,12 +112,7 @@ fun MessagesView(
|
||||||
val bottomSheetState = rememberModalBottomSheetState(initialValue = initialBottomSheetState)
|
val bottomSheetState = rememberModalBottomSheetState(initialValue = initialBottomSheetState)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val attachmentsState = state.composerState.attachmentsState
|
AttachmentStateView(state.composerState.attachmentsState, onPreviewAttachments)
|
||||||
if (attachmentsState is AttachmentsState.Previewing) {
|
|
||||||
LaunchedEffect(attachmentsState) {
|
|
||||||
onPreviewAttachments(attachmentsState.attachments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = bottomSheetState.isVisible) {
|
BackHandler(enabled = bottomSheetState.isVisible) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
|
|
@ -221,6 +219,20 @@ fun MessagesView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AttachmentStateView(
|
||||||
|
state: AttachmentsState,
|
||||||
|
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit
|
||||||
|
) {
|
||||||
|
when (state) {
|
||||||
|
AttachmentsState.None -> Unit
|
||||||
|
is AttachmentsState.Previewing -> LaunchedEffect(state) {
|
||||||
|
onPreviewAttachments(state.attachments)
|
||||||
|
}
|
||||||
|
is AttachmentsState.Sending -> ProgressDialog(text = stringResource(id = StringsR.string.common_loading))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessagesViewContent(
|
fun MessagesViewContent(
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
|
|
|
||||||
|
|
@ -27,23 +27,28 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.media3.common.MimeTypes
|
||||||
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.attachments.preview.error.sendAttachmentError
|
||||||
import io.element.android.features.messages.impl.media.local.LocalMediaFactory
|
import io.element.android.features.messages.impl.media.local.LocalMediaFactory
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.data.StableCharSequence
|
import io.element.android.libraries.core.data.StableCharSequence
|
||||||
import io.element.android.libraries.core.data.toStableCharSequence
|
import io.element.android.libraries.core.data.toStableCharSequence
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
||||||
|
import io.element.android.libraries.designsystem.utils.SnackbarMessage
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
|
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import io.element.android.libraries.core.mimetype.MimeTypes.Any as AnyMimeTypes
|
||||||
|
|
||||||
@SingleIn(RoomScope::class)
|
@SingleIn(RoomScope::class)
|
||||||
class MessageComposerPresenter @Inject constructor(
|
class MessageComposerPresenter @Inject constructor(
|
||||||
|
|
@ -52,6 +57,8 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
private val mediaPickerProvider: PickerProvider,
|
private val mediaPickerProvider: PickerProvider,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val localMediaFactory: LocalMediaFactory,
|
private val localMediaFactory: LocalMediaFactory,
|
||||||
|
private val mediaSender: MediaSender,
|
||||||
|
private val snackbarDispatcher: SnackbarDispatcher,
|
||||||
) : Presenter<MessageComposerState> {
|
) : Presenter<MessageComposerState> {
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
|
@ -69,16 +76,26 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
AttachmentsState.None
|
AttachmentsState.None
|
||||||
} else {
|
} else {
|
||||||
val mediaAttachment = Attachment.Media(localMedia)
|
val mediaAttachment = Attachment.Media(localMedia)
|
||||||
AttachmentsState.Previewing(persistentListOf(mediaAttachment))
|
val isPreviewable = when {
|
||||||
|
MimeTypes.isImage(mimeType) -> true
|
||||||
|
MimeTypes.isVideo(mimeType) -> true
|
||||||
|
MimeTypes.isAudio(mimeType) -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (isPreviewable) {
|
||||||
|
AttachmentsState.Previewing(persistentListOf(mediaAttachment))
|
||||||
|
} else {
|
||||||
|
AttachmentsState.Sending(persistentListOf(mediaAttachment))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker(onResult = { uri, mimeType ->
|
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker(onResult = { uri, mimeType ->
|
||||||
handlePickedMedia(uri, mimeType)
|
handlePickedMedia(uri, mimeType)
|
||||||
})
|
})
|
||||||
val filesPicker = mediaPickerProvider.registerFilePicker(MimeTypes.Any, onResult = { handlePickedMedia(it) })
|
val filesPicker = mediaPickerProvider.registerFilePicker(AnyMimeTypes, onResult = { handlePickedMedia(it) })
|
||||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(onResult = { handlePickedMedia(it, MimeTypes.Jpeg) })
|
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(onResult = { handlePickedMedia(it, MimeTypes.IMAGE_JPEG) })
|
||||||
val cameraVideoPicker = mediaPickerProvider.registerCameraVideoPicker(onResult = { handlePickedMedia(it, MimeTypes.Mp4) })
|
val cameraVideoPicker = mediaPickerProvider.registerCameraVideoPicker(onResult = { handlePickedMedia(it, MimeTypes.VIDEO_MP4) })
|
||||||
|
|
||||||
val isFullScreen = rememberSaveable {
|
val isFullScreen = rememberSaveable {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
|
|
@ -99,6 +116,13 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(attachmentsState.value) {
|
||||||
|
when (val attachmentStateValue = attachmentsState.value) {
|
||||||
|
is AttachmentsState.Sending -> localCoroutineScope.sendAttachment(attachmentStateValue.attachments.first(), attachmentsState)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: MessageComposerEvents) {
|
fun handleEvents(event: MessageComposerEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
|
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
|
||||||
|
|
@ -177,4 +201,33 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.sendAttachment(
|
||||||
|
attachment: Attachment,
|
||||||
|
attachmentState: MutableState<AttachmentsState>,
|
||||||
|
) = launch {
|
||||||
|
when (attachment) {
|
||||||
|
is Attachment.Media -> {
|
||||||
|
sendMedia(
|
||||||
|
uri = attachment.localMedia.uri,
|
||||||
|
mimeType = attachment.localMedia.mimeType,
|
||||||
|
attachmentState = attachmentState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendMedia(
|
||||||
|
uri: Uri,
|
||||||
|
mimeType: String,
|
||||||
|
attachmentState: MutableState<AttachmentsState>,
|
||||||
|
) {
|
||||||
|
mediaSender.sendMedia(uri, mimeType)
|
||||||
|
.onSuccess {
|
||||||
|
attachmentState.value = AttachmentsState.None
|
||||||
|
}.onFailure {
|
||||||
|
val snackbarMessage = SnackbarMessage(sendAttachmentError(it))
|
||||||
|
snackbarDispatcher.post(snackbarMessage)
|
||||||
|
attachmentState.value = AttachmentsState.None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ data class MessageComposerState(
|
||||||
sealed interface AttachmentsState {
|
sealed interface AttachmentsState {
|
||||||
object None : AttachmentsState
|
object None : 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
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface AttachmentSourcePicker {
|
sealed interface AttachmentSourcePicker {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue