diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt index 1edac5af9f..1da4723f8b 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt @@ -22,6 +22,8 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId +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.FakeMatrixRoom import io.element.android.libraries.textcomposer.model.MessageComposerMode @@ -397,8 +399,7 @@ class SendLocationPresenterTest { ) fakeMessageComposerContext.apply { composerMode = MessageComposerMode.Edit( - eventId = null, - transactionId = null, + eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = "" ) } @@ -446,8 +447,7 @@ class SendLocationPresenterTest { ) fakeMessageComposerContext.apply { composerMode = MessageComposerMode.Edit( - eventId = null, - transactionId = null, + eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = "" ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt index 943040ce49..c8699524bd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt @@ -9,11 +9,11 @@ package io.element.android.features.messages.impl import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId sealed interface MessagesEvents { data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents - data class ToggleReaction(val emoji: String, val uniqueId: UniqueId) : MessagesEvents + data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvents data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents data object Dismiss : MessagesEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index a42a09fcd6..b215fc49fd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -62,7 +62,6 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo @@ -73,6 +72,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.room.canCall @@ -191,7 +191,7 @@ class MessagesPresenter @AssistedInject constructor( ) } is MessagesEvents.ToggleReaction -> { - localCoroutineScope.toggleReaction(event.emoji, event.uniqueId) + localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId) } is MessagesEvents.InviteDialogDismissed -> { hasDismissedInviteDialog = true @@ -327,10 +327,10 @@ class MessagesPresenter @AssistedInject constructor( private fun CoroutineScope.toggleReaction( emoji: String, - uniqueId: UniqueId, + eventOrTransactionId: EventOrTransactionId, ) = launch(dispatchers.io) { timelineController.invokeOnCurrentTimeline { - toggleReaction(emoji, uniqueId) + toggleReaction(emoji, eventOrTransactionId) .onFailure { Timber.e(it) } } } @@ -360,7 +360,7 @@ class MessagesPresenter @AssistedInject constructor( private suspend fun handleActionRedact(event: TimelineItem.Event) { timelineController.invokeOnCurrentTimeline { - redactEvent(eventId = event.eventId, transactionId = event.transactionId, reason = null) + redactEvent(eventOrTransactionId = event.eventOrTransactionId, reason = null) .onFailure { Timber.e(it) } } } @@ -377,8 +377,7 @@ class MessagesPresenter @AssistedInject constructor( } else -> { val composerMode = MessageComposerMode.Edit( - targetEvent.eventId, - targetEvent.transactionId, + targetEvent.eventOrTransactionId, (targetEvent.content as? TimelineItemTextBasedContent)?.let { if (enableTextFormatting) { it.htmlBody ?: it.body diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index e56d37005b..bd8ae98b2f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -168,7 +168,7 @@ fun MessagesView( } fun onEmojiReactionClick(emoji: String, event: TimelineItem.Event) { - state.eventSink(MessagesEvents.ToggleReaction(emoji, event.id)) + state.eventSink(MessagesEvents.ToggleReaction(emoji, event.eventOrTransactionId)) } fun onEmojiReactionLongClick(emoji: String, event: TimelineItem.Event) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index d0daa9bcb9..648be160d1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -53,6 +53,7 @@ import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.timeline.TimelineException +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.map @@ -442,12 +443,11 @@ class MessageComposerPresenter @Inject constructor( intentionalMentions = message.intentionalMentions ) is MessageComposerMode.Edit -> { - val eventId = capturedMode.eventId - val transactionId = capturedMode.transactionId timelineController.invokeOnCurrentTimeline { // First try to edit the message in the current timeline - editMessage(eventId, transactionId, message.markdown, message.html, message.intentionalMentions) + editMessage(capturedMode.eventOrTransactionId, message.markdown, message.html, message.intentionalMentions) .onFailure { cause -> + val eventId = capturedMode.eventOrTransactionId.eventId if (cause is TimelineException.EventNotFound && eventId != null) { // if the event is not found in the timeline, try to edit the message directly room.editMessage(eventId, message.markdown, message.html, message.intentionalMentions) @@ -581,8 +581,7 @@ class MessageComposerPresenter @Inject constructor( when (val draftType = draft.draftType) { ComposerDraftType.NewMessage -> messageComposerContext.composerMode = MessageComposerMode.Normal is ComposerDraftType.Edit -> messageComposerContext.composerMode = MessageComposerMode.Edit( - eventId = draftType.eventId, - transactionId = null, + eventOrTransactionId = draftType.eventId.toEventOrTransactionId(), content = htmlText ?: markdownText ) is ComposerDraftType.Reply -> { @@ -611,7 +610,7 @@ class MessageComposerPresenter @Inject constructor( val draftType = when (val mode = messageComposerContext.composerMode) { is MessageComposerMode.Normal -> ComposerDraftType.NewMessage is MessageComposerMode.Edit -> { - mode.eventId?.let { eventId -> ComposerDraftType.Edit(eventId) } + mode.eventOrTransactionId.eventId?.let { eventId -> ComposerDraftType.Edit(eventId) } } is MessageComposerMode.Reply -> ComposerDraftType.Reply(mode.eventId) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt index 751cfdd050..c1478ca8a6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt @@ -16,13 +16,13 @@ import androidx.compose.ui.Modifier import io.element.android.emojibasebindings.Emoji import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.hide -import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId @OptIn(ExperimentalMaterial3Api::class) @Composable fun CustomReactionBottomSheet( state: CustomReactionState, - onSelectEmoji: (UniqueId, Emoji) -> Unit, + onSelectEmoji: (EventOrTransactionId, Emoji) -> Unit, modifier: Modifier = Modifier, ) { val sheetState = rememberModalBottomSheetState() @@ -37,7 +37,7 @@ fun CustomReactionBottomSheet( if (target?.event == null) return sheetState.hide(coroutineScope) { state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) - onSelectEmoji(target.event.id, emoji) + onSelectEmoji(target.event.eventOrTransactionId, emoji) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index d13fd992bf..dcf0e16aa8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.MessageShieldProvider @@ -92,6 +93,9 @@ sealed interface TimelineItem { val isRemote = eventId != null + val eventOrTransactionId: EventOrTransactionId + get() = EventOrTransactionId.from(eventId = eventId, transactionId = transactionId) + // No need to be lazy here? val messageShield: MessageShield? = messageShieldProvider(strict = false) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 9fb83f6471..7664379207 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -47,8 +47,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.permalink.PermalinkParser @@ -56,13 +54,14 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.A_THROWABLE -import io.element.android.libraries.matrix.test.A_UNIQUE_ID import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -164,8 +163,9 @@ class MessagesPresenterTest { @Test fun `present - handle toggling a reaction`() = runTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) - val toggleReactionSuccess = lambdaRecorder { _: String, _: UniqueId -> Result.success(Unit) } - val toggleReactionFailure = lambdaRecorder { _: String, _: UniqueId -> Result.failure(IllegalStateException("Failed to send reaction")) } + val toggleReactionSuccess = lambdaRecorder { _: String, _: EventOrTransactionId -> Result.success(Unit) } + val toggleReactionFailure = + lambdaRecorder { _: String, _: EventOrTransactionId -> Result.failure(IllegalStateException("Failed to send reaction")) } val timeline = FakeTimeline().apply { this.toggleReactionLambda = toggleReactionSuccess @@ -185,23 +185,23 @@ class MessagesPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID)) + initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) assert(toggleReactionSuccess) .isCalledOnce() - .with(value("👍"), value(A_UNIQUE_ID)) + .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) // No crashes when sending a reaction failed timeline.apply { toggleReactionLambda = toggleReactionFailure } - initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID)) + initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) assert(toggleReactionFailure) .isCalledOnce() - .with(value("👍"), value(A_UNIQUE_ID)) + .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) } } @Test fun `present - handle toggling a reaction twice`() = runTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) - val toggleReactionSuccess = lambdaRecorder { _: String, _: UniqueId -> Result.success(Unit) } + val toggleReactionSuccess = lambdaRecorder { _: String, _: EventOrTransactionId -> Result.success(Unit) } val timeline = FakeTimeline().apply { this.toggleReactionLambda = toggleReactionSuccess @@ -220,13 +220,13 @@ class MessagesPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID)) - initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID)) + initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) + initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) assert(toggleReactionSuccess) .isCalledExactly(2) .withSequence( - listOf(value("👍"), value(A_UNIQUE_ID)), - listOf(value("👍"), value(A_UNIQUE_ID)), + listOf(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())), + listOf(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())), ) skipItems(1) } @@ -452,8 +452,7 @@ class MessagesPresenterTest { composerRecorder.assertSingle( MessageComposerEvents.SetMode( composerMode = MessageComposerMode.Edit( - eventId = AN_EVENT_ID, - transactionId = null, + eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = (aMessageEvent().content as TimelineItemTextContent).body ) ) @@ -506,7 +505,7 @@ class MessagesPresenterTest { canUserPinUnpinResult = { Result.success(true) }, ) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } liveTimeline.redactEventLambda = redactEventLambda val presenter = createMessagesPresenter( matrixRoom = matrixRoom, @@ -521,7 +520,7 @@ class MessagesPresenterTest { awaitItem() assert(redactEventLambda) .isCalledOnce() - .with(value(messageEvent.eventId), value(messageEvent.transactionId), value(null)) + .with(value(messageEvent.eventOrTransactionId), value(null)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 210ffb4fe6..a20acaa5df 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -355,7 +355,7 @@ class MessagesViewTest { state = state, ) rule.onAllNodesWithText("👍️").onFirst().performClick() - eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.id)) + eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.eventOrTransactionId)) } @Test @@ -484,7 +484,7 @@ class MessagesViewTest { // Give time for the close animation to complete rule.mainClock.advanceTimeBy(milliseconds = 1_000) customReactionStateEventsRecorder.assertSingle(CustomReactionEvents.DismissCustomReactionSheet) - eventsRecorder.assertSingle(MessagesEvents.ToggleReaction(aUnicode, timelineItem.id)) + eventsRecorder.assertSingle(MessagesEvents.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId)) } @Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index df0cc1bdc2..c66a2b56ad 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -32,7 +32,6 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService 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.RoomId -import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder @@ -44,7 +43,9 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.timeline.TimelineException +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_MESSAGE @@ -355,7 +356,7 @@ class MessageComposerPresenterTest { @Test fun `present - edit sent message`() = runTest { - val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List -> + val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List -> Result.success(Unit) } val timeline = FakeTimeline().apply { @@ -392,7 +393,7 @@ class MessageComposerPresenterTest { assert(editMessageLambda) .isCalledOnce() - .with(value(AN_EVENT_ID), value(null), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) + .with(value(AN_EVENT_ID.toEventOrTransactionId()), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) assertThat(analyticsService.capturedEvents).containsExactly( Composer( @@ -407,7 +408,7 @@ class MessageComposerPresenterTest { @Test fun `present - edit sent message event not found`() = runTest { - val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List -> + val timelineEditMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List -> Result.failure(TimelineException.EventNotFound) } val timeline = FakeTimeline().apply { @@ -448,7 +449,7 @@ class MessageComposerPresenterTest { assert(timelineEditMessageLambda) .isCalledOnce() - .with(value(AN_EVENT_ID), value(null), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) + .with(value(AN_EVENT_ID.toEventOrTransactionId()), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) assert(roomEditMessageLambda) .isCalledOnce() @@ -467,7 +468,7 @@ class MessageComposerPresenterTest { @Test fun `present - edit not sent message`() = runTest { - val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List -> + val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List -> Result.success(Unit) } val timeline = FakeTimeline().apply { @@ -487,7 +488,7 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") - val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID) + val mode = anEditMode(eventOrTransactionId = A_TRANSACTION_ID.toEventOrTransactionId()) initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) @@ -504,7 +505,7 @@ class MessageComposerPresenterTest { assert(editMessageLambda) .isCalledOnce() - .with(value(null), value(A_TRANSACTION_ID), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) + .with(value(A_TRANSACTION_ID.toEventOrTransactionId()), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) assertThat(analyticsService.capturedEvents).containsExactly( Composer( @@ -1058,7 +1059,7 @@ class MessageComposerPresenterTest { val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List, _: Boolean -> Result.success(Unit) } - val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List -> + val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List -> Result.success(Unit) } val timeline = FakeTimeline().apply { @@ -1128,7 +1129,7 @@ class MessageComposerPresenterTest { assert(editMessageLambda) .isCalledOnce() - .with(any(), any(), any(), any(), value(listOf(IntentionalMention.User(A_USER_ID_3)))) + .with(any(), any(), any(), value(listOf(IntentionalMention.User(A_USER_ID_3)))) skipItems(1) } @@ -1516,10 +1517,9 @@ class MessageComposerPresenterTest { } fun anEditMode( - eventId: EventId? = AN_EVENT_ID, + eventOrTransactionId: EventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), message: String = A_MESSAGE, - transactionId: TransactionId? = null, -) = MessageComposerMode.Edit(eventId, transactionId, message) +) = MessageComposerMode.Edit(eventOrTransactionId, message) fun aReplyMode() = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt index 839adc492f..d40ccfa680 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.TimelineProvider import io.element.android.libraries.matrix.api.timeline.getActiveTimeline import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import kotlinx.coroutines.flow.first import javax.inject.Inject @@ -63,8 +64,7 @@ class PollRepository @Inject constructor( timelineProvider .getActiveTimeline() .redactEvent( - eventId = pollStartId, - transactionId = null, + eventOrTransactionId = pollStartId.toEventOrTransactionId(), reason = null, ) } diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index d3abb3d10d..f25707694a 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -20,10 +20,11 @@ import io.element.android.features.poll.impl.aPollTimelineItems import io.element.android.features.poll.impl.anOngoingPollContent import io.element.android.features.poll.impl.data.PollRepository import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline @@ -466,7 +467,7 @@ class CreatePollPresenterTest { @Test fun `delete confirms`() = runTest { val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } timeline.redactEventLambda = redactEventLambda moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -481,7 +482,7 @@ class CreatePollPresenterTest { @Test fun `delete can be cancelled`() = runTest { val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } timeline.redactEventLambda = redactEventLambda moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -499,7 +500,7 @@ class CreatePollPresenterTest { @Test fun `delete can be confirmed`() = runTest { val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } timeline.redactEventLambda = redactEventLambda moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -512,7 +513,7 @@ class CreatePollPresenterTest { } assert(redactEventLambda) .isCalledOnce() - .with(value(pollEventId), value(null), any()) + .with(value(pollEventId.toEventOrTransactionId()), any()) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aced112ac8..4f6dd3bc93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -168,7 +168,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.54" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.55" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index b8d6d66043..fcc1fd1812 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -14,7 +14,6 @@ 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.SessionId import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.core.UniqueId 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.media.AudioInfo @@ -29,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerL import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.timeline.ReceiptType 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.MatrixWidgetSettings import kotlinx.coroutines.flow.Flow @@ -150,7 +150,7 @@ interface MatrixRoom : Closeable { suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result - suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result + suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result suspend fun forwardEvent(eventId: EventId, roomIds: List): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index 70d5a5ab57..085c4d49ea 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -11,7 +11,6 @@ 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.RoomId import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.core.UniqueId 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 @@ -20,7 +19,9 @@ 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.IntentionalMention import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import java.io.File @@ -57,8 +58,7 @@ interface Timeline : AutoCloseable { ): Result suspend fun editMessage( - originalEventId: EventId?, - transactionId: TransactionId?, + eventOrTransactionId: EventOrTransactionId, body: String, htmlBody: String?, intentionalMentions: List, ): Result @@ -89,17 +89,18 @@ interface Timeline : AutoCloseable { progressCallback: ProgressCallback? ): Result - suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result + suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result - suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result + suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result suspend fun forwardEvent(eventId: EventId, roomIds: List): Result - suspend fun cancelSend(transactionId: TransactionId): Result + suspend fun cancelSend(transactionId: TransactionId): Result = + redactEvent(transactionId.toEventOrTransactionId(), reason = null) /** * Share a location message in the room. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt new file mode 100644 index 0000000000..42f9c9198e --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.timeline.item.event + +import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.TransactionId + +@Immutable +sealed interface EventOrTransactionId { + @JvmInline + value class Event(val id: EventId) : EventOrTransactionId + + @JvmInline + value class Transaction(val id: TransactionId) : EventOrTransactionId + + val eventId: EventId? + get() = (this as? Event)?.id + + companion object { + fun from(eventId: EventId?, transactionId: TransactionId?): EventOrTransactionId { + return when { + eventId != null -> Event(eventId) + transactionId != null -> Transaction(transactionId) + else -> throw IllegalArgumentException("EventId and TransactionId are both null") + } + } + } +} + +fun EventId.toEventOrTransactionId() = EventOrTransactionId.Event(this) +fun TransactionId.toEventOrTransactionId() = EventOrTransactionId.Transaction(this) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 7bc95471c3..31d8ae1f43 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -17,7 +17,6 @@ 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.SessionId import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.core.UniqueId 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.media.AudioInfo @@ -42,6 +41,7 @@ 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.timeline.ReceiptType 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.MatrixWidgetSettings import io.element.android.libraries.matrix.impl.mapper.map @@ -471,8 +471,8 @@ class RustMatrixRoom( return liveTimeline.sendFile(file, fileInfo, progressCallback) } - override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result { - return liveTimeline.toggleReaction(emoji, uniqueId) + override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result { + return liveTimeline.toggleReaction(emoji, eventOrTransactionId) } override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt new file mode 100644 index 0000000000..85684faed0 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.timeline + +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId +import org.matrix.rustcomponents.sdk.EventOrTransactionId as RustEventOrTransactionId + +fun EventOrTransactionId.toRustEventOrTransactionId() = when (this) { + is EventOrTransactionId.Event -> RustEventOrTransactionId.EventId(id.value) + is EventOrTransactionId.Transaction -> RustEventOrTransactionId.TransactionId(id.value) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt index 4096b87ba4..97cfc27457 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -23,7 +23,7 @@ class MatrixTimelineItemMapper( private val eventTimelineItemMapper: EventTimelineItemMapper, ) { fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use { - val uniqueId = UniqueId(timelineItem.uniqueId()) + val uniqueId = UniqueId(timelineItem.uniqueId().id) val asEvent = it.asEvent() if (asEvent != null) { val eventTimelineItem = eventTimelineItemMapper.map(asEvent) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 517d83ff80..56c9cf4d85 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -10,8 +10,6 @@ package io.element.android.libraries.matrix.impl.timeline 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.RoomId -import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.core.UniqueId 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 @@ -26,6 +24,7 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.TimelineException +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.impl.core.toProgressWatcher import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl @@ -65,8 +64,6 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.EditedContent -import org.matrix.rustcomponents.sdk.EventOrTransactionId -import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageFormat import org.matrix.rustcomponents.sdk.PollData @@ -75,6 +72,7 @@ import org.matrix.rustcomponents.sdk.use import timber.log.Timber import uniffi.matrix_sdk_ui.LiveBackPaginationStatus import java.io.File +import org.matrix.rustcomponents.sdk.EventOrTransactionId as RustEventOrTransactionId import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline private const val PAGINATION_SIZE = 50 @@ -280,31 +278,23 @@ class RustTimeline( } } - override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result = withContext(dispatcher) { + override suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result = withContext(dispatcher) { runCatching { - val eventOrTransactionId = if (eventId != null) { - EventOrTransactionId.EventId(eventId.value) - } else { - EventOrTransactionId.TransactionId(transactionId!!.value) - } - inner.redactEvent(eventOrTransactionId = eventOrTransactionId, reason = reason) + inner.redactEvent( + eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(), + reason = reason, + ) } } override suspend fun editMessage( - originalEventId: EventId?, - transactionId: TransactionId?, + eventOrTransactionId: EventOrTransactionId, body: String, htmlBody: String?, intentionalMentions: List, ): Result = withContext(dispatcher) { runCatching { - val eventOrTransactionId = if (originalEventId != null) { - EventOrTransactionId.EventId(originalEventId.value) - } else { - EventOrTransactionId.TransactionId(transactionId!!.value) - } val editedContent = EditedContent.RoomMessage( content = MessageEventContent.from( body = body, @@ -314,7 +304,7 @@ class RustTimeline( ) inner.edit( newContent = editedContent, - eventOrTransactionId = eventOrTransactionId, + eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(), ) } } @@ -354,21 +344,6 @@ class RustTimeline( } } - @Throws - @Suppress("UnusedPrivateMember") - private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem { - return try { - when { - eventId != null -> inner.getEventTimelineItemByEventId(eventId.value) - transactionId != null -> inner.getEventTimelineItemByTransactionId(transactionId.value) - else -> error("Either eventId or transactionId must be non-null") - } - } catch (e: Exception) { - Timber.e(e, "Failed to get event timeline item") - throw TimelineException.EventNotFound - } - } - override suspend fun sendVideo( file: File, thumbnailFile: File?, @@ -410,9 +385,12 @@ class RustTimeline( } } - override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result = withContext(dispatcher) { + override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result = withContext(dispatcher) { runCatching { - inner.toggleReaction(key = emoji, uniqueId = uniqueId.value) + inner.toggleReaction( + key = emoji, + itemId = eventOrTransactionId.toRustEventOrTransactionId(), + ) } } @@ -424,9 +402,6 @@ class RustTimeline( } } - override suspend fun cancelSend(transactionId: TransactionId): Result = - redactEvent(eventId = null, transactionId = transactionId, reason = null) - override suspend fun sendLocation( body: String, geoUri: String, @@ -479,7 +454,7 @@ class RustTimeline( ) inner.edit( newContent = editedContent, - eventOrTransactionId = EventOrTransactionId.EventId(pollStartId.value), + eventOrTransactionId = RustEventOrTransactionId.EventId(pollStartId.value), ) }.map { } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItem.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItem.kt index f6d46799c3..4ce2c2e063 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItem.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItem.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.TimelineItem +import org.matrix.rustcomponents.sdk.TimelineUniqueId import org.matrix.rustcomponents.sdk.VirtualTimelineItem class FakeRustTimelineItem( @@ -18,5 +19,5 @@ class FakeRustTimelineItem( override fun asEvent(): EventTimelineItem? = asEventResult override fun asVirtual(): VirtualTimelineItem? = null override fun fmtDebug(): String = "fmtDebug" - override fun uniqueId(): String = "uniqueId" + override fun uniqueId(): TimelineUniqueId = TimelineUniqueId("uniqueId") } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 2190f89d13..b46b45f20e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -14,7 +14,6 @@ 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.SessionId import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.core.UniqueId 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.media.AudioInfo @@ -38,6 +37,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerL import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.timeline.ReceiptType 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.MatrixWidgetSettings import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -95,7 +95,7 @@ class FakeMatrixRoom( private val editMessageLambda: (EventId, String, String?, List) -> Result = { _, _, _, _ -> lambdaError() }, private val sendMessageResult: (String, String?, List) -> Result = { _, _, _ -> lambdaError() }, private val updateUserRoleResult: () -> Result = { lambdaError() }, - private val toggleReactionResult: (String, UniqueId) -> Result = { _, _ -> lambdaError() }, + private val toggleReactionResult: (String, EventOrTransactionId) -> Result = { _, _ -> lambdaError() }, private val retrySendMessageResult: (TransactionId) -> Result = { lambdaError() }, private val cancelSendResult: (TransactionId) -> Result = { lambdaError() }, private val forwardEventResult: (EventId, List) -> Result = { _, _ -> lambdaError() }, @@ -236,8 +236,8 @@ class FakeMatrixRoom( sendMessageResult(body, htmlBody, intentionalMentions) } - override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result { - return toggleReactionResult(emoji, uniqueId) + override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result { + return toggleReactionResult(emoji, eventOrTransactionId) } override suspend fun retrySendMessage(transactionId: TransactionId): Result = simulateLongTask { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt index d2a99df97b..0395bc2328 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt @@ -10,8 +10,6 @@ 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.ProgressCallback 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.core.UniqueId 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 @@ -23,6 +21,7 @@ import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.ReceiptType 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.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.tests.testutils.lambda.lambdaError @@ -63,35 +62,31 @@ class FakeTimeline( intentionalMentions: List, ): Result = sendMessageLambda(body, htmlBody, intentionalMentions) - var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result = { _, _, _ -> + var redactEventLambda: (eventOrTransactionId: EventOrTransactionId, reason: String?) -> Result = { _, _ -> Result.success(Unit) } override suspend fun redactEvent( - eventId: EventId?, - transactionId: TransactionId?, + eventOrTransactionId: EventOrTransactionId, reason: String? - ): Result = redactEventLambda(eventId, transactionId, reason) + ): Result = redactEventLambda(eventOrTransactionId, reason) var editMessageLambda: ( - originalEventId: EventId?, - transactionId: TransactionId?, + eventOrTransactionId: EventOrTransactionId, body: String, htmlBody: String?, intentionalMentions: List, - ) -> Result = { _, _, _, _, _ -> + ) -> Result = { _, _, _, _ -> Result.success(Unit) } override suspend fun editMessage( - originalEventId: EventId?, - transactionId: TransactionId?, + eventOrTransactionId: EventOrTransactionId, body: String, htmlBody: String?, intentionalMentions: List, ): Result = editMessageLambda( - originalEventId, - transactionId, + eventOrTransactionId, body, htmlBody, intentionalMentions @@ -211,14 +206,15 @@ class FakeTimeline( progressCallback ) - var toggleReactionLambda: (emoji: String, uniqueId: UniqueId) -> Result = { _, _ -> Result.success(Unit) } - override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result = toggleReactionLambda(emoji, uniqueId) + var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result = { _, _ -> Result.success(Unit) } + override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result = toggleReactionLambda( + emoji, + eventOrTransactionId + ) var forwardEventLambda: (eventId: EventId, roomIds: List) -> Result = { _, _ -> Result.success(Unit) } override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = forwardEventLambda(eventId, roomIds) - override suspend fun cancelSend(transactionId: TransactionId): Result = redactEvent(null, transactionId, null) - var sendLocationLambda: ( body: String, geoUri: String, diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 57cc810a7c..f7b19f9d9a 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -42,7 +42,8 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.TransactionId +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider import io.element.android.libraries.testtags.TestTags @@ -719,12 +720,10 @@ fun aRichTextEditorState( ) fun aMessageComposerModeEdit( - eventId: EventId? = EventId("$1234"), - transactionId: TransactionId? = TransactionId("1234"), + eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(), content: String = "Some text", ) = MessageComposerMode.Edit( - eventId = eventId, - transactionId = transactionId, + eventOrTransactionId = eventOrTransactionId, content = content ) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt index 9c3d47453a..7f1efe55d2 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt @@ -26,6 +26,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.ui.strings.CommonStrings @@ -77,7 +79,7 @@ internal fun SendButton( @Composable internal fun SendButtonPreview() = ElementPreview { val normalMode = MessageComposerMode.Normal - val editMode = MessageComposerMode.Edit(null, null, "") + val editMode = MessageComposerMode.Edit(EventId("\$id").toEventOrTransactionId(), "") Row { SendButton(canSendMessage = true, onClick = {}, composerMode = normalMode) SendButton(canSendMessage = false, onClick = {}, composerMode = normalMode) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt index fafa65e64f..ef96000d2b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt @@ -9,7 +9,7 @@ package io.element.android.libraries.textcomposer.model import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.TransactionId +import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.eventId @@ -21,8 +21,7 @@ sealed interface MessageComposerMode { sealed interface Special : MessageComposerMode data class Edit( - val eventId: EventId?, - val transactionId: TransactionId?, + val eventOrTransactionId: EventOrTransactionId, val content: String ) : Special @@ -36,7 +35,7 @@ sealed interface MessageComposerMode { val relatedEventId: EventId? get() = when (this) { is Normal -> null - is Edit -> eventId + is Edit -> eventOrTransactionId.eventId is Reply -> eventId }