From a92ce8d00491105410b960cb2e771ae98f3e0cd3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 19 Jun 2024 11:32:04 +0200 Subject: [PATCH] Sending queue : reintroduce failed status for unrecoverable error --- .../impl/timeline/TimelineStateProvider.kt | 4 +-- .../components/TimelineEventTimestampView.kt | 30 ++++++++++++++----- ...melineItemEventForTimestampViewProvider.kt | 8 +++-- .../components/TimelineItemEventRow.kt | 10 ++----- .../receipt/TimelineItemReadReceiptView.kt | 7 +++-- .../item/event/LocalEventSendState.kt | 9 +++--- .../item/event/EventTimelineItemMapper.kt | 8 ++++- 7 files changed, 49 insertions(+), 27 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index a15763648d..c646308336 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -81,7 +81,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList isMine = false, content = content, groupPosition = TimelineItemGroupPosition.Middle, - sendState = LocalEventSendState.SendingFailed("Message failed to send"), + sendState = LocalEventSendState.SendingFailed.Unrecoverable("Message failed to send"), ), aTimelineItemEvent( isMine = false, @@ -104,7 +104,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList isMine = true, content = content, groupPosition = TimelineItemGroupPosition.Middle, - sendState = LocalEventSendState.SendingFailed("Message failed to send"), + sendState = LocalEventSendState.SendingFailed.Unrecoverable("Message failed to send"), ), aTimelineItemEvent( isMine = true, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt index 5ec0262205..25181d7eb3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -29,45 +30,60 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.isEdited 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.Text +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineEventTimestampView( - formattedTime: String, - isMessageEdited: Boolean, + event: TimelineItem.Event, modifier: Modifier = Modifier, ) { + val formattedTime = event.sentTime + val hasUnrecoverableError = event.localSendState is LocalEventSendState.SendingFailed.Unrecoverable + val isMessageEdited = event.content.isEdited() + val tint = if (hasUnrecoverableError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary Row( modifier = Modifier - .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) - .then(modifier), + .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) + .then(modifier), verticalAlignment = Alignment.CenterVertically, ) { if (isMessageEdited) { Text( stringResource(CommonStrings.common_edited_suffix), style = ElementTheme.typography.fontBodyXsRegular, - color = MaterialTheme.colorScheme.secondary, + color = tint, ) Spacer(modifier = Modifier.width(4.dp)) } Text( formattedTime, style = ElementTheme.typography.fontBodyXsRegular, - color = MaterialTheme.colorScheme.secondary, + color = tint, ) + if (hasUnrecoverableError) { + Spacer(modifier = Modifier.width(2.dp)) + Icon( + imageVector = CompoundIcons.Error(), + contentDescription = stringResource(id = CommonStrings.common_sending_failed), + tint = tint, + modifier = Modifier.size(15.dp, 18.dp), + ) + } } } @PreviewsDayNight @Composable internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event) = ElementPreview { - TimelineEventTimestampView(formattedTime = event.sentTime, isMessageEdited = event.content.isEdited()) + TimelineEventTimestampView(event = event) } object TimelineEventTimestampViewDefaults { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt index 7697ccf4a4..84a93f1c09 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt @@ -26,13 +26,15 @@ class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider get() = sequenceOf( aTimelineItemEvent(), - // Sending failed - aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed("AN_ERROR")), + // Sending failed recoverable + aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed.Recoverable("AN_ERROR")), + // Sending failed unrecoverable + aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed.Unrecoverable("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( - localSendState = LocalEventSendState.SendingFailed("AN_ERROR"), + localSendState = LocalEventSendState.SendingFailed.Unrecoverable("AN_ERROR"), content = aTimelineItemTextContent().copy(isEdited = true), ), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 0c5dec2553..6f81c9c55f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -92,7 +92,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo -import io.element.android.features.messages.impl.timeline.model.event.isEdited import io.element.android.features.messages.impl.timeline.model.eventId import io.element.android.features.messages.impl.timeline.model.metadata import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom @@ -460,8 +459,7 @@ private fun MessageEventBubbleContent( Box(modifier, contentAlignment = Alignment.Center) { content {} TimelineEventTimestampView( - formattedTime = event.sentTime, - isMessageEdited = event.content.isEdited(), + event = event, modifier = Modifier // Outer padding .padding(horizontal = 4.dp, vertical = 4.dp) @@ -481,8 +479,7 @@ private fun MessageEventBubbleContent( content = { content(this::onContentLayoutChange) }, overlay = { TimelineEventTimestampView( - formattedTime = event.sentTime, - isMessageEdited = event.content.isEdited(), + event = event, modifier = Modifier .padding(horizontal = 8.dp, vertical = 4.dp) ) @@ -492,8 +489,7 @@ private fun MessageEventBubbleContent( Column(modifier) { content {} TimelineEventTimestampView( - formattedTime = event.sentTime, - isMessageEdited = event.content.isEdited(), + event = event, modifier = Modifier .align(Alignment.End) .padding(horizontal = 8.dp, vertical = 4.dp) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt index 84f619341c..0342f684dd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -81,8 +81,8 @@ fun TimelineItemReadReceiptView( } } else { when (state.sendState) { - is LocalEventSendState.SendingFailed, - is LocalEventSendState.NotSentYet -> { + LocalEventSendState.NotSentYet, + is LocalEventSendState.SendingFailed.Recoverable -> { ReadReceiptsRow(modifier) { Icon( modifier = Modifier.padding(2.dp), @@ -92,6 +92,9 @@ fun TimelineItemReadReceiptView( ) } } + is LocalEventSendState.SendingFailed.Unrecoverable -> { + // Error? The timestamp is already displayed in red + } null, is LocalEventSendState.Sent -> { if (state.isLastOutgoingMessage) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt index e68ca3fedb..b956e51168 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt @@ -22,11 +22,10 @@ import io.element.android.libraries.matrix.api.core.EventId @Immutable sealed interface LocalEventSendState { data object NotSentYet : LocalEventSendState - - data class SendingFailed( - val error: String - ) : LocalEventSendState - + sealed class SendingFailed(open val error: String) : LocalEventSendState { + data class Recoverable(override val error: String) : SendingFailed(error) + data class Unrecoverable(override val error: String) : SendingFailed(error) + } data class Sent( val eventId: EventId ) : LocalEventSendState diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index a603d354e5..dd0bdabd7f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -77,7 +77,13 @@ fun RustEventSendState?.map(): LocalEventSendState? { return when (this) { null -> null RustEventSendState.NotSentYet -> LocalEventSendState.NotSentYet - is RustEventSendState.SendingFailed -> LocalEventSendState.SendingFailed(error) + is RustEventSendState.SendingFailed -> { + if (this.isRecoverable) { + LocalEventSendState.SendingFailed.Recoverable(this.error) + } else { + LocalEventSendState.SendingFailed.Unrecoverable(this.error) + } + } is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId)) } }