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:
parent
bd4ece41ac
commit
02fa8aaf46
28 changed files with 270 additions and 72 deletions
1
changelog.d/712.bugfix
Normal file
1
changelog.d/712.bugfix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix actions for redacted, not sent and media messages
|
||||||
|
|
@ -90,7 +90,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
data class LocationViewer(val location: Location, val description: String?) : NavTarget
|
data class LocationViewer(val location: Location, val description: String?) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class EventDebugInfo(val eventId: EventId, val debugInfo: TimelineItemDebugInfo) : NavTarget
|
data class EventDebugInfo(val eventId: EventId?, val debugInfo: TimelineItemDebugInfo) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ForwardEvent(val eventId: EventId) : NavTarget
|
data class ForwardEvent(val eventId: EventId) : NavTarget
|
||||||
|
|
@ -124,7 +124,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
callback?.onUserDataClicked(userId)
|
callback?.onUserDataClicked(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
|
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||||
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
|
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ 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.TimelineItemDebugInfo
|
||||||
|
|
||||||
interface MessagesNavigator {
|
interface MessagesNavigator {
|
||||||
fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo)
|
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
|
||||||
fun onForwardEventClicked(eventId: EventId)
|
fun onForwardEventClicked(eventId: EventId)
|
||||||
fun onReportContentClicked(eventId: EventId, senderId: UserId)
|
fun onReportContentClicked(eventId: EventId, senderId: UserId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class MessagesNode @AssistedInject constructor(
|
||||||
fun onEventClicked(event: TimelineItem.Event)
|
fun onEventClicked(event: TimelineItem.Event)
|
||||||
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
|
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
|
||||||
fun onUserDataClicked(userId: UserId)
|
fun onUserDataClicked(userId: UserId)
|
||||||
fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo)
|
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
|
||||||
fun onForwardEventClicked(eventId: EventId)
|
fun onForwardEventClicked(eventId: EventId)
|
||||||
fun onReportMessage(eventId: EventId, senderId: UserId)
|
fun onReportMessage(eventId: EventId, senderId: UserId)
|
||||||
fun onSendLocationClicked()
|
fun onSendLocationClicked()
|
||||||
|
|
@ -83,7 +83,7 @@ class MessagesNode @AssistedInject constructor(
|
||||||
private fun onUserDataClicked(userId: UserId) {
|
private fun onUserDataClicked(userId: UserId) {
|
||||||
callback?.onUserDataClicked(userId)
|
callback?.onUserDataClicked(userId)
|
||||||
}
|
}
|
||||||
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
|
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||||
callback?.onShowEventDebugInfoClicked(eventId, debugInfo)
|
callback?.onShowEventDebugInfoClicked(eventId, debugInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,15 +227,19 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleActionRedact(event: TimelineItem.Event) {
|
private suspend fun handleActionRedact(event: TimelineItem.Event) {
|
||||||
if (event.eventId == null) return
|
if (event.failedToSend) {
|
||||||
room.redactEvent(event.eventId)
|
// 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) {
|
private fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
|
||||||
if (targetEvent.eventId == null) return
|
|
||||||
val composerMode = MessageComposerMode.Edit(
|
val composerMode = MessageComposerMode.Edit(
|
||||||
targetEvent.eventId,
|
targetEvent.eventId,
|
||||||
(targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty()
|
(targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty(),
|
||||||
|
targetEvent.transactionId,
|
||||||
)
|
)
|
||||||
composerState.eventSink(
|
composerState.eventSink(
|
||||||
MessageComposerEvents.SetMode(composerMode)
|
MessageComposerEvents.SetMode(composerMode)
|
||||||
|
|
@ -288,7 +292,6 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleShowDebugInfoAction(event: TimelineItem.Event) {
|
private fun handleShowDebugInfoAction(event: TimelineItem.Event) {
|
||||||
if (event.eventId == null) return
|
|
||||||
navigator.onShowEventDebugInfoClicked(event.eventId, event.debugInfo)
|
navigator.onShowEventDebugInfoClicked(event.eventId, event.debugInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package io.element.android.features.messages.impl.actionlist
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
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.features.messages.impl.timeline.model.event.canBeCopied
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
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.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -45,6 +48,10 @@ class ActionListPresenter @Inject constructor(
|
||||||
mutableStateOf(ActionListState.Target.None)
|
mutableStateOf(ActionListState.Target.None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val displayEmojiReactions by remember {
|
||||||
|
derivedStateOf { (target.value as? ActionListState.Target.Success)?.event?.sendState is EventSendState.Sent }
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: ActionListEvents) {
|
fun handleEvents(event: ActionListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||||
|
|
@ -54,29 +61,37 @@ class ActionListPresenter @Inject constructor(
|
||||||
|
|
||||||
return ActionListState(
|
return ActionListState(
|
||||||
target = target.value,
|
target = target.value,
|
||||||
|
displayEmojiReactions = displayEmojiReactions,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState<ActionListState.Target>) = launch {
|
private fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState<ActionListState.Target>) = launch {
|
||||||
target.value = ActionListState.Target.Loading(timelineItem)
|
target.value = ActionListState.Target.Loading(timelineItem)
|
||||||
|
val itemSent = timelineItem.sendState is EventSendState.Sent
|
||||||
val actions =
|
val actions =
|
||||||
when (timelineItem.content) {
|
when (timelineItem.content) {
|
||||||
is TimelineItemRedactedContent,
|
is TimelineItemRedactedContent -> {
|
||||||
|
if (buildMeta.isDebuggable) {
|
||||||
|
listOf(TimelineItemAction.Developer)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
is TimelineItemStateContent -> {
|
is TimelineItemStateContent -> {
|
||||||
buildList {
|
buildList {
|
||||||
if (timelineItem.content.canBeCopied()) {
|
add(TimelineItemAction.Copy)
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
}
|
|
||||||
if (buildMeta.isDebuggable) {
|
if (buildMeta.isDebuggable) {
|
||||||
add(TimelineItemAction.Developer)
|
add(TimelineItemAction.Developer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> buildList<TimelineItemAction> {
|
else -> buildList<TimelineItemAction> {
|
||||||
add(TimelineItemAction.Reply)
|
if (itemSent) {
|
||||||
add(TimelineItemAction.Forward)
|
add(TimelineItemAction.Reply)
|
||||||
if (timelineItem.isMine) {
|
add(TimelineItemAction.Forward)
|
||||||
|
}
|
||||||
|
if (timelineItem.isMine && timelineItem.isTextMessage) {
|
||||||
add(TimelineItemAction.Edit)
|
add(TimelineItemAction.Edit)
|
||||||
}
|
}
|
||||||
if (timelineItem.content.canBeCopied()) {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ActionListState(
|
data class ActionListState(
|
||||||
val target: Target,
|
val target: Target,
|
||||||
|
val displayEmojiReactions: Boolean,
|
||||||
val eventSink: (ActionListEvents) -> Unit,
|
val eventSink: (ActionListEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
sealed interface Target {
|
sealed interface Target {
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,19 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
anActionListState().copy(
|
||||||
|
target = ActionListState.Target.Success(
|
||||||
|
event = aTimelineItemEvent(content = aTimelineItemLocationContent()),
|
||||||
|
actions = aTimelineItemActionList(),
|
||||||
|
),
|
||||||
|
displayEmojiReactions = false,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun anActionListState() = ActionListState(
|
fun anActionListState() = ActionListState(
|
||||||
target = ActionListState.Target.None,
|
target = ActionListState.Target.None,
|
||||||
|
displayEmojiReactions = true,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,13 +175,15 @@ private fun SheetContent(
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
if (state.displayEmojiReactions) {
|
||||||
EmojiReactionsRow(
|
item {
|
||||||
onEmojiReactionClicked = onEmojiReactionClicked,
|
EmojiReactionsRow(
|
||||||
onCustomReactionClicked = onCustomReactionClicked,
|
onEmojiReactionClicked = onEmojiReactionClicked,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
onCustomReactionClicked = onCustomReactionClicked,
|
||||||
)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Divider()
|
)
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
items(
|
items(
|
||||||
items = actions,
|
items = actions,
|
||||||
|
|
|
||||||
|
|
@ -196,10 +196,11 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
composerMode.setToNormal()
|
composerMode.setToNormal()
|
||||||
when (capturedMode) {
|
when (capturedMode) {
|
||||||
is MessageComposerMode.Normal -> room.sendMessage(text)
|
is MessageComposerMode.Normal -> room.sendMessage(text)
|
||||||
is MessageComposerMode.Edit -> room.editMessage(
|
is MessageComposerMode.Edit -> {
|
||||||
capturedMode.eventId,
|
val eventId = capturedMode.eventId
|
||||||
text
|
val transactionId = capturedMode.transactionId
|
||||||
)
|
room.editMessage(eventId, transactionId, text)
|
||||||
|
}
|
||||||
|
|
||||||
is MessageComposerMode.Quote -> TODO()
|
is MessageComposerMode.Quote -> TODO()
|
||||||
is MessageComposerMode.Reply -> room.replyMessage(
|
is MessageComposerMode.Reply -> room.replyMessage(
|
||||||
|
|
|
||||||
|
|
@ -161,28 +161,20 @@ fun TimelineItemRow(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is TimelineItem.Event -> {
|
is TimelineItem.Event -> {
|
||||||
fun onClick() {
|
|
||||||
onClick(timelineItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onLongClick() {
|
|
||||||
onLongClick(timelineItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timelineItem.content is TimelineItemStateContent) {
|
if (timelineItem.content is TimelineItemStateContent) {
|
||||||
TimelineItemStateEventRow(
|
TimelineItemStateEventRow(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||||
onClick = ::onClick,
|
onClick = { onClick(timelineItem) },
|
||||||
onLongClick = ::onLongClick,
|
onLongClick = { onLongClick(timelineItem) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TimelineItemEventRow(
|
TimelineItemEventRow(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||||
onClick = ::onClick,
|
onClick = { onClick(timelineItem) },
|
||||||
onLongClick = ::onLongClick,
|
onLongClick = { onLongClick(timelineItem) },
|
||||||
onUserDataClick = onUserDataClick,
|
onUserDataClick = onUserDataClick,
|
||||||
inReplyToClick = inReplyToClick,
|
inReplyToClick = inReplyToClick,
|
||||||
onReactionClick = onReactionClick,
|
onReactionClick = onReactionClick,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class EventDebugInfoNode @AssistedInject constructor(
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val eventId: EventId,
|
val eventId: EventId?,
|
||||||
val timelineItemDebugInfo: TimelineItemDebugInfo,
|
val timelineItemDebugInfo: TimelineItemDebugInfo,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EventDebugInfoView(
|
fun EventDebugInfoView(
|
||||||
eventId: EventId,
|
eventId: EventId?,
|
||||||
model: String,
|
model: String,
|
||||||
originalJson: String?,
|
originalJson: String?,
|
||||||
latestEditedJson: String?,
|
latestEditedJson: String?,
|
||||||
|
|
@ -99,7 +99,7 @@ fun EventDebugInfoView(
|
||||||
item {
|
item {
|
||||||
Column(Modifier.padding(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
Column(Modifier.padding(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
Text(text = "Event ID:")
|
Text(text = "Event ID:")
|
||||||
CopyableText(text = eventId.value)
|
CopyableText(text = eventId?.value ?: "-", modifier = Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|
@ -142,7 +142,7 @@ private fun CollapsibleSection(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AnimatedVisibility(visible = isExpanded, enter = expandVertically(), exit = shrinkVertically()) {
|
AnimatedVisibility(visible = isExpanded, enter = expandVertically(), exit = shrinkVertically()) {
|
||||||
CopyableText(text = text)
|
CopyableText(text = text, modifier = Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
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.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.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
@ -69,6 +70,10 @@ sealed interface TimelineItem {
|
||||||
val showSenderInformation = groupPosition.isNew() && !isMine
|
val showSenderInformation = groupPosition.isNew() && !isMine
|
||||||
|
|
||||||
val safeSenderName: String = senderDisplayName ?: senderId.value
|
val safeSenderName: String = senderDisplayName ?: senderId.value
|
||||||
|
|
||||||
|
val failedToSend: Boolean = sendState is EventSendState.SendingFailed
|
||||||
|
|
||||||
|
val isTextMessage: Boolean = content is TimelineItemTextBasedContent
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class FakeMessagesNavigator : MessagesNavigator {
|
||||||
var onReportContentClickedCount = 0
|
var onReportContentClickedCount = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
|
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||||
onShowEventDebugInfoClickedCount++
|
onShowEventDebugInfoClickedCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.ActionListPresenter
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
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.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.TimelineItemRedactedContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
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.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.A_MESSAGE
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -62,7 +65,6 @@ class ActionListPresenterTest {
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
messageEvent,
|
messageEvent,
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.Developer,
|
TimelineItemAction.Developer,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -88,7 +90,6 @@ class ActionListPresenterTest {
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
messageEvent,
|
messageEvent,
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.Developer,
|
TimelineItemAction.Developer,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -184,7 +185,6 @@ class ActionListPresenterTest {
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Developer,
|
TimelineItemAction.Developer,
|
||||||
TimelineItemAction.Redact,
|
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
|
@Test
|
||||||
fun `present - compute message in non-debuggable build`() = runTest {
|
fun `present - compute message in non-debuggable build`() = runTest {
|
||||||
val presenter = anActionListPresenter(isBuildDebuggable = false)
|
val presenter = anActionListPresenter(isBuildDebuggable = false)
|
||||||
|
|
@ -226,6 +283,62 @@ class ActionListPresenterTest {
|
||||||
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
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))
|
private fun anActionListPresenter(isBuildDebuggable: Boolean) = ActionListPresenter(buildMeta = aBuildMeta(isDebuggable = isBuildDebuggable))
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ internal fun aMessageEvent(
|
||||||
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
|
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
|
||||||
inReplyTo: InReplyTo? = null,
|
inReplyTo: InReplyTo? = null,
|
||||||
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
|
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
|
||||||
|
sendState: EventSendState = EventSendState.Sent(AN_EVENT_ID),
|
||||||
) = TimelineItem.Event(
|
) = TimelineItem.Event(
|
||||||
id = eventId?.value.orEmpty(),
|
id = eventId?.value.orEmpty(),
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
|
|
@ -48,7 +49,7 @@ internal fun aMessageEvent(
|
||||||
sentTime = "",
|
sentTime = "",
|
||||||
isMine = isMine,
|
isMine = isMine,
|
||||||
reactionsState = aTimelineItemReactions(count = 0),
|
reactionsState = aTimelineItemReactions(count = 0),
|
||||||
sendState = EventSendState.Sent(AN_EVENT_ID),
|
sendState = sendState,
|
||||||
inReplyTo = inReplyTo,
|
inReplyTo = inReplyTo,
|
||||||
debugInfo = debugInfo,
|
debugInfo = debugInfo,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
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.ImageInfo
|
||||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
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.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
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_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.A_USER_NAME
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
|
|
@ -193,7 +195,7 @@ class MessageComposerPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - edit message`() = runTest {
|
fun `present - edit sent message`() = runTest {
|
||||||
val fakeMatrixRoom = FakeMatrixRoom()
|
val fakeMatrixRoom = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
this,
|
this,
|
||||||
|
|
@ -219,7 +221,38 @@ class MessageComposerPresenterTest {
|
||||||
val messageSentState = awaitItem()
|
val messageSentState = awaitItem()
|
||||||
assertThat(messageSentState.text).isEqualTo(StableCharSequence(""))
|
assertThat(messageSentState.text).isEqualTo(StableCharSequence(""))
|
||||||
assertThat(messageSentState.isSendButtonVisible).isFalse()
|
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 aReplyMode() = MessageComposerMode.Reply(A_USER_NAME, null, AN_EVENT_ID, A_MESSAGE)
|
||||||
fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE)
|
fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ interface MatrixRoom : Closeable {
|
||||||
|
|
||||||
suspend fun sendMessage(message: String): Result<Unit>
|
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>
|
suspend fun replyMessage(eventId: EventId, message: String): Result<Unit>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.StateEventType
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
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.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.api.timeline.item.event.EventType
|
||||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
||||||
import io.element.android.libraries.matrix.impl.room.location.toInner
|
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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -209,11 +211,16 @@ class RustMatrixRoom(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
|
override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||||
val transactionId = genTransactionId()
|
if (originalEventId != null) {
|
||||||
// val content = messageEventContentFromMarkdown(message)
|
runCatching {
|
||||||
runCatching {
|
innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId)
|
||||||
innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId)
|
}
|
||||||
|
} else {
|
||||||
|
runCatching {
|
||||||
|
transactionId?.let { cancelSend(it) }
|
||||||
|
innerRoom.send(messageEventContentFromMarkdown(message), genTransactionId())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,4 +110,8 @@ class RustMatrixTimeline(
|
||||||
innerRoom.sendReadReceipt(eventId = eventId.value)
|
innerRoom.sendReadReceipt(eventId = eventId.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getItemById(eventId: EventId): MatrixTimelineItem.Event? {
|
||||||
|
return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ class FakeMatrixRoom(
|
||||||
private var reportContentResult = Result.success(Unit)
|
private var reportContentResult = Result.success(Unit)
|
||||||
private var sendLocationResult = Result.success(Unit)
|
private var sendLocationResult = Result.success(Unit)
|
||||||
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
|
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
|
||||||
|
val editMessageCalls = mutableListOf<String>()
|
||||||
|
|
||||||
var sendMediaCount = 0
|
var sendMediaCount = 0
|
||||||
private set
|
private set
|
||||||
|
|
@ -174,11 +175,8 @@ class FakeMatrixRoom(
|
||||||
return cancelSendResult
|
return cancelSendResult
|
||||||
}
|
}
|
||||||
|
|
||||||
var editMessageParameter: String? = null
|
override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit> {
|
||||||
private set
|
editMessageCalls += message
|
||||||
|
|
||||||
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
|
|
||||||
editMessageParameter = message
|
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ sealed interface MessageComposerMode : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Normal(val content: CharSequence?) : MessageComposerMode
|
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
|
MessageComposerMode
|
||||||
|
|
||||||
@Parcelize
|
@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)
|
Special(eventId, defaultContent)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|
|
||||||
|
|
@ -473,7 +473,7 @@ private fun EditContentToPreview() {
|
||||||
TextComposer(
|
TextComposer(
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
onComposerTextChange = {},
|
onComposerTextChange = {},
|
||||||
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text"),
|
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", "1234"),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
composerCanSendMessage = true,
|
composerCanSendMessage = true,
|
||||||
composerText = "A message",
|
composerText = "A message",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:12f2f8146898375b4556dfd0718b02b18ac99859efe7e857914b2cf424ca17ba
|
||||||
|
size 25792
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2822e203b85195a5584fa0d3e0be2b260ff233e725ae104f237c46978f6ae068
|
||||||
|
size 27337
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:15c77e539cc793ab0875813ac98eab14412a0619ca16afc90513e11f41462e07
|
oid sha256:b28bdd7f227340b1af4456f0e24a78b8b279819b7e41edca11c0f2bc9a14a15b
|
||||||
size 32575
|
size 32894
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:047b5e935803b2c7b12ebb1d86feabccac9d6291de3c14fea1185c2279db8d57
|
oid sha256:f7d39ebcf85878e3869d369a10f95efe6c786db44c098864473b3e5bda51bd83
|
||||||
size 35318
|
size 35141
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue