Avoid using MutableStateFlow, just MutableState in presenter.

This commit is contained in:
Benoit Marty 2024-11-26 12:06:24 +01:00
parent 4689607150
commit 5f27c7107a
2 changed files with 48 additions and 40 deletions

View file

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

View file

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