Fix displaying reaction row in message action list (#788)

* Fix displaying reaction row in message action list

* Rename `sendState` to `localSendState` and make it nullable.

Create an `isRemote` helper to detect if an event comes from the server instead.

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-07-06 18:51:51 +02:00 committed by GitHub
parent 77e2ff4953
commit c133a6e606
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 46 additions and 46 deletions

View file

@ -249,6 +249,7 @@ koverMerged {
excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState"
excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState"
excludes += "io.element.android.features.location.impl.map.MapState"
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*"
}
bound {
minValue = 90

View file

@ -78,7 +78,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import timber.log.Timber
@ -153,7 +153,7 @@ fun MessagesView(
onMessageLongClicked = ::onMessageLongClicked,
onUserDataClicked = onUserDataClicked,
onTimestampClicked = { event ->
if (event.sendState is EventSendState.SendingFailed) {
if (event.localSendState is LocalEventSendState.SendingFailed) {
state.retrySendMenuState.eventSink(RetrySendMenuEvents.EventSelected(event))
}
},

View file

@ -30,7 +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 io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -49,7 +49,7 @@ class ActionListPresenter @Inject constructor(
}
val displayEmojiReactions by remember {
derivedStateOf { (target.value as? ActionListState.Target.Success)?.event?.sendState is EventSendState.Sent }
derivedStateOf { (target.value as? ActionListState.Target.Success)?.event?.isRemote == true }
}
fun handleEvents(event: ActionListEvents) {
@ -68,7 +68,6 @@ class ActionListPresenter @Inject constructor(
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 -> {
@ -87,7 +86,8 @@ class ActionListPresenter @Inject constructor(
}
}
else -> buildList<TimelineItemAction> {
if (itemSent) {
if (timelineItem.isRemote) {
// Can only reply or forward messages already uploaded to the server
add(TimelineItemAction.Reply)
add(TimelineItemAction.Forward)
}

View file

@ -31,7 +31,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -59,7 +59,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
isMine = false,
content = content,
groupPosition = TimelineItemGroupPosition.Middle,
sendState = EventSendState.SendingFailed("Message failed to send"),
sendState = LocalEventSendState.SendingFailed("Message failed to send"),
),
aTimelineItemEvent(
isMine = false,
@ -82,7 +82,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
isMine = true,
content = content,
groupPosition = TimelineItemGroupPosition.Middle,
sendState = EventSendState.SendingFailed("Message failed to send"),
sendState = LocalEventSendState.SendingFailed("Message failed to send"),
),
aTimelineItemEvent(
isMine = true,
@ -112,7 +112,7 @@ internal fun aTimelineItemEvent(
isMine: Boolean = false,
content: TimelineItemEventContent = aTimelineItemTextContent(),
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
sendState: EventSendState = EventSendState.Sent(eventId),
sendState: LocalEventSendState = LocalEventSendState.Sent(eventId),
inReplyTo: InReplyTo? = null,
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(),
@ -129,7 +129,7 @@ internal fun aTimelineItemEvent(
isMine = isMine,
senderDisplayName = "Sender",
groupPosition = groupPosition,
sendState = sendState,
localSendState = sendState,
inReplyTo = inReplyTo,
debugInfo = debugInfo,
)

View file

@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalFoundationApi::class)
@ -55,7 +55,7 @@ fun TimelineEventTimestampView(
modifier: Modifier = Modifier,
) {
val formattedTime = event.sentTime
val hasMessageSendingFailed = event.sendState is EventSendState.SendingFailed
val hasMessageSendingFailed = event.localSendState is LocalEventSendState.SendingFailed
val isMessageEdited = (event.content as? TimelineItemTextBasedContent)?.isEdited.orFalse()
val tint = if (hasMessageSendingFailed) MaterialTheme.colorScheme.error else null
val clickModifier = if (hasMessageSendingFailed) {

View file

@ -20,19 +20,19 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider<TimelineItem.Event> {
override val values: Sequence<TimelineItem.Event>
get() = sequenceOf(
aTimelineItemEvent(),
// Sending failed
aTimelineItemEvent().copy(sendState = EventSendState.SendingFailed("AN_ERROR")),
aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed("AN_ERROR")),
// Edited
aTimelineItemEvent().copy(content = aTimelineItemTextContent().copy(isEdited = true)),
// Sending failed + Edited (not sure this is possible IRL, but should be covered by test)
aTimelineItemEvent().copy(
sendState = EventSendState.SendingFailed("AN_ERROR"),
localSendState = LocalEventSendState.SendingFailed("AN_ERROR"),
content = aTimelineItemTextContent().copy(isEdited = true),
),
)

View file

@ -22,7 +22,7 @@ import androidx.compose.ui.unit.TextUnit
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@ -39,7 +39,7 @@ val noExtraPadding = ExtraPadding(0)
@Composable
fun TimelineItem.Event.toExtraPadding(): ExtraPadding {
val formattedTime = sentTime
val hasMessageSendingFailed = sendState is EventSendState.SendingFailed
val hasMessageSendingFailed = localSendState is LocalEventSendState.SendingFailed
val isMessageEdited = (content as? TimelineItemTextBasedContent)?.isEdited.orFalse()
var strLen = 6

View file

@ -26,7 +26,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import kotlinx.collections.immutable.toImmutableList
import java.text.DateFormat
@ -83,7 +82,7 @@ class TimelineItemEventFactory @Inject constructor(
sentTime = sentTime,
groupPosition = groupPosition,
reactionsState = currentTimelineItem.computeReactionsState(),
sendState = currentTimelineItem.event.localSendState ?: EventSendState.NotSentYet,
localSendState = currentTimelineItem.event.localSendState,
inReplyTo = currentTimelineItem.event.inReplyTo(),
debugInfo = currentTimelineItem.event.debugInfo,
)

View file

@ -24,7 +24,7 @@ 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.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import kotlinx.collections.immutable.ImmutableList
@ -62,7 +62,7 @@ sealed interface TimelineItem {
val isMine: Boolean = false,
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
val reactionsState: TimelineItemReactions,
val sendState: EventSendState,
val localSendState: LocalEventSendState?,
val inReplyTo: InReplyTo?,
val debugInfo: TimelineItemDebugInfo,
) : TimelineItem {
@ -71,9 +71,11 @@ sealed interface TimelineItem {
val safeSenderName: String = senderDisplayName ?: senderId.value
val failedToSend: Boolean = sendState is EventSendState.SendingFailed
val failedToSend: Boolean = localSendState is LocalEventSendState.SendingFailed
val isTextMessage: Boolean = content is TimelineItemTextBasedContent
val isRemote = eventId != null
}
@Immutable

View file

@ -30,7 +30,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
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.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.core.aBuildMeta
import kotlinx.collections.immutable.persistentListOf
@ -319,9 +319,9 @@ class ActionListPresenterTest {
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
eventId = null, // No event id, so it's not sent yet
isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
sendState = EventSendState.NotSentYet,
)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))

View file

@ -24,7 +24,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
@ -38,7 +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),
sendState: LocalEventSendState = LocalEventSendState.Sent(AN_EVENT_ID),
) = TimelineItem.Event(
id = eventId?.value.orEmpty(),
eventId = eventId,
@ -49,7 +49,7 @@ internal fun aMessageEvent(
sentTime = "",
isMine = isMine,
reactionsState = aTimelineItemReactions(count = 0),
sendState = sendState,
localSendState = sendState,
inReplyTo = inReplyTo,
debugInfo = debugInfo,
)

View file

@ -24,7 +24,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID
@ -42,7 +42,7 @@ class TimelineItemGrouperTest {
senderDisplayName = "",
content = TimelineItemStateEventContent(body = "a state event"),
reactionsState = aTimelineItemReactions(count = 0),
sendState = EventSendState.Sent(AN_EVENT_ID),
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
inReplyTo = null,
debugInfo = aTimelineItemDebugInfo(),
)

View file

@ -28,7 +28,7 @@ data class EventTimelineItem(
val isLocal: Boolean,
val isOwn: Boolean,
val isRemote: Boolean,
val localSendState: EventSendState?,
val localSendState: LocalEventSendState?,
val reactions: List<EventReaction>,
val sender: UserId,
val senderProfile: ProfileTimelineDetails,

View file

@ -18,15 +18,15 @@ package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
sealed interface EventSendState {
object NotSentYet : EventSendState
object Canceled : EventSendState
sealed interface LocalEventSendState {
object NotSentYet : LocalEventSendState
object Canceled : LocalEventSendState
data class SendingFailed(
val error: String
) : EventSendState
) : LocalEventSendState
data class Sent(
val eventId: EventId
) : EventSendState
) : LocalEventSendState
}

View file

@ -34,7 +34,6 @@ 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
@ -47,7 +46,6 @@ 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

View file

@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId
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.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import org.matrix.rustcomponents.sdk.Reaction
@ -64,13 +64,13 @@ fun RustProfileDetails.map(): ProfileTimelineDetails {
}
}
fun RustEventSendState?.map(): EventSendState? {
fun RustEventSendState?.map(): LocalEventSendState? {
return when (this) {
null -> null
RustEventSendState.NotSentYet -> EventSendState.NotSentYet
is RustEventSendState.SendingFailed -> EventSendState.SendingFailed(error)
is RustEventSendState.Sent -> EventSendState.Sent(EventId(eventId))
RustEventSendState.Cancelled -> EventSendState.Canceled
RustEventSendState.NotSentYet -> LocalEventSendState.NotSentYet
is RustEventSendState.SendingFailed -> LocalEventSendState.SendingFailed(error)
is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId))
RustEventSendState.Cancelled -> LocalEventSendState.Canceled
}
}

View file

@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
@ -94,7 +94,7 @@ fun anEventTimelineItem(
isLocal: Boolean = false,
isOwn: Boolean = false,
isRemote: Boolean = false,
localSendState: EventSendState? = null,
localSendState: LocalEventSendState? = null,
reactions: List<EventReaction> = emptyList(),
sender: UserId = A_USER_ID,
senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(),