Rust sdk : refactor LocalSendState to use the new failure states as iOS does

This commit is contained in:
ganfra 2024-08-27 17:45:22 +02:00
parent 57c6abfe47
commit 18959a930f
9 changed files with 57 additions and 38 deletions

View file

@ -84,7 +84,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
isMine = false, isMine = false,
content = content, content = content,
groupPosition = TimelineItemGroupPosition.Middle, groupPosition = TimelineItemGroupPosition.Middle,
sendState = LocalEventSendState.SendingFailed.Unrecoverable("Message failed to send"), sendState = LocalEventSendState.Failed.Unknown("Message failed to send"),
), ),
aTimelineItemEvent( aTimelineItemEvent(
isMine = false, isMine = false,
@ -107,7 +107,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
isMine = true, isMine = true,
content = content, content = content,
groupPosition = TimelineItemGroupPosition.Middle, groupPosition = TimelineItemGroupPosition.Middle,
sendState = LocalEventSendState.SendingFailed.Unrecoverable("Message failed to send"), sendState = LocalEventSendState.Failed.Unknown("Message failed to send"),
), ),
aTimelineItemEvent( aTimelineItemEvent(
isMine = true, isMine = true,

View file

@ -51,10 +51,10 @@ fun TimelineEventTimestampView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val formattedTime = event.sentTime val formattedTime = event.sentTime
val hasUnrecoverableError = event.localSendState is LocalEventSendState.SendingFailed.Unrecoverable val hasError = event.localSendState is LocalEventSendState.Failed
val hasEncryptionCritical = event.messageShield?.isCritical.orFalse() val hasEncryptionCritical = event.messageShield?.isCritical.orFalse()
val isMessageEdited = event.content.isEdited() val isMessageEdited = event.content.isEdited()
val tint = if (hasUnrecoverableError || hasEncryptionCritical) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary val tint = if (hasError || hasEncryptionCritical) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
Row( Row(
modifier = Modifier modifier = Modifier
.padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing))
@ -74,7 +74,7 @@ fun TimelineEventTimestampView(
style = ElementTheme.typography.fontBodyXsRegular, style = ElementTheme.typography.fontBodyXsRegular,
color = tint, color = tint,
) )
if (hasUnrecoverableError) { if (hasError) {
Spacer(modifier = Modifier.width(2.dp)) Spacer(modifier = Modifier.width(2.dp))
Icon( Icon(
imageVector = CompoundIcons.Error(), imageVector = CompoundIcons.Error(),

View file

@ -27,15 +27,13 @@ class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider<Timel
override val values: Sequence<TimelineItem.Event> override val values: Sequence<TimelineItem.Event>
get() = sequenceOf( get() = sequenceOf(
aTimelineItemEvent(), aTimelineItemEvent(),
// Sending failed recoverable aTimelineItemEvent().copy(localSendState = LocalEventSendState.Sending),
aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed.Recoverable("AN_ERROR")), aTimelineItemEvent().copy(localSendState = LocalEventSendState.Failed.Unknown("AN_ERROR")),
// Sending failed unrecoverable
aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed.Unrecoverable("AN_ERROR")),
// Edited // Edited
aTimelineItemEvent().copy(content = aTimelineItemTextContent().copy(isEdited = true)), aTimelineItemEvent().copy(content = aTimelineItemTextContent().copy(isEdited = true)),
// Sending failed + Edited (not sure this is possible IRL, but should be covered by test) // Sending failed + Edited (not sure this is possible IRL, but should be covered by test)
aTimelineItemEvent().copy( aTimelineItemEvent().copy(
localSendState = LocalEventSendState.SendingFailed.Unrecoverable("AN_ERROR"), localSendState = LocalEventSendState.Failed.Unknown("AN_ERROR"),
content = aTimelineItemTextContent().copy(isEdited = true), content = aTimelineItemTextContent().copy(isEdited = true),
), ),
aTimelineItemEvent().copy( aTimelineItemEvent().copy(

View file

@ -26,7 +26,7 @@ class ReadReceiptViewStateForTimelineItemEventRowProvider :
override val values: Sequence<ReadReceiptViewState> override val values: Sequence<ReadReceiptViewState>
get() = sequenceOf( get() = sequenceOf(
aReadReceiptViewState( aReadReceiptViewState(
sendState = LocalEventSendState.NotSentYet sendState = LocalEventSendState.Sending,
), ),
aReadReceiptViewState( aReadReceiptViewState(
sendState = LocalEventSendState.Sent(EventId("\$eventId")), sendState = LocalEventSendState.Sent(EventId("\$eventId")),

View file

@ -29,7 +29,7 @@ class ReadReceiptViewStateProvider : PreviewParameterProvider<ReadReceiptViewSta
override val values: Sequence<ReadReceiptViewState> override val values: Sequence<ReadReceiptViewState>
get() = sequenceOf( get() = sequenceOf(
aReadReceiptViewState(), aReadReceiptViewState(),
aReadReceiptViewState(sendState = LocalEventSendState.NotSentYet), aReadReceiptViewState(sendState = LocalEventSendState.Sending),
aReadReceiptViewState(sendState = LocalEventSendState.Sent(EventId("\$eventId"))), aReadReceiptViewState(sendState = LocalEventSendState.Sent(EventId("\$eventId"))),
aReadReceiptViewState( aReadReceiptViewState(
sendState = LocalEventSendState.Sent(EventId("\$eventId")), sendState = LocalEventSendState.Sent(EventId("\$eventId")),

View file

@ -70,19 +70,18 @@ fun TimelineItemReadReceiptView(
ReadReceiptsAvatars( ReadReceiptsAvatars(
receipts = state.receipts, receipts = state.receipts,
modifier = Modifier modifier = Modifier
.testTag(TestTags.messageReadReceipts) .testTag(TestTags.messageReadReceipts)
.clip(RoundedCornerShape(4.dp)) .clip(RoundedCornerShape(4.dp))
.clickable { .clickable {
onReadReceiptsClick() onReadReceiptsClick()
} }
.padding(2.dp) .padding(2.dp)
) )
} }
} }
} else { } else {
when (state.sendState) { when (state.sendState) {
LocalEventSendState.NotSentYet, LocalEventSendState.Sending -> {
is LocalEventSendState.SendingFailed.Recoverable -> {
ReadReceiptsRow(modifier) { ReadReceiptsRow(modifier) {
Icon( Icon(
modifier = Modifier.padding(2.dp), modifier = Modifier.padding(2.dp),
@ -92,7 +91,7 @@ fun TimelineItemReadReceiptView(
) )
} }
} }
is LocalEventSendState.SendingFailed.Unrecoverable -> { is LocalEventSendState.Failed -> {
// Error? The timestamp is already displayed in red // Error? The timestamp is already displayed in red
} }
null, null,
@ -119,9 +118,9 @@ private fun ReadReceiptsRow(
) { ) {
Row( Row(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.height(AvatarSize.TimelineReadReceipt.dp + 8.dp) .height(AvatarSize.TimelineReadReceipt.dp + 8.dp)
.padding(horizontal = 18.dp), .padding(horizontal = 18.dp),
horizontalArrangement = Arrangement.End, horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -160,11 +159,11 @@ private fun ReadReceiptsAvatars(
.forEachIndexed { index, readReceiptData -> .forEachIndexed { index, readReceiptData ->
Box( Box(
modifier = Modifier modifier = Modifier
.padding(end = (12.dp + avatarStrokeSize * 2) * index) .padding(end = (12.dp + avatarStrokeSize * 2) * index)
.size(size = avatarSize + avatarStrokeSize * 2) .size(size = avatarSize + avatarStrokeSize * 2)
.clip(CircleShape) .clip(CircleShape)
.background(avatarStrokeColor) .background(avatarStrokeColor)
.zIndex(index.toFloat()), .zIndex(index.toFloat()),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Avatar( Avatar(

View file

@ -90,7 +90,7 @@ sealed interface TimelineItem {
val safeSenderName: String = senderProfile.getDisambiguatedDisplayName(senderId) val safeSenderName: String = senderProfile.getDisambiguatedDisplayName(senderId)
val failedToSend: Boolean = localSendState is LocalEventSendState.SendingFailed val failedToSend: Boolean = localSendState is LocalEventSendState.Failed
val isTextMessage: Boolean = content is TimelineItemTextBasedContent val isTextMessage: Boolean = content is TimelineItemTextBasedContent

View file

@ -18,13 +18,27 @@ package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
@Immutable @Immutable
sealed interface LocalEventSendState { sealed interface LocalEventSendState {
data object NotSentYet : LocalEventSendState data object Sending : LocalEventSendState
sealed class SendingFailed(open val error: String) : LocalEventSendState { sealed interface Failed : LocalEventSendState {
data class Recoverable(override val error: String) : SendingFailed(error) data class Unknown(val error: String) : Failed
data class Unrecoverable(override val error: String) : SendingFailed(error) data class VerifiedUserHasUnsignedDevice(
/**
* The unsigned devices belonging to verified users. A map from user ID
* to a list of device IDs.
*/
val devices: Map<UserId, List<String>>
) : Failed
data class VerifiedUserChangedIdentity(
/**
* The users that were previously verified but are no longer.
*/
val users: List<UserId>
) : Failed
} }
data class Sent( data class Sent(
val eventId: EventId val eventId: EventId

View file

@ -81,15 +81,23 @@ fun RustProfileDetails.map(): ProfileTimelineDetails {
fun RustEventSendState?.map(): LocalEventSendState? { fun RustEventSendState?.map(): LocalEventSendState? {
return when (this) { return when (this) {
null -> null null -> null
RustEventSendState.NotSentYet -> LocalEventSendState.NotSentYet RustEventSendState.NotSentYet -> LocalEventSendState.Sending
is RustEventSendState.SendingFailed -> { is RustEventSendState.SendingFailed -> {
if (this.isRecoverable) { if (isRecoverable) {
LocalEventSendState.SendingFailed.Recoverable(this.error) LocalEventSendState.Sending
} else { } else {
LocalEventSendState.SendingFailed.Unrecoverable(this.error) LocalEventSendState.Failed.Unknown(error)
} }
} }
is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId)) is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId))
is RustEventSendState.VerifiedUserChangedIdentity -> {
LocalEventSendState.Failed.VerifiedUserChangedIdentity(users.map { UserId(it) })
}
is RustEventSendState.VerifiedUserHasUnsignedDevice -> {
LocalEventSendState.Failed.VerifiedUserHasUnsignedDevice(
devices = devices.mapKeys { UserId(it.key) }
)
}
} }
} }