Fix API Break: introduce EventOrTransactionId

This commit is contained in:
Benoit Marty 2024-10-18 08:49:02 +02:00
parent 12f60dcaaa
commit 1f8f1c998f
25 changed files with 175 additions and 147 deletions

View file

@ -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<MediaUploadHandler>
suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit>
suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit>
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>

View file

@ -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<Unit>
suspend fun editMessage(
originalEventId: EventId?,
transactionId: TransactionId?,
eventOrTransactionId: EventOrTransactionId,
body: String, htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
): Result<Unit>
@ -89,17 +89,18 @@ interface Timeline : AutoCloseable {
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>
suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result<Unit>
suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result<Unit>
suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): 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 cancelSend(transactionId: TransactionId): Result<Unit> =
redactEvent(transactionId.toEventOrTransactionId(), reason = null)
/**
* Share a location message in the room.

View file

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

View file

@ -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<Unit> {
return liveTimeline.toggleReaction(emoji, uniqueId)
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> {

View file

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

View file

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

View file

@ -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<Unit> = withContext(dispatcher) {
override suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result<Unit> = 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<IntentionalMention>,
): Result<Unit> =
withContext(dispatcher) {
runCatching<Unit> {
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<Unit> = withContext(dispatcher) {
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> = 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<Unit> =
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 { }
}

View file

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

View file

@ -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<IntentionalMention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
private val sendMessageResult: (String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _ -> lambdaError() },
private val updateUserRoleResult: () -> Result<Unit> = { lambdaError() },
private val toggleReactionResult: (String, UniqueId) -> Result<Unit> = { _, _ -> lambdaError() },
private val toggleReactionResult: (String, EventOrTransactionId) -> Result<Unit> = { _, _ -> lambdaError() },
private val retrySendMessageResult: (TransactionId) -> Result<Unit> = { lambdaError() },
private val cancelSendResult: (TransactionId) -> Result<Unit> = { lambdaError() },
private val forwardEventResult: (EventId, List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() },
@ -236,8 +236,8 @@ class FakeMatrixRoom(
sendMessageResult(body, htmlBody, intentionalMentions)
}
override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit> {
return toggleReactionResult(emoji, uniqueId)
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> {
return toggleReactionResult(emoji, eventOrTransactionId)
}
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> = simulateLongTask {

View file

@ -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<IntentionalMention>,
): Result<Unit> = sendMessageLambda(body, htmlBody, intentionalMentions)
var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result<Unit> = { _, _, _ ->
var redactEventLambda: (eventOrTransactionId: EventOrTransactionId, reason: String?) -> Result<Unit> = { _, _ ->
Result.success(Unit)
}
override suspend fun redactEvent(
eventId: EventId?,
transactionId: TransactionId?,
eventOrTransactionId: EventOrTransactionId,
reason: String?
): Result<Unit> = redactEventLambda(eventId, transactionId, reason)
): Result<Unit> = redactEventLambda(eventOrTransactionId, reason)
var editMessageLambda: (
originalEventId: EventId?,
transactionId: TransactionId?,
eventOrTransactionId: EventOrTransactionId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
) -> Result<Unit> = { _, _, _, _, _ ->
) -> Result<Unit> = { _, _, _, _ ->
Result.success(Unit)
}
override suspend fun editMessage(
originalEventId: EventId?,
transactionId: TransactionId?,
eventOrTransactionId: EventOrTransactionId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
): Result<Unit> = editMessageLambda(
originalEventId,
transactionId,
eventOrTransactionId,
body,
htmlBody,
intentionalMentions
@ -211,14 +206,15 @@ class FakeTimeline(
progressCallback
)
var toggleReactionLambda: (emoji: String, uniqueId: UniqueId) -> Result<Unit> = { _, _ -> Result.success(Unit) }
override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit> = toggleReactionLambda(emoji, uniqueId)
var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result<Unit> = { _, _ -> Result.success(Unit) }
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> = toggleReactionLambda(
emoji,
eventOrTransactionId
)
var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> Result.success(Unit) }
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = forwardEventLambda(eventId, roomIds)
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = redactEvent(null, transactionId, null)
var sendLocationLambda: (
body: String,
geoUri: String,

View file

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

View file

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

View file

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