Fix actions for redacted, not sent and media messages (#771)

* Fix actions for redacted, not sent and media messages

* Make `EventDebugInfoView` sections fill max width

* Don't display action list if there are no actions to display

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa
2023-07-05 16:08:17 +02:00
committed by GitHub
parent bd4ece41ac
commit 02fa8aaf46
28 changed files with 270 additions and 72 deletions

1
changelog.d/712.bugfix Normal file
View File

@@ -0,0 +1 @@
Fix actions for redacted, not sent and media messages

View File

@@ -90,7 +90,7 @@ class MessagesFlowNode @AssistedInject constructor(
data class LocationViewer(val location: Location, val description: String?) : NavTarget
@Parcelize
data class EventDebugInfo(val eventId: EventId, val debugInfo: TimelineItemDebugInfo) : NavTarget
data class EventDebugInfo(val eventId: EventId?, val debugInfo: TimelineItemDebugInfo) : NavTarget
@Parcelize
data class ForwardEvent(val eventId: EventId) : NavTarget
@@ -124,7 +124,7 @@ class MessagesFlowNode @AssistedInject constructor(
callback?.onUserDataClicked(userId)
}
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
}

View File

@@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
interface MessagesNavigator {
fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo)
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun onForwardEventClicked(eventId: EventId)
fun onReportContentClicked(eventId: EventId, senderId: UserId)
}

View File

@@ -54,7 +54,7 @@ class MessagesNode @AssistedInject constructor(
fun onEventClicked(event: TimelineItem.Event)
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
fun onUserDataClicked(userId: UserId)
fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo)
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun onForwardEventClicked(eventId: EventId)
fun onReportMessage(eventId: EventId, senderId: UserId)
fun onSendLocationClicked()
@@ -83,7 +83,7 @@ class MessagesNode @AssistedInject constructor(
private fun onUserDataClicked(userId: UserId) {
callback?.onUserDataClicked(userId)
}
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
callback?.onShowEventDebugInfoClicked(eventId, debugInfo)
}
@@ -94,7 +94,7 @@ class MessagesNode @AssistedInject constructor(
override fun onReportContentClicked(eventId: EventId, senderId: UserId) {
callback?.onReportMessage(eventId, senderId)
}
private fun onSendLocationClicked() {
callback?.onSendLocationClicked()
}

View File

@@ -227,15 +227,19 @@ class MessagesPresenter @AssistedInject constructor(
}
private suspend fun handleActionRedact(event: TimelineItem.Event) {
if (event.eventId == null) return
room.redactEvent(event.eventId)
if (event.failedToSend) {
// If the message hasn't been sent yet, just cancel it
event.transactionId?.let { room.cancelSend(it) }
} else if (event.eventId != null) {
room.redactEvent(event.eventId)
}
}
private fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
if (targetEvent.eventId == null) return
val composerMode = MessageComposerMode.Edit(
targetEvent.eventId,
(targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty()
(targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty(),
targetEvent.transactionId,
)
composerState.eventSink(
MessageComposerEvents.SetMode(composerMode)
@@ -288,7 +292,6 @@ class MessagesPresenter @AssistedInject constructor(
}
private fun handleShowDebugInfoAction(event: TimelineItem.Event) {
if (event.eventId == null) return
navigator.onShowEventDebugInfoClicked(event.eventId, event.debugInfo)
}

View File

@@ -18,6 +18,8 @@ package io.element.android.features.messages.impl.actionlist
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -28,6 +30,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -45,6 +48,10 @@ class ActionListPresenter @Inject constructor(
mutableStateOf(ActionListState.Target.None)
}
val displayEmojiReactions by remember {
derivedStateOf { (target.value as? ActionListState.Target.Success)?.event?.sendState is EventSendState.Sent }
}
fun handleEvents(event: ActionListEvents) {
when (event) {
ActionListEvents.Clear -> target.value = ActionListState.Target.None
@@ -54,29 +61,37 @@ class ActionListPresenter @Inject constructor(
return ActionListState(
target = target.value,
displayEmojiReactions = displayEmojiReactions,
eventSink = ::handleEvents
)
}
private fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState<ActionListState.Target>) = launch {
target.value = ActionListState.Target.Loading(timelineItem)
val itemSent = timelineItem.sendState is EventSendState.Sent
val actions =
when (timelineItem.content) {
is TimelineItemRedactedContent,
is TimelineItemRedactedContent -> {
if (buildMeta.isDebuggable) {
listOf(TimelineItemAction.Developer)
} else {
emptyList()
}
}
is TimelineItemStateContent -> {
buildList {
if (timelineItem.content.canBeCopied()) {
add(TimelineItemAction.Copy)
}
add(TimelineItemAction.Copy)
if (buildMeta.isDebuggable) {
add(TimelineItemAction.Developer)
}
}
}
else -> buildList<TimelineItemAction> {
add(TimelineItemAction.Reply)
add(TimelineItemAction.Forward)
if (timelineItem.isMine) {
if (itemSent) {
add(TimelineItemAction.Reply)
add(TimelineItemAction.Forward)
}
if (timelineItem.isMine && timelineItem.isTextMessage) {
add(TimelineItemAction.Edit)
}
if (timelineItem.content.canBeCopied()) {
@@ -93,6 +108,10 @@ class ActionListPresenter @Inject constructor(
}
}
}
target.value = ActionListState.Target.Success(timelineItem, actions.toImmutableList())
if (actions.isNotEmpty()) {
target.value = ActionListState.Target.Success(timelineItem, actions.toImmutableList())
} else {
target.value = ActionListState.Target.None
}
}
}

View File

@@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
@Immutable
data class ActionListState(
val target: Target,
val displayEmojiReactions: Boolean,
val eventSink: (ActionListEvents) -> Unit,
) {
sealed interface Target {

View File

@@ -61,11 +61,19 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(),
)
),
anActionListState().copy(
target = ActionListState.Target.Success(
event = aTimelineItemEvent(content = aTimelineItemLocationContent()),
actions = aTimelineItemActionList(),
),
displayEmojiReactions = false,
),
)
}
fun anActionListState() = ActionListState(
target = ActionListState.Target.None,
displayEmojiReactions = true,
eventSink = {}
)

View File

@@ -175,13 +175,15 @@ private fun SheetContent(
Divider()
}
}
item {
EmojiReactionsRow(
onEmojiReactionClicked = onEmojiReactionClicked,
onCustomReactionClicked = onCustomReactionClicked,
modifier = Modifier.fillMaxWidth(),
)
Divider()
if (state.displayEmojiReactions) {
item {
EmojiReactionsRow(
onEmojiReactionClicked = onEmojiReactionClicked,
onCustomReactionClicked = onCustomReactionClicked,
modifier = Modifier.fillMaxWidth(),
)
Divider()
}
}
items(
items = actions,

View File

@@ -196,10 +196,11 @@ class MessageComposerPresenter @Inject constructor(
composerMode.setToNormal()
when (capturedMode) {
is MessageComposerMode.Normal -> room.sendMessage(text)
is MessageComposerMode.Edit -> room.editMessage(
capturedMode.eventId,
text
)
is MessageComposerMode.Edit -> {
val eventId = capturedMode.eventId
val transactionId = capturedMode.transactionId
room.editMessage(eventId, transactionId, text)
}
is MessageComposerMode.Quote -> TODO()
is MessageComposerMode.Reply -> room.replyMessage(

View File

@@ -161,28 +161,20 @@ fun TimelineItemRow(
)
}
is TimelineItem.Event -> {
fun onClick() {
onClick(timelineItem)
}
fun onLongClick() {
onLongClick(timelineItem)
}
if (timelineItem.content is TimelineItemStateContent) {
TimelineItemStateEventRow(
event = timelineItem,
isHighlighted = highlightedItem == timelineItem.identifier(),
onClick = ::onClick,
onLongClick = ::onLongClick,
onClick = { onClick(timelineItem) },
onLongClick = { onLongClick(timelineItem) },
modifier = modifier,
)
} else {
TimelineItemEventRow(
event = timelineItem,
isHighlighted = highlightedItem == timelineItem.identifier(),
onClick = ::onClick,
onLongClick = ::onLongClick,
onClick = { onClick(timelineItem) },
onLongClick = { onLongClick(timelineItem) },
onUserDataClick = onUserDataClick,
inReplyToClick = inReplyToClick,
onReactionClick = onReactionClick,

View File

@@ -37,7 +37,7 @@ class EventDebugInfoNode @AssistedInject constructor(
) : Node(buildContext, plugins = plugins) {
data class Inputs(
val eventId: EventId,
val eventId: EventId?,
val timelineItemDebugInfo: TimelineItemDebugInfo,
) : NodeInputs

View File

@@ -70,7 +70,7 @@ import io.element.android.libraries.matrix.api.core.EventId
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun EventDebugInfoView(
eventId: EventId,
eventId: EventId?,
model: String,
originalJson: String?,
latestEditedJson: String?,
@@ -99,7 +99,7 @@ fun EventDebugInfoView(
item {
Column(Modifier.padding(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(text = "Event ID:")
CopyableText(text = eventId.value)
CopyableText(text = eventId?.value ?: "-", modifier = Modifier.fillMaxWidth())
}
}
item {
@@ -142,7 +142,7 @@ private fun CollapsibleSection(
)
}
AnimatedVisibility(visible = isExpanded, enter = expandVertically(), exit = shrinkVertically()) {
CopyableText(text = text)
CopyableText(text = text, modifier = Modifier.fillMaxWidth())
}
}
}

View File

@@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
import androidx.compose.runtime.Immutable
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId
@@ -69,6 +70,10 @@ sealed interface TimelineItem {
val showSenderInformation = groupPosition.isNew() && !isMine
val safeSenderName: String = senderDisplayName ?: senderId.value
val failedToSend: Boolean = sendState is EventSendState.SendingFailed
val isTextMessage: Boolean = content is TimelineItemTextBasedContent
}
@Immutable

View File

@@ -31,7 +31,7 @@ class FakeMessagesNavigator : MessagesNavigator {
var onReportContentClickedCount = 0
private set
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
onShowEventDebugInfoClickedCount++
}

View File

@@ -25,9 +25,12 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.core.aBuildMeta
import kotlinx.collections.immutable.persistentListOf
@@ -62,7 +65,6 @@ class ActionListPresenterTest {
ActionListState.Target.Success(
messageEvent,
persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.Developer,
)
)
@@ -88,7 +90,6 @@ class ActionListPresenterTest {
ActionListState.Target.Success(
messageEvent,
persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.Developer,
)
)
@@ -184,7 +185,6 @@ class ActionListPresenterTest {
persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Edit,
TimelineItemAction.Developer,
TimelineItemAction.Redact,
)
@@ -195,6 +195,63 @@ class ActionListPresenterTest {
}
}
@Test
fun `present - compute for a state item in debug build`() = runTest {
val presenter = anActionListPresenter(isBuildDebuggable = true)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val stateEvent = aTimelineItemEvent(
isMine = true,
content = aTimelineItemStateEventContent(),
)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent))
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
stateEvent,
persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.Developer,
)
)
)
initialState.eventSink.invoke(ActionListEvents.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
}
}
@Test
fun `present - compute for a state item in non-debuggable build`() = runTest {
val presenter = anActionListPresenter(isBuildDebuggable = false)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val stateEvent = aTimelineItemEvent(
isMine = true,
content = aTimelineItemStateEventContent(),
)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent))
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
stateEvent,
persistentListOf(
TimelineItemAction.Copy,
)
)
)
initialState.eventSink.invoke(ActionListEvents.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
}
}
@Test
fun `present - compute message in non-debuggable build`() = runTest {
val presenter = anActionListPresenter(isBuildDebuggable = false)
@@ -226,6 +283,62 @@ class ActionListPresenterTest {
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
}
}
@Test
fun `present - compute message with no actions`() = runTest {
val presenter = anActionListPresenter(isBuildDebuggable = false)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
)
val redactedEvent = aMessageEvent(
isMine = true,
content = TimelineItemRedactedContent,
)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent))
awaitItem().run {
assertThat(target).isEqualTo(ActionListState.Target.None)
assertThat(displayEmojiReactions).isFalse()
}
}
}
@Test
fun `present - compute not sent message`() = runTest {
val presenter = anActionListPresenter(isBuildDebuggable = false)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
sendState = EventSendState.NotSentYet,
)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
messageEvent,
persistentListOf(
TimelineItemAction.Edit,
TimelineItemAction.Copy,
TimelineItemAction.Redact,
)
)
)
assertThat(successState.displayEmojiReactions).isFalse()
}
}
}
private fun anActionListPresenter(isBuildDebuggable: Boolean) = ActionListPresenter(buildMeta = aBuildMeta(isDebuggable = isBuildDebuggable))

View File

@@ -38,6 +38,7 @@ internal fun aMessageEvent(
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
inReplyTo: InReplyTo? = null,
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
sendState: EventSendState = EventSendState.Sent(AN_EVENT_ID),
) = TimelineItem.Event(
id = eventId?.value.orEmpty(),
eventId = eventId,
@@ -48,7 +49,7 @@ internal fun aMessageEvent(
sentTime = "",
isMine = isMine,
reactionsState = aTimelineItemReactions(count = 0),
sendState = EventSendState.Sent(AN_EVENT_ID),
sendState = sendState,
inReplyTo = inReplyTo,
debugInfo = debugInfo,
)

View File

@@ -36,6 +36,7 @@ import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId
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.room.MatrixRoom
@@ -43,6 +44,7 @@ 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
import io.element.android.libraries.matrix.test.A_REPLY
import io.element.android.libraries.matrix.test.A_TRANSACTION_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediapickers.api.PickerProvider
@@ -193,7 +195,7 @@ class MessageComposerPresenterTest {
}
@Test
fun `present - edit message`() = runTest {
fun `present - edit sent message`() = runTest {
val fakeMatrixRoom = FakeMatrixRoom()
val presenter = createPresenter(
this,
@@ -219,7 +221,38 @@ class MessageComposerPresenterTest {
val messageSentState = awaitItem()
assertThat(messageSentState.text).isEqualTo(StableCharSequence(""))
assertThat(messageSentState.isSendButtonVisible).isFalse()
assertThat(fakeMatrixRoom.editMessageParameter).isEqualTo(ANOTHER_MESSAGE)
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE)
}
}
@Test
fun `present - edit not sent message`() = runTest {
val fakeMatrixRoom = FakeMatrixRoom()
val presenter = createPresenter(
this,
fakeMatrixRoom,
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.text).isEqualTo(StableCharSequence(""))
val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID)
initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode))
skipItems(1)
val withMessageState = awaitItem()
assertThat(withMessageState.mode).isEqualTo(mode)
assertThat(withMessageState.text).isEqualTo(StableCharSequence(A_MESSAGE))
assertThat(withMessageState.isSendButtonVisible).isTrue()
withMessageState.eventSink.invoke(MessageComposerEvents.UpdateText(ANOTHER_MESSAGE))
val withEditedMessageState = awaitItem()
assertThat(withEditedMessageState.text).isEqualTo(StableCharSequence(ANOTHER_MESSAGE))
withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(ANOTHER_MESSAGE))
skipItems(1)
val messageSentState = awaitItem()
assertThat(messageSentState.text).isEqualTo(StableCharSequence(""))
assertThat(messageSentState.isSendButtonVisible).isFalse()
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE)
}
}
@@ -474,6 +507,10 @@ class MessageComposerPresenterTest {
)
}
fun anEditMode() = MessageComposerMode.Edit(AN_EVENT_ID, A_MESSAGE)
fun anEditMode(
eventId: EventId? = AN_EVENT_ID,
message: String = A_MESSAGE,
transactionId: String? = null,
) = MessageComposerMode.Edit(eventId, message, transactionId)
fun aReplyMode() = MessageComposerMode.Reply(A_USER_NAME, null, AN_EVENT_ID, A_MESSAGE)
fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE)

