Media: send file without preview

This commit is contained in:
ganfra 2023-05-22 16:25:50 +02:00
parent 727cf69a39
commit c8c2cb8ff3
3 changed files with 77 additions and 11 deletions

View file

@ -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,

View file

@ -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
}
}
} }

View file

@ -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 {