Skip to content

Commit

Permalink
Update dependency org.matrix.rustcomponents:sdk-android to v0.2.41 (#…
Browse files Browse the repository at this point in the history
…3384)

* Introduce value class UniqueId.

* Allow reactions on non-sent Event, the SDK can now handle it.

Also the SDK will manage local echo for reactions.

* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.41

* Fixes after SDK upgrade:

- Use `ClientBuilderSlidingSync` to set `SlidingSyncVersionBuilder` in `RustMatrixClientFactory`.
- `Room.toggleReaction(emoji: String, eventId: EventId)` is now `Room.toggleReaction(emoji: String, uniqueId: UniqueId)`, since reactions can now be applied to local echoes too in the SDK.

* Rename exception case

* Fix wrong error case being used in test

---------

Co-authored-by: Benoit Marty <[email protected]>
Co-authored-by: Benoit Marty <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <[email protected]>
  • Loading branch information
4 people authored Sep 3, 2024
1 parent 5920fd3 commit 9fb82a1
Show file tree
Hide file tree
Showing 47 changed files with 237 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ sealed class ChangeServerError : Throwable() {

companion object {
fun from(error: Throwable): ChangeServerError = when (error) {
is AuthenticationException.SlidingSyncNotAvailable -> SlidingSyncAlert
is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert
else -> Error(R.string.screen_change_server_error_invalid_homeserver)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ErrorFormatterTest {

@Test
fun `loginError - invalid auth error returns unknown error message`() {
val error = AuthenticationException.SlidingSyncNotAvailable("Some message. Also contains M_FORBIDDEN, but won't be parsed")
val error = AuthenticationException.SlidingSyncVersion("Some message. Also contains M_FORBIDDEN, but won't be parsed")
assertThat(loginError(error)).isEqualTo(CommonStrings.error_unknown)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ package io.element.android.features.messages.impl

import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId

sealed interface MessagesEvents {
data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents
data class ToggleReaction(val emoji: String, val eventId: EventId) : MessagesEvents
data class ToggleReaction(val emoji: String, val uniqueId: UniqueId) : MessagesEvents
data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents
data object Dismiss : MessagesEvents
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
Expand Down Expand Up @@ -192,7 +192,7 @@ class MessagesPresenter @AssistedInject constructor(
)
}
is MessagesEvents.ToggleReaction -> {
localCoroutineScope.toggleReaction(event.emoji, event.eventId)
localCoroutineScope.toggleReaction(event.emoji, event.uniqueId)
}
is MessagesEvents.InviteDialogDismissed -> {
hasDismissedInviteDialog = true
Expand Down Expand Up @@ -313,10 +313,10 @@ class MessagesPresenter @AssistedInject constructor(

private fun CoroutineScope.toggleReaction(
emoji: String,
eventId: EventId,
uniqueId: UniqueId,
) = launch(dispatchers.io) {
timelineController.invokeOnCurrentTimeline {
toggleReaction(emoji, eventId)
toggleReaction(emoji, uniqueId)
.onFailure { Timber.e(it) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,7 @@ fun MessagesView(
}

fun onEmojiReactionClick(emoji: String, event: TimelineItem.Event) {
if (event.eventId == null) return
state.eventSink(MessagesEvents.ToggleReaction(emoji, event.eventId))
state.eventSink(MessagesEvents.ToggleReaction(emoji, event.id))
}

fun onEmojiReactionLongClick(emoji: String, event: TimelineItem.Event) {
Expand Down Expand Up @@ -248,16 +247,15 @@ fun MessagesView(
state = state.actionListState,
onSelectAction = ::onActionSelected,
onCustomReactionClick = { event ->
if (event.eventId == null) return@ActionListView
state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event))
},
onEmojiReactionClick = ::onEmojiReactionClick,
)

CustomReactionBottomSheet(
state = state.customReactionState,
onSelectEmoji = { eventId, emoji ->
state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, eventId))
onSelectEmoji = { uniqueId, emoji ->
state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, uniqueId))
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ class ActionListPresenter @Inject constructor(
isEventPinned = pinnedEventIds.contains(timelineItem.eventId),
)
val displayEmojiReactions = usersEventPermissions.canSendReaction &&
timelineItem.isRemote &&
timelineItem.content.canReact()
if (actions.isNotEmpty() || displayEmojiReactions) {
target.value = ActionListState.Target.Success(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.isDm
Expand Down Expand Up @@ -95,7 +96,7 @@ class TimelinePresenter @AssistedInject constructor(
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = syncUpdateFlow.value)

val prevMostRecentItemId = rememberSaveable { mutableStateOf<String?>(null) }
val prevMostRecentItemId = rememberSaveable { mutableStateOf<UniqueId?>(null) }

val newEventState = remember { mutableStateOf(NewEventState.None) }
val messageShield: MutableState<MessageShield?> = remember { mutableStateOf(null) }
Expand Down Expand Up @@ -242,7 +243,7 @@ class TimelinePresenter @AssistedInject constructor(
*/
private suspend fun computeNewItemState(
timelineItems: ImmutableList<TimelineItem>,
prevMostRecentItemId: MutableState<String?>,
prevMostRecentItemId: MutableState<UniqueId?>,
newEventState: MutableState<NewEventState>
) = withContext(dispatchers.computation) {
// FromMe is prioritized over FromOther, so skip if we already have a FromMe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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.core.TransactionId
import io.element.android.libraries.matrix.api.core.UniqueId
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.LocalEventSendState
Expand Down Expand Up @@ -122,7 +123,10 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
}

fun aTimelineItemDaySeparator(): TimelineItem.Virtual {
return TimelineItem.Virtual(UUID.randomUUID().toString(), aTimelineItemDaySeparatorModel("Today"))
return TimelineItem.Virtual(
id = UniqueId(UUID.randomUUID().toString()),
model = aTimelineItemDaySeparatorModel("Today"),
)
}

internal fun aTimelineItemEvent(
Expand All @@ -144,7 +148,7 @@ internal fun aTimelineItemEvent(
messageShield: MessageShield? = null,
): TimelineItem.Event {
return TimelineItem.Event(
id = UUID.randomUUID().toString(),
id = UniqueId(UUID.randomUUID().toString()),
eventId = eventId,
transactionId = transactionId,
senderId = UserId("@senderId:domain"),
Expand Down Expand Up @@ -210,7 +214,7 @@ internal fun aTimelineItemReadReceipts(
}

internal fun aGroupedEvents(
id: Long = 0,
id: UniqueId = UniqueId("0"),
withReadReceipts: Boolean = false,
): TimelineItem.GroupedEvents {
val event1 = aTimelineItemEvent(
Expand All @@ -231,7 +235,7 @@ internal fun aGroupedEvents(
)
val events = listOf(event1, event2)
return TimelineItem.GroupedEvents(
id = id.toString(),
id = id,
events = events.toImmutableList(),
aggregatedReadReceipts = events.flatMap { it.readReceiptState.receipts }.toImmutableList(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fun TimelineItemGroupedEventsRow(
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
modifier: Modifier = Modifier
) {
val isExpanded = rememberSaveable(key = timelineItem.identifier()) { mutableStateOf(false) }
val isExpanded = rememberSaveable(key = timelineItem.identifier().value) { mutableStateOf(false) }

fun onExpandGroupClick() {
isExpanded.value = !isExpanded.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import androidx.compose.ui.Modifier
import io.element.android.emojibasebindings.Emoji
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.hide
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomReactionBottomSheet(
state: CustomReactionState,
onSelectEmoji: (EventId, Emoji) -> Unit,
onSelectEmoji: (UniqueId, Emoji) -> Unit,
modifier: Modifier = Modifier,
) {
val sheetState = rememberModalBottomSheetState()
Expand All @@ -43,10 +43,10 @@ fun CustomReactionBottomSheet(
}

fun onEmojiSelectedDismiss(emoji: Emoji) {
if (target?.event?.eventId == null) return
if (target?.event == null) return
sheetState.hide(coroutineScope) {
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet)
onSelectEmoji(target.event.eventId, emoji)
onSelectEmoji(target.event.id, emoji)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.annotation.VisibleForTesting
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.core.UniqueId
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject

Expand Down Expand Up @@ -71,7 +72,7 @@ private fun MutableList<TimelineItem>.addGroup(
val groupId = groupIds.getOrPutGroupId(groupOfItems)
add(
TimelineItem.GroupedEvents(
id = groupId,
id = UniqueId(groupId),
events = groupOfItems.toImmutableList(),
aggregatedReadReceipts = groupOfItems.flatMap { it.readReceiptState.receipts }.toImmutableList()
)
Expand All @@ -83,15 +84,15 @@ private fun MutableMap<String, String>.getOrPutGroupId(timelineItems: List<Timel
assert(timelineItems.isNotEmpty())
for (item in timelineItems) {
val itemIdentifier = item.identifier()
if (this.contains(itemIdentifier)) {
return this[itemIdentifier]!!
if (this.contains(itemIdentifier.value)) {
return this[itemIdentifier.value]!!
}
}
val timelineItem = timelineItems.first()
return computeGroupIdWith(timelineItem).also { groupId ->
this[timelineItem.identifier()] = groupId
return computeGroupIdWith(timelineItem).value.also { groupId ->
this[timelineItem.identifier().value] = groupId
}
}

@VisibleForTesting
internal fun computeGroupIdWith(timelineItem: TimelineItem): String = "${timelineItem.identifier()}_group"
internal fun computeGroupIdWith(timelineItem: TimelineItem): UniqueId = UniqueId("${timelineItem.identifier()}_group")
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
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.TransactionId
import io.element.android.libraries.matrix.api.core.UniqueId
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.LocalEventSendState
Expand All @@ -36,7 +37,7 @@ import kotlinx.collections.immutable.ImmutableList

@Immutable
sealed interface TimelineItem {
fun identifier(): String = when (this) {
fun identifier(): UniqueId = when (this) {
is Event -> id
is Virtual -> id
is GroupedEvents -> id
Expand All @@ -58,13 +59,13 @@ sealed interface TimelineItem {

@Immutable
data class Virtual(
val id: String,
val id: UniqueId,
val model: TimelineItemVirtualModel
) : TimelineItem

@Immutable
data class Event(
val id: String,
val id: UniqueId,
// Note: eventId can be null when the event is a local echo
val eventId: EventId? = null,
val transactionId: TransactionId? = null,
Expand Down Expand Up @@ -101,7 +102,7 @@ sealed interface TimelineItem {

@Immutable
data class GroupedEvents(
val id: String,
val id: UniqueId,
val events: ImmutableList<Event>,
val aggregatedReadReceipts: ImmutableList<ReadReceiptData>,
) : TimelineItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ 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.core.TransactionId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
Expand All @@ -77,11 +78,11 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
Expand Down Expand Up @@ -197,8 +198,8 @@ class MessagesPresenterTest {
@Test
fun `present - handle toggling a reaction`() = runTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val toggleReactionSuccess = lambdaRecorder { _: String, _: EventId -> Result.success(Unit) }
val toggleReactionFailure = lambdaRecorder { _: String, _: EventId -> Result.failure<Unit>(IllegalStateException("Failed to send reaction")) }
val toggleReactionSuccess = lambdaRecorder { _: String, _: UniqueId -> Result.success(Unit) }
val toggleReactionFailure = lambdaRecorder { _: String, _: UniqueId -> Result.failure<Unit>(IllegalStateException("Failed to send reaction")) }

val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
Expand All @@ -218,25 +219,25 @@ class MessagesPresenterTest {
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
// No crashes when sending a reaction failed
timeline.apply { toggleReactionLambda = toggleReactionFailure }
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)

assert(toggleReactionSuccess)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID))
.with(value("👍"), value(A_UNIQUE_ID))
assert(toggleReactionFailure)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID))
.with(value("👍"), value(A_UNIQUE_ID))
}
}

@Test
fun `present - handle toggling a reaction twice`() = runTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val toggleReactionSuccess = lambdaRecorder { _: String, _: EventId -> Result.success(Unit) }
val toggleReactionSuccess = lambdaRecorder { _: String, _: UniqueId -> Result.success(Unit) }

val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
Expand All @@ -255,13 +256,13 @@ class MessagesPresenterTest {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
assert(toggleReactionSuccess)
.isCalledExactly(2)
.withSequence(
listOf(value("👍"), value(AN_EVENT_ID)),
listOf(value("👍"), value(AN_EVENT_ID)),
listOf(value("👍"), value(A_UNIQUE_ID)),
listOf(value("👍"), value(A_UNIQUE_ID)),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ class MessagesViewTest {
state = state,
)
rule.onAllNodesWithText("👍️").onFirst().performClick()
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.eventId!!))
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.id))
}

@Test
Expand Down Expand Up @@ -463,7 +463,7 @@ class MessagesViewTest {
// Give time for the close animation to complete
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
customReactionStateEventsRecorder.assertSingle(CustomReactionEvents.DismissCustomReactionSheet)
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction(aUnicode, timelineItem.eventId!!))
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction(aUnicode, timelineItem.id))
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
displayEmojiReactions = false,
displayEmojiReactions = true,
actions = persistentListOf(
TimelineItemAction.Edit,
TimelineItemAction.Copy,
Expand Down
Loading

0 comments on commit 9fb82a1

Please sign in to comment.