View File

@@ -71,7 +71,7 @@ interface MatrixRoom : Closeable {
suspend fun sendMessage(message: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit>
suspend fun replyMessage(eventId: EventId, message: String): Result<Unit>

View File

@@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.room.location.toInner
@@ -46,6 +47,7 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -209,11 +211,16 @@ class RustMatrixRoom(
}
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
val transactionId = genTransactionId()
// val content = messageEventContentFromMarkdown(message)
runCatching {
innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId)
override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
if (originalEventId != null) {
runCatching {
innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId)
}
} else {
runCatching {
transactionId?.let { cancelSend(it) }
innerRoom.send(messageEventContentFromMarkdown(message), genTransactionId())
}
}
}

View File

@@ -110,4 +110,8 @@ class RustMatrixTimeline(
innerRoom.sendReadReceipt(eventId = eventId.value)
}
}
fun getItemById(eventId: EventId): MatrixTimelineItem.Event? {
return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event
}
}

View File

@@ -81,6 +81,7 @@ class FakeMatrixRoom(
private var reportContentResult = Result.success(Unit)
private var sendLocationResult = Result.success(Unit)
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
val editMessageCalls = mutableListOf<String>()
var sendMediaCount = 0
private set
@@ -174,11 +175,8 @@ class FakeMatrixRoom(
return cancelSendResult
}
var editMessageParameter: String? = null
private set
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
editMessageParameter = message
override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit> {
editMessageCalls += message
return Result.success(Unit)
}

View File

@@ -25,11 +25,11 @@ sealed interface MessageComposerMode : Parcelable {
@Parcelize
data class Normal(val content: CharSequence?) : MessageComposerMode
sealed class Special(open val eventId: EventId, open val defaultContent: CharSequence) :
sealed class Special(open val eventId: EventId?, open val defaultContent: CharSequence) :
MessageComposerMode
@Parcelize
data class Edit(override val eventId: EventId, override val defaultContent: CharSequence) :
data class Edit(override val eventId: EventId?, override val defaultContent: CharSequence, val transactionId: String?) :
Special(eventId, defaultContent)
@Parcelize

View File

@@ -473,7 +473,7 @@ private fun EditContentToPreview() {
TextComposer(
onSendMessage = {},
onComposerTextChange = {},
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text"),
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", "1234"),
onResetComposerMode = {},
composerCanSendMessage = true,
composerText = "A message",