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 c6f6c2cd65
commit caf25894d6
20 changed files with 258 additions and 651 deletions

View file

@ -101,7 +101,7 @@ class SendLocationPresenter @Inject constructor(
when (mode) { when (mode) {
SendLocationState.Mode.PinLocation -> { SendLocationState.Mode.PinLocation -> {
val geoUri = event.cameraPosition.toGeoUri() val geoUri = event.cameraPosition.toGeoUri()
room.sendLocation( room.liveTimeline.sendLocation(
body = generateBody(geoUri), body = generateBody(geoUri),
geoUri = geoUri, geoUri = geoUri,
description = null, description = null,
@ -119,7 +119,7 @@ class SendLocationPresenter @Inject constructor(
} }
SendLocationState.Mode.SenderLocation -> { SendLocationState.Mode.SenderLocation -> {
val geoUri = event.toGeoUri() val geoUri = event.toGeoUri()
room.sendLocation( room.liveTimeline.sendLocation(
body = generateBody(geoUri), body = generateBody(geoUri),
geoUri = geoUri, geoUri = geoUri,
description = null, 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.AN_EVENT_ID
import io.element.android.libraries.matrix.test.core.aBuildMeta 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.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
@ -266,7 +267,9 @@ class SendLocationPresenterTest {
Result.success(Unit) Result.success(Unit)
} }
val joinedRoom = FakeJoinedRoom( val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult, liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
) )
val sendLocationPresenter = createSendLocationPresenter(joinedRoom) val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState( fakePermissionsPresenter.givenState(
@ -327,7 +330,9 @@ class SendLocationPresenterTest {
Result.success(Unit) Result.success(Unit)
} }
val joinedRoom = FakeJoinedRoom( val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult, liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
) )
val sendLocationPresenter = createSendLocationPresenter(joinedRoom) val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState( fakePermissionsPresenter.givenState(
@ -388,7 +393,9 @@ class SendLocationPresenterTest {
Result.success(Unit) Result.success(Unit)
} }
val joinedRoom = FakeJoinedRoom( val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult, liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
) )
val sendLocationPresenter = createSendLocationPresenter(joinedRoom) val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState( fakePermissionsPresenter.givenState(

View file

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

View file

@ -5,6 +5,8 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.messages.impl package io.element.android.features.messages.impl
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -96,6 +98,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@ -173,12 +176,14 @@ class MessagesPresenterTest {
skipItems(1) skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionSuccess) assert(toggleReactionSuccess)
.isCalledOnce() .isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
// No crashes when sending a reaction failed // No crashes when sending a reaction failed
timeline.apply { toggleReactionLambda = toggleReactionFailure } timeline.toggleReactionLambda = toggleReactionFailure
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionFailure) assert(toggleReactionFailure)
.isCalledOnce() .isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
@ -209,6 +214,7 @@ class MessagesPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionSuccess) assert(toggleReactionSuccess)
.isCalledExactly(2) .isCalledExactly(2)
.withSequence( .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.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder 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.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.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.api.MediaUploadInfo
@ -108,15 +109,18 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media success scenario`() = runTest { fun `present - send media success scenario`() = runTest {
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
progressCallbackValues = listOf( liveTimeline = FakeTimeline(
Pair(0, 10), progressCallbackValues = listOf(
Pair(5, 10), Pair(0, 10),
Pair(10, 10) Pair(5, 10),
), Pair(10, 10)
sendFileResult = sendFileResult, ),
).apply {
sendFileLambda = sendFileResult
},
) )
val onDoneListener = lambdaRecorder<Unit> { } val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter( val presenter = createAttachmentsPreviewPresenter(
@ -146,10 +150,12 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media after pre-processing success scenario`() = runTest { fun `present - send media after pre-processing success scenario`() = runTest {
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendFileResult = sendFileResult, liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
) )
val onDoneListener = lambdaRecorder<Unit> { } val onDoneListener = lambdaRecorder<Unit> { }
val processLatch = CompletableDeferred<Unit>() val processLatch = CompletableDeferred<Unit>()
@ -182,10 +188,12 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media before pre-processing success scenario`() = runTest { fun `present - send media before pre-processing success scenario`() = runTest {
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendFileResult = sendFileResult, liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
) )
val onDoneListener = lambdaRecorder<Unit> { } val onDoneListener = lambdaRecorder<Unit> { }
val processLatch = CompletableDeferred<Unit>() val processLatch = CompletableDeferred<Unit>()
@ -298,7 +306,9 @@ class AttachmentsPreviewPresenterTest {
givenImageResult() givenImageResult()
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendImageResult = sendImageResult, liveTimeline = FakeTimeline().apply {
sendImageLambda = sendImageResult
},
) )
val onDoneListener = lambdaRecorder<Unit> { } val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter( val presenter = createAttachmentsPreviewPresenter(
@ -340,7 +350,9 @@ class AttachmentsPreviewPresenterTest {
givenVideoResult() givenVideoResult()
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendVideoResult = sendVideoResult, liveTimeline = FakeTimeline().apply {
sendVideoLambda = sendVideoResult
},
) )
val onDoneListener = lambdaRecorder<Unit> { } val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter( val presenter = createAttachmentsPreviewPresenter(
@ -382,7 +394,9 @@ class AttachmentsPreviewPresenterTest {
givenAudioResult() givenAudioResult()
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendAudioResult = sendAudioResult, liveTimeline = FakeTimeline().apply {
sendAudioLambda = sendAudioResult
},
) )
val onDoneListener = lambdaRecorder<Unit> { } val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter( val presenter = createAttachmentsPreviewPresenter(
@ -416,10 +430,12 @@ class AttachmentsPreviewPresenterTest {
val failure = MediaPreProcessor.Failure(null) val failure = MediaPreProcessor.Failure(null)
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure) Result.failure(failure)
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendFileResult = sendFileResult, liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
) )
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = false) val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
@ -445,11 +461,13 @@ class AttachmentsPreviewPresenterTest {
val failure = MediaPreProcessor.Failure(null) val failure = MediaPreProcessor.Failure(null)
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure) Result.failure(failure)
} }
val onDoneListenerResult = lambdaRecorder<Unit> {} val onDoneListenerResult = lambdaRecorder<Unit> {}
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendFileResult = sendFileResult, liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
) )
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = true, onDoneListener = onDoneListenerResult) val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = true, onDoneListener = onDoneListenerResult)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {

View file

@ -384,7 +384,9 @@ class MessageComposerPresenterTest {
val presenter = createPresenter( val presenter = createPresenter(
coroutineScope = this, coroutineScope = this,
room = FakeJoinedRoom( room = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) }, liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
typingNoticeResult = { Result.success(Unit) } typingNoticeResult = { Result.success(Unit) }
), ),
) )
@ -418,7 +420,9 @@ class MessageComposerPresenterTest {
coroutineScope = this, coroutineScope = this,
isRichTextEditorEnabled = false, isRichTextEditorEnabled = false,
room = FakeJoinedRoom( room = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) }, liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
typingNoticeResult = { Result.success(Unit) } typingNoticeResult = { Result.success(Unit) }
), ),
) )
@ -1118,16 +1122,16 @@ class MessageComposerPresenterTest {
val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> -> val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> ->
Result.success(Unit) Result.success(Unit)
} }
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
}
val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> -> val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
Result.success(Unit) Result.success(Unit)
} }
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
sendMessageLambda = sendMessageResult
}
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
liveTimeline = timeline, liveTimeline = timeline,
sendMessageResult = sendMessageResult,
typingNoticeResult = { Result.success(Unit) } typingNoticeResult = { Result.success(Unit) }
) )
val presenter = createPresenter(room = room, coroutineScope = this) 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.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler 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.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
@ -45,6 +46,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -65,7 +67,9 @@ class VoiceMessageComposerPresenterTest {
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
private val joinedRoom = FakeJoinedRoom( private val joinedRoom = FakeJoinedRoom(
sendVoiceMessageResult = sendVoiceMessageResult liveTimeline = FakeTimeline().apply {
sendVoiceMessageLambda = sendVoiceMessageResult
},
) )
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() } private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
private val mediaSender = MediaSender(mediaPreProcessor, joinedRoom, InMemorySessionPreferencesStore()) private val mediaSender = MediaSender(mediaPreProcessor, joinedRoom, InMemorySessionPreferencesStore())
@ -295,7 +299,6 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
sendVoiceMessageResult.assertions().isCalledOnce() sendVoiceMessageResult.assertions().isCalledOnce()
@ -317,7 +320,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
skipItems(1) // Sending state skipItems(1) // Sending state
advanceUntilIdle()
// Now reply with a voice message // Now reply with a voice message
messageComposerContext.composerMode = aReplyMode() messageComposerContext.composerMode = aReplyMode()
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
@ -653,7 +656,7 @@ class VoiceMessageComposerPresenterTest {
permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(), permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(),
): VoiceMessageComposerPresenter { ): VoiceMessageComposerPresenter {
return VoiceMessageComposerPresenter( return VoiceMessageComposerPresenter(
this, backgroundScope,
voiceRecorder, voiceRecorder,
analyticsService, analyticsService,
mediaSender, mediaSender,

View file

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

View file

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

View file

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

View file

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

View file

@ -94,7 +94,7 @@ class SharePresenter @AssistedInject constructor(
onPlainText = { text -> onPlainText = { text ->
roomIds roomIds
.map { roomId -> .map { roomId ->
matrixClient.getJoinedRoom(roomId)?.sendMessage( matrixClient.getJoinedRoom(roomId)?.liveTimeline?.sendMessage(
body = text, body = text,
htmlBody = null, htmlBody = null,
intentionalMentions = emptyList(), 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.FakeMatrixClient
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler 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.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.MediaPreProcessor
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
@ -91,7 +92,9 @@ class SharePresenterTest {
@Test @Test
fun `present - send text ok`() = runTest { fun `present - send text ok`() = runTest {
val joinedRoom = FakeJoinedRoom( val joinedRoom = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) }, liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
) )
val matrixClient = FakeMatrixClient().apply { val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, joinedRoom) givenGetRoomResult(A_ROOM_ID, joinedRoom)
@ -122,7 +125,9 @@ class SharePresenterTest {
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
val joinedRoom = FakeJoinedRoom( val joinedRoom = FakeJoinedRoom(
sendFileResult = sendFileResult, liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
) )
val matrixClient = FakeMatrixClient().apply { val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, joinedRoom) givenGetRoomResult(A_ROOM_ID, joinedRoom)

View file

@ -9,34 +9,21 @@ package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SendHandle import io.element.android.libraries.matrix.api.core.SendHandle
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.io.File
interface JoinedRoom : BaseRoom { interface JoinedRoom : BaseRoom {
val syncUpdateFlow: StateFlow<Long> val syncUpdateFlow: StateFlow<Long>
@ -63,135 +50,14 @@ interface JoinedRoom : BaseRoom {
createTimelineParams: CreateTimelineParams, createTimelineParams: CreateTimelineParams,
): Result<Timeline> ): Result<Timeline>
suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit>
suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit> suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit>
suspend fun sendImage(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler>
suspend fun sendVideo(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler>
suspend fun sendAudio(
file: File,
audioInfo: AudioInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler>
suspend fun sendFile(
file: File,
fileInfo: FileInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler>
suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler>
/**
* Share a location message in the room.
*
* @param body A human readable textual representation of the location.
* @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`.
* Respectively: latitude, longitude, and (optional) uncertainty.
* @param description Optional description of the location to display to the user.
* @param zoomLevel Optional zoom level to display the map at.
* @param assetType Optional type of the location asset.
* Set to SENDER if sharing own location. Set to PIN if sharing any location.
*/
suspend fun sendLocation(
body: String,
geoUri: String,
description: String? = null,
zoomLevel: Int? = null,
assetType: AssetType? = null,
): Result<Unit>
/**
* Create a poll in the room.
*
* @param question The question to ask.
* @param answers The list of answers.
* @param maxSelections The maximum number of answers that can be selected.
* @param pollKind The kind of poll to create.
*/
suspend fun createPoll(
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
): Result<Unit>
/**
* Edit a poll in the room.
*
* @param pollStartId The event ID of the poll start event.
* @param question The question to ask.
* @param answers The list of answers.
* @param maxSelections The maximum number of answers that can be selected.
* @param pollKind The kind of poll to create.
*/
suspend fun editPoll(
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
): Result<Unit>
/**
* Send a response to a poll.
*
* @param pollStartId The event ID of the poll start event.
* @param answers The list of answer ids to send.
*/
suspend fun sendPollResponse(pollStartId: EventId, answers: List<String>): Result<Unit>
/**
* Ends a poll in the room.
*
* @param pollStartId The event ID of the poll start event.
* @param text Fallback text of the poll end event.
*/
suspend fun endPoll(pollStartId: EventId, text: String): Result<Unit>
/** /**
* Send a typing notification. * Send a typing notification.
* @param isTyping True if the user is typing, false otherwise. * @param isTyping True if the user is typing, false otherwise.
*/ */
suspend fun typingNotice(isTyping: Boolean): Result<Unit> suspend fun typingNotice(isTyping: Boolean): Result<Unit>
suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit>
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>
suspend fun cancelSend(transactionId: TransactionId): Result<Unit>
suspend fun inviteUserById(id: UserId): Result<Unit> suspend fun inviteUserById(id: UserId): Result<Unit>
suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit> suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit>

View file

@ -13,20 +13,11 @@ import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SendHandle import io.element.android.libraries.matrix.api.core.SendHandle
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.CreateTimelineParams import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.IntentionalMention
@ -35,14 +26,11 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettingsStat
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.room.roomNotificationSettings import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.impl.core.RustSendHandle import io.element.android.libraries.matrix.impl.core.RustSendHandle
@ -84,7 +72,6 @@ import org.matrix.rustcomponents.sdk.getElementCallRequiredPermissions
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
import uniffi.matrix_sdk.RoomPowerLevelChanges import uniffi.matrix_sdk.RoomPowerLevelChanges
import java.io.File
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange
import org.matrix.rustcomponents.sdk.KnockRequest as InnerKnockRequest import org.matrix.rustcomponents.sdk.KnockRequest as InnerKnockRequest
@ -249,10 +236,6 @@ class JoinedRustRoom(
} }
} }
override suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit> {
return liveTimeline.sendMessage(body, htmlBody, intentionalMentions)
}
override suspend fun editMessage( override suspend fun editMessage(
eventId: EventId, eventId: EventId,
body: String, body: String,
@ -266,159 +249,12 @@ class JoinedRustRoom(
} }
} }
override suspend fun sendImage(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendImage(
file = file,
thumbnailFile = thumbnailFile,
imageInfo = imageInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters
)
}
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendVideo(
file = file,
thumbnailFile = thumbnailFile,
videoInfo = videoInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters
)
}
override suspend fun sendAudio(
file: File,
audioInfo: AudioInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendAudio(
file = file,
audioInfo = audioInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters,
)
}
override suspend fun sendFile(
file: File,
fileInfo: FileInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendFile(
file = file,
fileInfo = fileInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters,
)
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendVoiceMessage(
file = file,
audioInfo = audioInfo,
waveform = waveform,
progressCallback = progressCallback,
replyParameters = replyParameters,
)
}
override suspend fun sendLocation(
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> {
return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType)
}
override suspend fun createPoll(
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
): Result<Unit> {
return liveTimeline.createPoll(question, answers, maxSelections, pollKind)
}
override suspend fun editPoll(
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
): Result<Unit> {
return liveTimeline.editPoll(pollStartId, question, answers, maxSelections, pollKind)
}
override suspend fun sendPollResponse(
pollStartId: EventId,
answers: List<String>
): Result<Unit> {
return liveTimeline.sendPollResponse(pollStartId, answers)
}
override suspend fun endPoll(
pollStartId: EventId,
text: String
): Result<Unit> {
return liveTimeline.endPoll(pollStartId, text)
}
override suspend fun typingNotice(isTyping: Boolean) = withContext(roomDispatcher) { override suspend fun typingNotice(isTyping: Boolean) = withContext(roomDispatcher) {
runCatching { runCatching {
innerRoom.typingNotice(isTyping) innerRoom.typingNotice(isTyping)
} }
} }
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> {
return liveTimeline.toggleReaction(emoji, eventOrTransactionId)
}
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
return liveTimeline.forwardEvent(eventId, roomIds)
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> {
return liveTimeline.cancelSend(transactionId)
}
override suspend fun inviteUserById(id: UserId): Result<Unit> = withContext(roomDispatcher) { override suspend fun inviteUserById(id: UserId): Result<Unit> = withContext(roomDispatcher) {
runCatching { runCatching {
innerRoom.inviteUserById(id.value) innerRoom.inviteUserById(id.value)

View file

@ -12,17 +12,9 @@ import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SendHandle import io.element.android.libraries.matrix.api.core.SendHandle
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.CreateTimelineParams import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.IntentionalMention
@ -33,16 +25,12 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettingsStat
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
@ -53,7 +41,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import java.io.File
class FakeJoinedRoom( class FakeJoinedRoom(
val baseRoom: FakeBaseRoom = FakeBaseRoom(), val baseRoom: FakeBaseRoom = FakeBaseRoom(),
@ -63,35 +50,16 @@ class FakeJoinedRoom(
override val roomTypingMembersFlow: Flow<List<UserId>> = MutableStateFlow(emptyList()), override val roomTypingMembersFlow: Flow<List<UserId>> = MutableStateFlow(emptyList()),
override val identityStateChangesFlow: Flow<List<IdentityStateChange>> = MutableStateFlow(emptyList()), override val identityStateChangesFlow: Flow<List<IdentityStateChange>> = MutableStateFlow(emptyList()),
override val roomNotificationSettingsStateFlow: StateFlow<RoomNotificationSettingsState> = override val roomNotificationSettingsStateFlow: StateFlow<RoomNotificationSettingsState> =
MutableStateFlow(RoomNotificationSettingsState.Unknown), MutableStateFlow(RoomNotificationSettingsState.Unknown),
override val knockRequestsFlow: Flow<List<KnockRequest>> = MutableStateFlow(emptyList()), override val knockRequestsFlow: Flow<List<KnockRequest>> = MutableStateFlow(emptyList()),
private val roomNotificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), private val roomNotificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
private var createTimelineResult: (CreateTimelineParams) -> Result<Timeline> = { lambdaError() }, private var createTimelineResult: (CreateTimelineParams) -> Result<Timeline> = { lambdaError() },
private val sendMessageResult: (String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _ -> lambdaError() },
private val editMessageLambda: (EventId, String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() }, private val editMessageLambda: (EventId, String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
private val sendImageResult: (File, File?, ImageInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result<FakeMediaUploadHandler> =
{ _, _, _, _, _, _, _ -> lambdaError() },
private val sendVideoResult: (File, File?, VideoInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result<FakeMediaUploadHandler> =
{ _, _, _, _, _, _, _ -> lambdaError() },
private val sendFileResult: (File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result<FakeMediaUploadHandler> =
{ _, _, _, _, _, _ -> lambdaError() },
private val sendAudioResult: (File, AudioInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result<FakeMediaUploadHandler> =
{ _, _, _, _, _, _ -> lambdaError() },
private val sendVoiceMessageResult: (File, AudioInfo, List<Float>, ProgressCallback?, ReplyParameters?) -> Result<FakeMediaUploadHandler> =
{ _, _, _, _, _ -> lambdaError() },
private val sendLocationResult: (String, String, String?, Int?, AssetType?) -> Result<Unit> = { _, _, _, _, _ -> lambdaError() },
private val sendCallNotificationIfNeededResult: () -> Result<Unit> = { lambdaError() }, private val sendCallNotificationIfNeededResult: () -> Result<Unit> = { lambdaError() },
private val progressCallbackValues: List<Pair<Long, Long>> = emptyList(), private val progressCallbackValues: List<Pair<Long, Long>> = emptyList(),
private val createPollResult: (String, List<String>, Int, PollKind) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
private val editPollResult: (EventId, String, List<String>, Int, PollKind) -> Result<Unit> = { _, _, _, _, _ -> lambdaError() },
private val sendPollResponseResult: (EventId, List<String>) -> Result<Unit> = { _, _ -> lambdaError() },
private val endPollResult: (EventId, String) -> Result<Unit> = { _, _ -> lambdaError() },
private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result<String> = { _, _, _, _ -> lambdaError() }, private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result<String> = { _, _, _, _ -> lambdaError() },
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() }, private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() },
private val typingNoticeResult: (Boolean) -> Result<Unit> = { lambdaError() }, private val typingNoticeResult: (Boolean) -> Result<Unit> = { lambdaError() },
private val toggleReactionResult: (String, EventOrTransactionId) -> Result<Unit> = { _, _ -> lambdaError() },
private val forwardEventResult: (EventId, List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() },
private val cancelSendResult: (TransactionId) -> Result<Unit> = { lambdaError() },
private val inviteUserResult: (UserId) -> Result<Unit> = { lambdaError() }, private val inviteUserResult: (UserId) -> Result<Unit> = { lambdaError() },
private val setNameResult: (String) -> Result<Unit> = { lambdaError() }, private val setNameResult: (String) -> Result<Unit> = { lambdaError() },
private val setTopicResult: (String) -> Result<Unit> = { lambdaError() }, private val setTopicResult: (String) -> Result<Unit> = { lambdaError() },
@ -127,10 +95,6 @@ class FakeJoinedRoom(
createTimelineResult(createTimelineParams) createTimelineResult(createTimelineParams)
} }
override suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit> = simulateLongTask {
sendMessageResult(body, htmlBody, intentionalMentions)
}
override suspend fun editMessage( override suspend fun editMessage(
eventId: EventId, eventId: EventId,
body: String, body: String,
@ -140,174 +104,10 @@ class FakeJoinedRoom(
editMessageLambda(eventId, body, htmlBody, intentionalMentions) editMessageLambda(eventId, body, htmlBody, intentionalMentions)
} }
override suspend fun sendImage(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
sendImageResult(
file,
thumbnailFile,
imageInfo,
caption,
formattedCaption,
progressCallback,
replyParameters,
)
}
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
sendVideoResult(
file,
thumbnailFile,
videoInfo,
caption,
formattedCaption,
progressCallback,
replyParameters,
)
}
override suspend fun sendAudio(
file: File,
audioInfo: AudioInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
sendAudioResult(
file,
audioInfo,
caption,
formattedCaption,
progressCallback,
replyParameters,
)
}
override suspend fun sendFile(
file: File,
fileInfo: FileInfo,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
sendFileResult(
file,
fileInfo,
caption,
formattedCaption,
progressCallback,
replyParameters,
)
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
sendVoiceMessageResult(
file,
audioInfo,
waveform,
progressCallback,
replyParameters,
)
}
override suspend fun sendLocation(
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> = simulateLongTask {
return sendLocationResult(
body,
geoUri,
description,
zoomLevel,
assetType,
)
}
override suspend fun createPoll(question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind): Result<Unit> = simulateLongTask {
return createPollResult(
question,
answers,
maxSelections,
pollKind,
)
}
override suspend fun editPoll(
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind
): Result<Unit> = simulateLongTask {
return editPollResult(
pollStartId,
question,
answers,
maxSelections,
pollKind,
)
}
override suspend fun sendPollResponse(pollStartId: EventId, answers: List<String>): Result<Unit> = simulateLongTask {
return sendPollResponseResult(
pollStartId,
answers,
)
}
override suspend fun endPoll(pollStartId: EventId, text: String): Result<Unit> = simulateLongTask {
endPollResult(
pollStartId,
text,
)
}
override suspend fun typingNotice(isTyping: Boolean): Result<Unit> = simulateLongTask { override suspend fun typingNotice(isTyping: Boolean): Result<Unit> = simulateLongTask {
typingNoticeResult(isTyping) typingNoticeResult(isTyping)
} }
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> = simulateLongTask {
toggleReactionResult(emoji, eventOrTransactionId)
}
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = simulateLongTask {
forwardEventResult(eventId, roomIds)
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = simulateLongTask {
cancelSendResult(transactionId)
}
override suspend fun inviteUserById(id: UserId): Result<Unit> = simulateLongTask { override suspend fun inviteUserById(id: UserId): Result<Unit> = simulateLongTask {
inviteUserResult(id) inviteUserResult(id)
} }

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.test.timeline
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.ImageInfo
@ -26,6 +27,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -47,23 +50,31 @@ class FakeTimeline(
) )
), ),
override val membershipChangeEventReceived: Flow<Unit> = MutableSharedFlow(), override val membershipChangeEventReceived: Flow<Unit> = MutableSharedFlow(),
private val progressCallbackValues: List<Pair<Long, Long>> = emptyList(),
private val cancelSendResult: (TransactionId) -> Result<Unit> = { lambdaError() },
) : Timeline { ) : Timeline {
var sendMessageLambda: ( var sendMessageLambda: (
body: String, body: String,
htmlBody: String?, htmlBody: String?,
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
) -> Result<Unit> = { _, _, _ -> ) -> Result<Unit> = { _, _, _ ->
Result.success(Unit) lambdaError()
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = simulateLongTask {
cancelSendResult(transactionId)
} }
override suspend fun sendMessage( override suspend fun sendMessage(
body: String, body: String,
htmlBody: String?, htmlBody: String?,
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
): Result<Unit> = sendMessageLambda(body, htmlBody, intentionalMentions) ): Result<Unit> = simulateLongTask {
sendMessageLambda(body, htmlBody, intentionalMentions)
}
var redactEventLambda: (eventOrTransactionId: EventOrTransactionId, reason: String?) -> Result<Unit> = { _, _ -> var redactEventLambda: (eventOrTransactionId: EventOrTransactionId, reason: String?) -> Result<Unit> = { _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun redactEvent( override suspend fun redactEvent(
@ -77,7 +88,7 @@ class FakeTimeline(
htmlBody: String?, htmlBody: String?,
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
) -> Result<Unit> = { _, _, _, _ -> ) -> Result<Unit> = { _, _, _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun editMessage( override suspend fun editMessage(
@ -117,7 +128,7 @@ class FakeTimeline(
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
fromNotification: Boolean, fromNotification: Boolean,
) -> Result<Unit> = { _, _, _, _, _ -> ) -> Result<Unit> = { _, _, _, _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun replyMessage( override suspend fun replyMessage(
@ -154,15 +165,18 @@ class FakeTimeline(
formattedCaption: String?, formattedCaption: String?,
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = sendImageLambda( ): Result<MediaUploadHandler> = simulateLongTask {
file, simulateSendMediaProgress(progressCallback)
thumbnailFile, sendImageLambda(
imageInfo, file,
caption, thumbnailFile,
formattedCaption, imageInfo,
progressCallback, caption,
replyParameters, formattedCaption,
) progressCallback,
replyParameters,
)
}
var sendVideoLambda: ( var sendVideoLambda: (
file: File, file: File,
@ -184,15 +198,18 @@ class FakeTimeline(
formattedCaption: String?, formattedCaption: String?,
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = sendVideoLambda( ): Result<MediaUploadHandler> = simulateLongTask {
file, simulateSendMediaProgress(progressCallback)
thumbnailFile, sendVideoLambda(
videoInfo, file,
caption, thumbnailFile,
formattedCaption, videoInfo,
progressCallback, caption,
replyParameters, formattedCaption,
) progressCallback,
replyParameters,
)
}
var sendAudioLambda: ( var sendAudioLambda: (
file: File, file: File,
@ -212,14 +229,17 @@ class FakeTimeline(
formattedCaption: String?, formattedCaption: String?,
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = sendAudioLambda( ): Result<MediaUploadHandler> = simulateLongTask {
file, simulateSendMediaProgress(progressCallback)
audioInfo, sendAudioLambda(
caption, file,
formattedCaption, audioInfo,
progressCallback, caption,
replyParameters, formattedCaption,
) progressCallback,
replyParameters,
)
}
var sendFileLambda: ( var sendFileLambda: (
file: File, file: File,
@ -239,14 +259,17 @@ class FakeTimeline(
formattedCaption: String?, formattedCaption: String?,
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = sendFileLambda( ): Result<MediaUploadHandler> = simulateLongTask {
file, simulateSendMediaProgress(progressCallback)
fileInfo, sendFileLambda(
caption, file,
formattedCaption, fileInfo,
progressCallback, caption,
replyParameters, formattedCaption,
) progressCallback,
replyParameters,
)
}
var sendVoiceMessageLambda: ( var sendVoiceMessageLambda: (
file: File, file: File,
@ -264,13 +287,16 @@ class FakeTimeline(
waveform: List<Float>, waveform: List<Float>,
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> = sendVoiceMessageLambda( ): Result<MediaUploadHandler> = simulateLongTask {
file, simulateSendMediaProgress(progressCallback)
audioInfo, sendVoiceMessageLambda(
waveform, file,
progressCallback, audioInfo,
replyParameters, waveform,
) progressCallback,
replyParameters,
)
}
var sendLocationLambda: ( var sendLocationLambda: (
body: String, body: String,
@ -279,7 +305,7 @@ class FakeTimeline(
zoomLevel: Int?, zoomLevel: Int?,
assetType: AssetType?, assetType: AssetType?,
) -> Result<Unit> = { _, _, _, _, _ -> ) -> Result<Unit> = { _, _, _, _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun sendLocation( override suspend fun sendLocation(
@ -288,24 +314,30 @@ class FakeTimeline(
description: String?, description: String?,
zoomLevel: Int?, zoomLevel: Int?,
assetType: AssetType?, assetType: AssetType?,
): Result<Unit> = sendLocationLambda( ): Result<Unit> = simulateLongTask {
body, sendLocationLambda(
geoUri, body,
description, geoUri,
zoomLevel, description,
assetType zoomLevel,
) assetType,
)
}
var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result<Unit> = { _, _ -> Result.success(Unit) } var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result<Unit> = { _, _ -> lambdaError() }
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> = toggleReactionLambda( override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> = simulateLongTask {
emoji, toggleReactionLambda(
eventOrTransactionId emoji,
) eventOrTransactionId,
)
}
var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> Result.success(Unit) } var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() }
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = forwardEventLambda(eventId, roomIds) override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = simulateLongTask {
forwardEventLambda(eventId, roomIds)
}
var createPollLambda: ( var createPollLambda: (
question: String, question: String,
@ -313,20 +345,17 @@ class FakeTimeline(
maxSelections: Int, maxSelections: Int,
pollKind: PollKind, pollKind: PollKind,
) -> Result<Unit> = { _, _, _, _ -> ) -> Result<Unit> = { _, _, _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun createPoll( override suspend fun createPoll(question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind): Result<Unit> = simulateLongTask {
question: String, createPollLambda(
answers: List<String>, question,
maxSelections: Int, answers,
pollKind: PollKind, maxSelections,
): Result<Unit> = createPollLambda( pollKind,
question, )
answers, }
maxSelections,
pollKind
)
var editPollLambda: ( var editPollLambda: (
pollStartId: EventId, pollStartId: EventId,
@ -335,7 +364,7 @@ class FakeTimeline(
maxSelections: Int, maxSelections: Int,
pollKind: PollKind, pollKind: PollKind,
) -> Result<Unit> = { _, _, _, _, _ -> ) -> Result<Unit> = { _, _, _, _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun editPoll( override suspend fun editPoll(
@ -343,44 +372,56 @@ class FakeTimeline(
question: String, question: String,
answers: List<String>, answers: List<String>,
maxSelections: Int, maxSelections: Int,
pollKind: PollKind, pollKind: PollKind
): Result<Unit> = editPollLambda( ): Result<Unit> = simulateLongTask {
pollStartId, editPollLambda(
question, pollStartId,
answers, question,
maxSelections, answers,
pollKind maxSelections,
) pollKind,
)
}
var sendPollResponseLambda: ( var sendPollResponseLambda: (
pollStartId: EventId, pollStartId: EventId,
answers: List<String>, answers: List<String>,
) -> Result<Unit> = { _, _ -> ) -> Result<Unit> = { _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun sendPollResponse( override suspend fun sendPollResponse(
pollStartId: EventId, pollStartId: EventId,
answers: List<String>, answers: List<String>,
): Result<Unit> = sendPollResponseLambda(pollStartId, answers) ): Result<Unit> = simulateLongTask {
sendPollResponseLambda(
pollStartId,
answers,
)
}
var endPollLambda: ( var endPollLambda: (
pollStartId: EventId, pollStartId: EventId,
text: String, text: String,
) -> Result<Unit> = { _, _ -> ) -> Result<Unit> = { _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun endPoll( override suspend fun endPoll(
pollStartId: EventId, pollStartId: EventId,
text: String, text: String,
): Result<Unit> = endPollLambda(pollStartId, text) ): Result<Unit> = simulateLongTask {
endPollLambda(
pollStartId,
text,
)
}
var sendReadReceiptLambda: ( var sendReadReceiptLambda: (
eventId: EventId, eventId: EventId,
receiptType: ReceiptType, receiptType: ReceiptType,
) -> Result<Unit> = { _, _ -> ) -> Result<Unit> = { _, _ ->
Result.success(Unit) lambdaError()
} }
override suspend fun sendReadReceipt( override suspend fun sendReadReceipt(
@ -417,5 +458,12 @@ class FakeTimeline(
closeCounter++ closeCounter++
} }
private suspend fun simulateSendMediaProgress(progressCallback: ProgressCallback?) {
progressCallbackValues.forEach { (current, total) ->
progressCallback?.onProgress(current, total)
delay(1)
}
}
override fun toString() = "FakeTimeline: $name" override fun toString() = "FakeTimeline: $name"
} }

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -49,7 +50,7 @@ class MediaSender @Inject constructor(
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
): Result<Unit> { ): Result<Unit> {
return room.sendMedia( return room.liveTimeline.sendMedia(
uploadInfo = mediaUploadInfo, uploadInfo = mediaUploadInfo,
progressCallback = progressCallback, progressCallback = progressCallback,
caption = caption, caption = caption,
@ -76,7 +77,7 @@ class MediaSender @Inject constructor(
compressIfPossible = compressIfPossible, compressIfPossible = compressIfPossible,
) )
.flatMapCatching { info -> .flatMapCatching { info ->
room.sendMedia( room.liveTimeline.sendMedia(
uploadInfo = info, uploadInfo = info,
progressCallback = progressCallback, progressCallback = progressCallback,
caption = caption, caption = caption,
@ -108,7 +109,7 @@ class MediaSender @Inject constructor(
audioInfo = audioInfo, audioInfo = audioInfo,
waveform = waveForm, waveform = waveForm,
) )
room.sendMedia( room.liveTimeline.sendMedia(
uploadInfo = newInfo, uploadInfo = newInfo,
progressCallback = progressCallback, progressCallback = progressCallback,
caption = null, caption = null,
@ -130,7 +131,7 @@ class MediaSender @Inject constructor(
ongoingUploadJobs.remove(Job) ongoingUploadJobs.remove(Job)
} }
private suspend fun JoinedRoom.sendMedia( private suspend fun Timeline.sendMedia(
uploadInfo: MediaUploadInfo, uploadInfo: MediaUploadInfo,
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
caption: String?, caption: String?,

View file

@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.message.ReplyParameters 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.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
@ -51,7 +52,9 @@ class MediaSenderTest {
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendImageResult = sendImageResult liveTimeline = FakeTimeline().apply {
sendImageLambda = sendImageResult
},
) )
val sender = createMediaSender(room = room) val sender = createMediaSender(room = room)
@ -74,14 +77,22 @@ class MediaSenderTest {
@Test @Test
fun `given a failure in the media upload when sending the whole process fails`() = runTest { fun `given a failure in the media upload when sending the whole process fails`() = runTest {
val preProcessor = FakeMediaPreProcessor().apply {
givenImageResult()
}
val sendImageResult = val sendImageResult =
lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? -> lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? ->
Result.failure<FakeMediaUploadHandler>(Exception()) Result.failure<FakeMediaUploadHandler>(Exception())
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendImageResult = sendImageResult liveTimeline = FakeTimeline().apply {
sendImageLambda = sendImageResult
},
)
val sender = createMediaSender(
preProcessor = preProcessor,
room = room,
) )
val sender = createMediaSender(room = room)
val uri = Uri.parse("content://image.jpg") val uri = Uri.parse("content://image.jpg")
val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg)
@ -94,10 +105,12 @@ class MediaSenderTest {
fun `given a cancellation in the media upload when sending the job is cancelled`() = runTest(StandardTestDispatcher()) { fun `given a cancellation in the media upload when sending the job is cancelled`() = runTest(StandardTestDispatcher()) {
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler()) Result.success(FakeMediaUploadHandler())
} }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
sendFileResult = sendFileResult liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
) )
val sender = createMediaSender(room = room) val sender = createMediaSender(room = room)
val sendJob = launch { val sendJob = launch {

View file

@ -366,7 +366,7 @@ class NotificationBroadcastReceiverHandlerTest {
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
), ),
) )
runCurrent() advanceUntilIdle()
sendMessage.assertions() sendMessage.assertions()
.isCalledOnce() .isCalledOnce()
.with(value(A_MESSAGE), value(null), value(emptyList<IntentionalMention>())) .with(value(A_MESSAGE), value(null), value(emptyList<IntentionalMention>()))