Reduce API of JoinedRoom, caller must use the Timeline API from liveTimeline instead. (#4731)

This removes lots of boilerplate code.
This commit is contained in:
Benoit Marty 2025-05-20 09:07:43 +02:00 committed by GitHub
parent 3cf8237d29
commit 8d115213cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 258 additions and 651 deletions

View file

@ -101,7 +101,7 @@ class SendLocationPresenter @Inject constructor(
when (mode) {
SendLocationState.Mode.PinLocation -> {
val geoUri = event.cameraPosition.toGeoUri()
room.sendLocation(
room.liveTimeline.sendLocation(
body = generateBody(geoUri),
geoUri = geoUri,
description = null,
@ -119,7 +119,7 @@ class SendLocationPresenter @Inject constructor(
}
SendLocationState.Mode.SenderLocation -> {
val geoUri = event.toGeoUri()
room.sendLocation(
room.liveTimeline.sendLocation(
body = generateBody(geoUri),
geoUri = geoUri,
description = null,

View file

@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTran
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
@ -266,7 +267,9 @@ class SendLocationPresenterTest {
Result.success(Unit)
}
val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult,
liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
@ -327,7 +330,9 @@ class SendLocationPresenterTest {
Result.success(Unit)
}
val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult,
liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
@ -388,7 +393,9 @@ class SendLocationPresenterTest {
Result.success(Unit)
}
val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult,
liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(

View file

@ -424,7 +424,7 @@ class MessageComposerPresenter @AssistedInject constructor(
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit)
when (capturedMode) {
is MessageComposerMode.Attachment,
is MessageComposerMode.Normal -> room.sendMessage(
is MessageComposerMode.Normal -> room.liveTimeline.sendMessage(
body = message.markdown,
htmlBody = message.html,
intentionalMentions = message.intentionalMentions

View file

@ -5,6 +5,8 @@
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.messages.impl
import androidx.lifecycle.Lifecycle
@ -96,6 +98,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@ -173,12 +176,14 @@ class MessagesPresenterTest {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionSuccess)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
// No crashes when sending a reaction failed
timeline.apply { toggleReactionLambda = toggleReactionFailure }
timeline.toggleReactionLambda = toggleReactionFailure
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionFailure)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
@ -209,6 +214,7 @@ class MessagesPresenterTest {
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionSuccess)
.isCalledExactly(2)
.withSequence(

View file

@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.test.A_CAPTION
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
@ -108,15 +109,18 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media success scenario`() = runTest {
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
Result.success(FakeMediaUploadHandler())
}
val room = FakeJoinedRoom(
progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
),
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline(
progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
),
).apply {
sendFileLambda = sendFileResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@ -146,10 +150,12 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media after pre-processing success scenario`() = runTest {
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
Result.success(FakeMediaUploadHandler())
}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val processLatch = CompletableDeferred<Unit>()
@ -182,10 +188,12 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media before pre-processing success scenario`() = runTest {
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
Result.success(FakeMediaUploadHandler())
}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val processLatch = CompletableDeferred<Unit>()
@ -298,7 +306,9 @@ class AttachmentsPreviewPresenterTest {
givenImageResult()
}
val room = FakeJoinedRoom(
sendImageResult = sendImageResult,
liveTimeline = FakeTimeline().apply {
sendImageLambda = sendImageResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@ -340,7 +350,9 @@ class AttachmentsPreviewPresenterTest {
givenVideoResult()
}
val room = FakeJoinedRoom(
sendVideoResult = sendVideoResult,
liveTimeline = FakeTimeline().apply {
sendVideoLambda = sendVideoResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@ -382,7 +394,9 @@ class AttachmentsPreviewPresenterTest {
givenAudioResult()
}
val room = FakeJoinedRoom(
sendAudioResult = sendAudioResult,
liveTimeline = FakeTimeline().apply {
sendAudioLambda = sendAudioResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@ -416,10 +430,12 @@ class AttachmentsPreviewPresenterTest {
val failure = MediaPreProcessor.Failure(null)
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure)
}
Result.failure(failure)
}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = false)
moleculeFlow(RecompositionMode.Immediate) {
@ -445,11 +461,13 @@ class AttachmentsPreviewPresenterTest {
val failure = MediaPreProcessor.Failure(null)
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure)
}
Result.failure(failure)
}
val onDoneListenerResult = lambdaRecorder<Unit> {}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = true, onDoneListener = onDoneListenerResult)
moleculeFlow(RecompositionMode.Immediate) {

View file

@ -384,7 +384,9 @@ class MessageComposerPresenterTest {
val presenter = createPresenter(
coroutineScope = this,
room = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) },
liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
typingNoticeResult = { Result.success(Unit) }
),
)
@ -418,7 +420,9 @@ class MessageComposerPresenterTest {
coroutineScope = this,
isRichTextEditorEnabled = false,
room = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) },
liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
typingNoticeResult = { Result.success(Unit) }
),
)
@ -1118,16 +1122,16 @@ class MessageComposerPresenterTest {
val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
}
val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
sendMessageLambda = sendMessageResult
}
val room = FakeJoinedRoom(
liveTimeline = timeline,
sendMessageResult = sendMessageResult,
typingNoticeResult = { Result.success(Unit) }
)
val presenter = createPresenter(room = room, coroutineScope = this)

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
@ -45,6 +46,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@ -65,7 +67,9 @@ class VoiceMessageComposerPresenterTest {
Result.success(FakeMediaUploadHandler())
}
private val joinedRoom = FakeJoinedRoom(
sendVoiceMessageResult = sendVoiceMessageResult
liveTimeline = FakeTimeline().apply {
sendVoiceMessageLambda = sendVoiceMessageResult
},
)
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
private val mediaSender = MediaSender(mediaPreProcessor, joinedRoom, InMemorySessionPreferencesStore())
@ -295,7 +299,6 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
sendVoiceMessageResult.assertions().isCalledOnce()
@ -317,7 +320,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
skipItems(1) // Sending state
advanceUntilIdle()
// Now reply with a voice message
messageComposerContext.composerMode = aReplyMode()
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
@ -653,7 +656,7 @@ class VoiceMessageComposerPresenterTest {
permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(),
): VoiceMessageComposerPresenter {
return VoiceMessageComposerPresenter(
this,
backgroundScope,
voiceRecorder,
analyticsService,
mediaSender,

View file

@ -22,7 +22,7 @@ class DefaultEndPollAction @Inject constructor(
private val analyticsService: AnalyticsService,
) : EndPollAction {
override suspend fun execute(pollStartId: EventId): Result<Unit> {
return room.endPoll(
return room.liveTimeline.endPoll(
pollStartId = pollStartId,
text = "The poll with event id: $pollStartId has ended."
).onSuccess {

View file

@ -22,7 +22,7 @@ class DefaultSendPollResponseAction @Inject constructor(
private val analyticsService: AnalyticsService,
) : SendPollResponseAction {
override suspend fun execute(pollStartId: EventId, answerId: String): Result<Unit> {
return room.sendPollResponse(
return room.liveTimeline.sendPollResponse(
pollStartId = pollStartId,
answers = listOf(answerId),
).onSuccess {

View file

@ -41,7 +41,7 @@ class PollRepository @Inject constructor(
pollKind: PollKind,
maxSelections: Int,
): Result<Unit> = when (existingPollId) {
null -> room.createPoll(
null -> room.liveTimeline.createPoll(
question = question,
answers = answers,
maxSelections = maxSelections,

View file

@ -121,7 +121,9 @@ class CreatePollPresenterTest {
val createPollResult = lambdaRecorder<String, List<String>, Int, PollKind, Result<Unit>> { _, _, _, _ -> Result.success(Unit) }
val presenter = createCreatePollPresenter(
room = FakeJoinedRoom(
createPollResult = createPollResult
liveTimeline = FakeTimeline().apply {
createPollLambda = createPollResult
},
),
mode = CreatePollMode.NewPoll,
)
@ -169,7 +171,9 @@ class CreatePollPresenterTest {
}
val presenter = createCreatePollPresenter(
room = FakeJoinedRoom(
createPollResult = createPollResult
liveTimeline = FakeTimeline().apply {
createPollLambda = createPollResult
},
),
mode = CreatePollMode.NewPoll,
)
@ -253,12 +257,8 @@ class CreatePollPresenterTest {
@Test
fun `when edit poll fails, error is tracked`() = runTest {
val error = Exception("cause")
val editPollResult = lambdaRecorder { _: EventId, _: String, _: List<String>, _: Int, _: PollKind ->
Result.failure<Unit>(error)
}
val presenter = createCreatePollPresenter(
room = FakeJoinedRoom(
editPollResult = editPollResult,
liveTimeline = timeline,
),
mode = CreatePollMode.EditPoll(pollEventId),
@ -276,7 +276,7 @@ class CreatePollPresenterTest {
awaitPollLoaded().eventSink(CreatePollEvents.SetAnswer(0, "A"))
awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvents.Save)
advanceUntilIdle() // Wait for the coroutine to finish
assert(editPollLambda).isCalledOnce()
editPollLambda.assertions().isCalledOnce()
assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
assertThat(fakeAnalyticsService.trackedErrors).containsExactly(

View file

@ -94,7 +94,7 @@ class SharePresenter @AssistedInject constructor(
onPlainText = { text ->
roomIds
.map { roomId ->
matrixClient.getJoinedRoom(roomId)?.sendMessage(
matrixClient.getJoinedRoom(roomId)?.liveTimeline?.sendMessage(
body = text,
htmlBody = null,
intentionalMentions = emptyList(),

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
@ -91,7 +92,9 @@ class SharePresenterTest {
@Test
fun `present - send text ok`() = runTest {
val joinedRoom = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) },
liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
)
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, joinedRoom)
@ -122,7 +125,9 @@ class SharePresenterTest {
Result.success(FakeMediaUploadHandler())
}
val joinedRoom = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, joinedRoom)