Avoid using MutableStateFlow, just MutableState in presenter.

This commit is contained in:
Benoit Marty
2024-11-26 12:06:24 +01:00
parent 97d8fde39a
commit e01ebb40ac
2 changed files with 48 additions and 40 deletions
@@ -34,18 +34,12 @@ import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.rememberMarkdownTextEditorState import io.element.android.libraries.textcomposer.model.rememberMarkdownTextEditorState
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job 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.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@OptIn(ExperimentalCoroutinesApi::class)
class AttachmentsPreviewPresenter @AssistedInject constructor( class AttachmentsPreviewPresenter @AssistedInject constructor(
@Assisted private val attachment: Attachment, @Assisted private val attachment: Attachment,
@Assisted private val onDoneListener: OnDoneListener, @Assisted private val onDoneListener: OnDoneListener,
@@ -77,41 +71,34 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) } val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
val userSentAttachment = remember { val userSentAttachment = remember { mutableStateOf(false) }
MutableStateFlow(false)
}
val mediaUploadInfoStateFlow = remember { MutableStateFlow<AsyncData<MediaUploadInfo>>(AsyncData.Uninitialized) } val mediaUploadInfoState = remember { mutableStateOf<AsyncData<MediaUploadInfo>>(AsyncData.Uninitialized) }
var prePropressingJob: Job? = null var prePropressingJob: Job? = null
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
prePropressingJob = preProcessAttachment( prePropressingJob = preProcessAttachment(
attachment, attachment,
mediaUploadInfoStateFlow, mediaUploadInfoState,
) )
} }
LaunchedEffect(Unit) { LaunchedEffect(userSentAttachment.value, mediaUploadInfoState.value) {
userSentAttachment.filter { it } val mediaUploadInfo = mediaUploadInfoState.value
.flatMapConcat { if (userSentAttachment.value && mediaUploadInfo.isReady())
mediaUploadInfoStateFlow.filter { it.isReady() } if (mediaUploadInfo is AsyncData.Success) {
} val caption = markdownTextEditorState.getMessageMarkdown(permalinkBuilder)
.distinctUntilChanged() .takeIf { it.isNotEmpty() }
.collect { mediaUploadInfo -> ongoingSendAttachmentJob.value = coroutineScope.launch {
if (mediaUploadInfo is AsyncData.Success) { sendPreProcessedMedia(
val caption = markdownTextEditorState.getMessageMarkdown(permalinkBuilder) mediaUploadInfo = mediaUploadInfo.data,
.takeIf { it.isNotEmpty() } caption = caption,
ongoingSendAttachmentJob.value = coroutineScope.launch { sendActionState = sendActionState,
sendPreProcessedMedia( )
mediaUploadInfo = mediaUploadInfo.data,
caption = caption,
sendActionState = sendActionState,
)
}
} else if (mediaUploadInfo is AsyncData.Failure) {
sendActionState.value = SendActionState.Failure(mediaUploadInfo.error)
} }
// 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) { fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) {
@@ -119,7 +106,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
is AttachmentsPreviewEvents.SendAttachment -> coroutineScope.launch { is AttachmentsPreviewEvents.SendAttachment -> coroutineScope.launch {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
userSentAttachment.value = true userSentAttachment.value = true
val instantSending = mediaUploadInfoStateFlow.value.isReady() && useSendQueue val instantSending = mediaUploadInfoState.value.isReady() && useSendQueue
sendActionState.value = if (instantSending) { sendActionState.value = if (instantSending) {
SendActionState.Sending.InstantSending SendActionState.Sending.InstantSending
} else { } else {
@@ -130,7 +117,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
coroutineScope.cancel( coroutineScope.cancel(
attachment, attachment,
prePropressingJob, prePropressingJob,
mediaUploadInfoStateFlow.value, mediaUploadInfoState.value,
sendActionState, sendActionState,
) )
} }
@@ -154,7 +141,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
private fun CoroutineScope.preProcessAttachment( private fun CoroutineScope.preProcessAttachment(
attachment: Attachment, attachment: Attachment,
mediaUploadInfoState: MutableStateFlow<AsyncData<MediaUploadInfo>>, mediaUploadInfoState: MutableState<AsyncData<MediaUploadInfo>>,
) = launch { ) = launch {
when (attachment) { when (attachment) {
is Attachment.Media -> { is Attachment.Media -> {
@@ -168,22 +155,22 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
private suspend fun preProcessMedia( private suspend fun preProcessMedia(
mediaAttachment: Attachment.Media, mediaAttachment: Attachment.Media,
mediaUploadInfoState: MutableStateFlow<AsyncData<MediaUploadInfo>>, mediaUploadInfoState: MutableState<AsyncData<MediaUploadInfo>>,
) { ) {
mediaUploadInfoState.emit(AsyncData.Loading()) mediaUploadInfoState.value = AsyncData.Loading()
mediaSender.preProcessMedia( mediaSender.preProcessMedia(
uri = mediaAttachment.localMedia.uri, uri = mediaAttachment.localMedia.uri,
mimeType = mediaAttachment.localMedia.info.mimeType, mimeType = mediaAttachment.localMedia.info.mimeType,
).fold( ).fold(
onSuccess = { mediaUploadInfo -> onSuccess = { mediaUploadInfo ->
mediaUploadInfoState.emit(AsyncData.Success(mediaUploadInfo)) mediaUploadInfoState.value = AsyncData.Success(mediaUploadInfo)
}, },
onFailure = { onFailure = {
Timber.e(it, "Failed to pre-process media") Timber.e(it, "Failed to pre-process media")
if (it is CancellationException) { if (it is CancellationException) {
throw it throw it
} else { } else {
mediaUploadInfoState.emit(AsyncData.Failure(it)) mediaUploadInfoState.value = AsyncData.Failure(it)
} }
} }
) )
@@ -88,6 +88,8 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Processing)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0f)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0f))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0.5f)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0.5f))
@@ -124,6 +126,8 @@ class AttachmentsPreviewPresenterTest {
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Sending.InstantSending)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
sendFileResult.assertions().isCalledOnce() sendFileResult.assertions().isCalledOnce()
@@ -154,9 +158,11 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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 // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
sendFileResult.assertions().isCalledOnce() sendFileResult.assertions().isCalledOnce()
onDoneListener.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce()
@@ -181,6 +187,8 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Processing)
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
@@ -209,6 +217,9 @@ class AttachmentsPreviewPresenterTest {
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java)
} }
} }
@@ -227,6 +238,7 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.Cancel) initialState.eventSink(AttachmentsPreviewEvents.Cancel)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
deleteCallback.assertions().isCalledOnce() deleteCallback.assertions().isCalledOnce()
onDoneListener.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce()
@@ -258,6 +270,8 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Processing)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
sendImageResult.assertions().isCalledOnce().with( sendImageResult.assertions().isCalledOnce().with(
@@ -297,6 +311,8 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Processing)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
sendVideoResult.assertions().isCalledOnce().with( sendVideoResult.assertions().isCalledOnce().with(
@@ -334,6 +350,8 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Processing)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
sendAudioResult.assertions().isCalledOnce().with( sendAudioResult.assertions().isCalledOnce().with(
@@ -363,8 +381,9 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
val loadingState = awaitItem() assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing)
val failureState = awaitItem() val failureState = awaitItem()
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure)) assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure))
sendFileResult.assertions().isCalledOnce() sendFileResult.assertions().isCalledOnce()
@@ -383,6 +402,8 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) 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.Processing)
initialState.eventSink(AttachmentsPreviewEvents.ClearSendState) initialState.eventSink(AttachmentsPreviewEvents.ClearSendState)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)