diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index f87ea3fb24..1ca48848a2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -34,18 +34,12 @@ import io.element.android.libraries.textcomposer.model.TextEditorState import io.element.android.libraries.textcomposer.model.rememberMarkdownTextEditorState import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import timber.log.Timber import kotlin.coroutines.coroutineContext -@OptIn(ExperimentalCoroutinesApi::class) class AttachmentsPreviewPresenter @AssistedInject constructor( @Assisted private val attachment: Attachment, @Assisted private val onDoneListener: OnDoneListener, @@ -77,41 +71,34 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( val ongoingSendAttachmentJob = remember { mutableStateOf(null) } - val userSentAttachment = remember { - MutableStateFlow(false) - } + val userSentAttachment = remember { mutableStateOf(false) } - val mediaUploadInfoStateFlow = remember { MutableStateFlow>(AsyncData.Uninitialized) } + val mediaUploadInfoState = remember { mutableStateOf>(AsyncData.Uninitialized) } var prePropressingJob: Job? = null LaunchedEffect(Unit) { prePropressingJob = preProcessAttachment( attachment, - mediaUploadInfoStateFlow, + mediaUploadInfoState, ) } - LaunchedEffect(Unit) { - userSentAttachment.filter { it } - .flatMapConcat { - mediaUploadInfoStateFlow.filter { it.isReady() } - } - .distinctUntilChanged() - .collect { mediaUploadInfo -> - if (mediaUploadInfo is AsyncData.Success) { - val caption = markdownTextEditorState.getMessageMarkdown(permalinkBuilder) - .takeIf { it.isNotEmpty() } - ongoingSendAttachmentJob.value = coroutineScope.launch { - sendPreProcessedMedia( - mediaUploadInfo = mediaUploadInfo.data, - caption = caption, - sendActionState = sendActionState, - ) - } - } else if (mediaUploadInfo is AsyncData.Failure) { - sendActionState.value = SendActionState.Failure(mediaUploadInfo.error) + LaunchedEffect(userSentAttachment.value, mediaUploadInfoState.value) { + val mediaUploadInfo = mediaUploadInfoState.value + if (userSentAttachment.value && mediaUploadInfo.isReady()) + if (mediaUploadInfo is AsyncData.Success) { + val caption = markdownTextEditorState.getMessageMarkdown(permalinkBuilder) + .takeIf { it.isNotEmpty() } + ongoingSendAttachmentJob.value = coroutineScope.launch { + sendPreProcessedMedia( + mediaUploadInfo = mediaUploadInfo.data, + caption = caption, + sendActionState = sendActionState, + ) } - // else: cannot happen since we filtered with isReady() + } else if (mediaUploadInfo is AsyncData.Failure) { + sendActionState.value = SendActionState.Failure(mediaUploadInfo.error) } + // else: cannot happen since we filtered with isReady() } fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) { @@ -119,7 +106,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( is AttachmentsPreviewEvents.SendAttachment -> coroutineScope.launch { val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) userSentAttachment.value = true - val instantSending = mediaUploadInfoStateFlow.value.isReady() && useSendQueue + val instantSending = mediaUploadInfoState.value.isReady() && useSendQueue sendActionState.value = if (instantSending) { SendActionState.Sending.InstantSending } else { @@ -130,7 +117,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( coroutineScope.cancel( attachment, prePropressingJob, - mediaUploadInfoStateFlow.value, + mediaUploadInfoState.value, sendActionState, ) } @@ -154,7 +141,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( private fun CoroutineScope.preProcessAttachment( attachment: Attachment, - mediaUploadInfoState: MutableStateFlow>, + mediaUploadInfoState: MutableState>, ) = launch { when (attachment) { is Attachment.Media -> { @@ -168,22 +155,22 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( private suspend fun preProcessMedia( mediaAttachment: Attachment.Media, - mediaUploadInfoState: MutableStateFlow>, + mediaUploadInfoState: MutableState>, ) { - mediaUploadInfoState.emit(AsyncData.Loading()) + mediaUploadInfoState.value = AsyncData.Loading() mediaSender.preProcessMedia( uri = mediaAttachment.localMedia.uri, mimeType = mediaAttachment.localMedia.info.mimeType, ).fold( onSuccess = { mediaUploadInfo -> - mediaUploadInfoState.emit(AsyncData.Success(mediaUploadInfo)) + mediaUploadInfoState.value = AsyncData.Success(mediaUploadInfo) }, onFailure = { Timber.e(it, "Failed to pre-process media") if (it is CancellationException) { throw it } else { - mediaUploadInfoState.emit(AsyncData.Failure(it)) + mediaUploadInfoState.value = AsyncData.Failure(it) } } ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 204a5b5b76..48e5c208b6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -88,6 +88,8 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0f)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0.5f)) @@ -124,6 +126,8 @@ class AttachmentsPreviewPresenterTest { processLatch.complete(Unit) advanceUntilIdle() initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.InstantSending) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) sendFileResult.assertions().isCalledOnce() @@ -154,9 +158,11 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) // Pre-processing finishes processLatch.complete(Unit) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) sendFileResult.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce() @@ -181,6 +187,8 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) // Pre-processing finishes processLatch.complete(Unit) @@ -209,6 +217,9 @@ class AttachmentsPreviewPresenterTest { processLatch.complete(Unit) advanceUntilIdle() initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.InstantSending) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java) } } @@ -227,6 +238,7 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.Cancel) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) deleteCallback.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce() @@ -258,6 +270,8 @@ class AttachmentsPreviewPresenterTest { assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.textEditorState.setMarkdown(A_CAPTION) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) sendImageResult.assertions().isCalledOnce().with( @@ -297,6 +311,8 @@ class AttachmentsPreviewPresenterTest { assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.textEditorState.setMarkdown(A_CAPTION) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) sendVideoResult.assertions().isCalledOnce().with( @@ -334,6 +350,8 @@ class AttachmentsPreviewPresenterTest { assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.textEditorState.setMarkdown(A_CAPTION) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) sendAudioResult.assertions().isCalledOnce().with( @@ -363,8 +381,9 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) - val loadingState = awaitItem() - assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) val failureState = awaitItem() assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure)) sendFileResult.assertions().isCalledOnce() @@ -383,6 +402,8 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) initialState.eventSink(AttachmentsPreviewEvents.ClearSendState) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)