Update dependency org.matrix.rustcomponents:sdk-android to v0.2.41 (#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 <benoit@matrix.org> Co-authored-by: Benoit Marty <benoitm@matrix.org> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
parent
5920fd3f45
commit
9fb82a1e86
47 changed files with 237 additions and 168 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -248,7 +247,6 @@ fun MessagesView(
|
|||
state = state.actionListState,
|
||||
onSelectAction = ::onActionSelected,
|
||||
onCustomReactionClick = { event ->
|
||||
if (event.eventId == null) return@ActionListView
|
||||
state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event))
|
||||
},
|
||||
onEmojiReactionClick = ::onEmojiReactionClick,
|
||||
|
|
@ -256,8 +254,8 @@ fun MessagesView(
|
|||
|
||||
CustomReactionBottomSheet(
|
||||
state = state.customReactionState,
|
||||
onSelectEmoji = { eventId, emoji ->
|
||||
state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, eventId))
|
||||
onSelectEmoji = { uniqueId, emoji ->
|
||||
state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, uniqueId))
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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(
|
||||
|
|
@ -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"),
|
||||
|
|
@ -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(
|
||||
|
|
@ -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(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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()
|
||||
)
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,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.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
|
|
@ -51,7 +52,7 @@ internal fun aMessageEvent(
|
|||
sendState: LocalEventSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
||||
messageShield: MessageShield? = null,
|
||||
) = TimelineItem.Event(
|
||||
id = eventId?.value.orEmpty(),
|
||||
id = UniqueId(eventId?.value.orEmpty()),
|
||||
eventId = eventId,
|
||||
transactionId = transactionId,
|
||||
senderId = A_USER_ID,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
|||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
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_UNIQUE_ID
|
||||
import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
|
|
@ -80,7 +82,7 @@ class PinnedMessagesBannerPresenterTest {
|
|||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = "FAKE_UNIQUE_ID",
|
||||
uniqueId = A_UNIQUE_ID,
|
||||
event = anEventTimelineItem(
|
||||
eventId = AN_EVENT_ID,
|
||||
content = messageContent,
|
||||
|
|
@ -112,14 +114,14 @@ class PinnedMessagesBannerPresenterTest {
|
|||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = "FAKE_UNIQUE_ID",
|
||||
uniqueId = A_UNIQUE_ID,
|
||||
event = anEventTimelineItem(
|
||||
eventId = AN_EVENT_ID,
|
||||
content = messageContent1,
|
||||
),
|
||||
),
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = "FAKE_UNIQUE_ID_2",
|
||||
uniqueId = A_UNIQUE_ID_2,
|
||||
event = anEventTimelineItem(
|
||||
eventId = AN_EVENT_ID_2,
|
||||
content = messageContent2,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ class TimelineItemIndexerTest {
|
|||
groupedEvents.events.forEach { eventIds.add(it.eventId!!) }
|
||||
},
|
||||
TimelineItem.Virtual(
|
||||
id = "dummy",
|
||||
id = UniqueId("dummy"),
|
||||
model = TimelineItemReadMarkerModel
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import io.element.android.features.poll.api.actions.SendPollResponseAction
|
|||
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
||||
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
||||
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.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
|
|
@ -46,6 +47,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
|
|||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
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_UNIQUE_ID
|
||||
import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
|
|
@ -79,9 +82,6 @@ import java.util.Date
|
|||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID"
|
||||
private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class) class TimelinePresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
|
@ -131,7 +131,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
val timeline = FakeTimeline(
|
||||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -164,9 +164,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
val timeline = FakeTimeline(
|
||||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem()),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()),
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = FAKE_UNIQUE_ID_2,
|
||||
uniqueId = A_UNIQUE_ID_2,
|
||||
event = anEventTimelineItem(
|
||||
eventId = AN_EVENT_ID_2,
|
||||
content = aMessageContent("Test message")
|
||||
|
|
@ -201,9 +201,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
val timeline = FakeTimeline(
|
||||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem()),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()),
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = FAKE_UNIQUE_ID_2,
|
||||
uniqueId = A_UNIQUE_ID_2,
|
||||
event = anEventTimelineItem(
|
||||
eventId = AN_EVENT_ID_2,
|
||||
content = aMessageContent("Test message")
|
||||
|
|
@ -243,9 +243,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
val timeline = FakeTimeline(
|
||||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem()),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()),
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = FAKE_UNIQUE_ID_2,
|
||||
uniqueId = A_UNIQUE_ID_2,
|
||||
event = anEventTimelineItem(
|
||||
eventId = AN_EVENT_ID_2,
|
||||
content = aMessageContent("Test message")
|
||||
|
|
@ -279,8 +279,8 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
val timeline = FakeTimeline(
|
||||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Virtual(FAKE_UNIQUE_ID, VirtualTimelineItem.ReadMarker),
|
||||
MatrixTimelineItem.Virtual(FAKE_UNIQUE_ID, VirtualTimelineItem.ReadMarker)
|
||||
MatrixTimelineItem.Virtual(A_UNIQUE_ID, VirtualTimelineItem.ReadMarker),
|
||||
MatrixTimelineItem.Virtual(A_UNIQUE_ID, VirtualTimelineItem.ReadMarker)
|
||||
)
|
||||
)
|
||||
).apply {
|
||||
|
|
@ -310,13 +310,13 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
assertThat(initialState.newEventState).isEqualTo(NewEventState.None)
|
||||
assertThat(initialState.timelineItems.size).isEqualTo(0)
|
||||
timelineItems.emit(
|
||||
listOf(MatrixTimelineItem.Event("0", anEventTimelineItem(content = aMessageContent())))
|
||||
listOf(MatrixTimelineItem.Event(UniqueId("0"), anEventTimelineItem(content = aMessageContent())))
|
||||
)
|
||||
consumeItemsUntilPredicate { it.timelineItems.size == 1 }
|
||||
// Mimics sending a message, and assert newEventState is FromMe
|
||||
timelineItems.getAndUpdate { items ->
|
||||
val event = anEventTimelineItem(content = aMessageContent(), isOwn = true)
|
||||
items + listOf(MatrixTimelineItem.Event("1", event))
|
||||
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), event))
|
||||
}
|
||||
consumeItemsUntilPredicate { it.timelineItems.size == 2 }
|
||||
awaitLastSequentialItem().also { state ->
|
||||
|
|
@ -325,7 +325,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
// Mimics receiving a message without clearing the previous FromMe
|
||||
timelineItems.getAndUpdate { items ->
|
||||
val event = anEventTimelineItem(content = aMessageContent())
|
||||
items + listOf(MatrixTimelineItem.Event("2", event))
|
||||
items + listOf(MatrixTimelineItem.Event(UniqueId("2"), event))
|
||||
}
|
||||
consumeItemsUntilPredicate { it.timelineItems.size == 3 }
|
||||
|
||||
|
|
@ -337,7 +337,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
// Mimics receiving a message and assert newEventState is FromOther
|
||||
timelineItems.getAndUpdate { items ->
|
||||
val event = anEventTimelineItem(content = aMessageContent())
|
||||
items + listOf(MatrixTimelineItem.Event("3", event))
|
||||
items + listOf(MatrixTimelineItem.Event(UniqueId("3"), event))
|
||||
}
|
||||
consumeItemsUntilPredicate { it.timelineItems.size == 4 }
|
||||
awaitLastSequentialItem().also { state ->
|
||||
|
|
@ -381,7 +381,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
),
|
||||
)
|
||||
timelineItems.emit(
|
||||
listOf(MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(reactions = oneReaction)))
|
||||
listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(reactions = oneReaction)))
|
||||
)
|
||||
val item = awaitItem().timelineItems.first()
|
||||
assertThat(item).isInstanceOf(TimelineItem.Event::class.java)
|
||||
|
|
@ -476,7 +476,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = FAKE_UNIQUE_ID,
|
||||
uniqueId = A_UNIQUE_ID,
|
||||
event = anEventTimelineItem(),
|
||||
)
|
||||
)
|
||||
|
|
@ -532,7 +532,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = FAKE_UNIQUE_ID,
|
||||
uniqueId = A_UNIQUE_ID,
|
||||
event = anEventTimelineItem(eventId = AN_EVENT_ID),
|
||||
)
|
||||
)
|
||||
|
|
@ -618,7 +618,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
timelineItems = flowOf(
|
||||
listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
FAKE_UNIQUE_ID,
|
||||
A_UNIQUE_ID,
|
||||
anEventTimelineItem(
|
||||
sender = A_USER_ID,
|
||||
receipts = persistentListOf(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
|||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||
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.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
|
|
@ -54,7 +55,7 @@ class TimelineViewTest {
|
|||
state = aTimelineState(
|
||||
timelineItems = persistentListOf<TimelineItem>(
|
||||
TimelineItem.Virtual(
|
||||
id = "backward_pagination",
|
||||
id = UniqueId("backward_pagination"),
|
||||
model = TimelineItemLoadingIndicatorModel(Timeline.PaginationDirection.BACKWARDS, 0)
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemRead
|
|||
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.core.UniqueId
|
||||
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.A_USER_ID
|
||||
|
|
@ -37,7 +38,7 @@ class TimelineItemGrouperTest {
|
|||
private val sut = TimelineItemGrouper()
|
||||
|
||||
private val aGroupableItem = TimelineItem.Event(
|
||||
id = "0",
|
||||
id = UniqueId("0"),
|
||||
senderId = A_USER_ID,
|
||||
senderAvatar = anAvatarData(),
|
||||
senderProfile = aProfileTimelineDetailsReady(displayName = ""),
|
||||
|
|
@ -54,7 +55,7 @@ class TimelineItemGrouperTest {
|
|||
messageShield = null,
|
||||
)
|
||||
private val aNonGroupableItem = aMessageEvent()
|
||||
private val aNonGroupableItemNoEvent = TimelineItem.Virtual("virtual", aTimelineItemDaySeparatorModel("Today"))
|
||||
private val aNonGroupableItemNoEvent = TimelineItem.Virtual(UniqueId("virtual"), aTimelineItemDaySeparatorModel("Today"))
|
||||
|
||||
@Test
|
||||
fun `test empty`() {
|
||||
|
|
@ -82,8 +83,8 @@ class TimelineItemGrouperTest {
|
|||
fun `test groupables and ensure reordering`() {
|
||||
val result = sut.group(
|
||||
listOf(
|
||||
aGroupableItem.copy(id = "1"),
|
||||
aGroupableItem.copy(id = "0"),
|
||||
aGroupableItem.copy(id = UniqueId("1")),
|
||||
aGroupableItem.copy(id = UniqueId("0")),
|
||||
),
|
||||
)
|
||||
assertThat(result).isEqualTo(
|
||||
|
|
@ -91,8 +92,8 @@ class TimelineItemGrouperTest {
|
|||
TimelineItem.GroupedEvents(
|
||||
id = computeGroupIdWith(aGroupableItem),
|
||||
events = listOf(
|
||||
aGroupableItem.copy("0"),
|
||||
aGroupableItem.copy(id = "1"),
|
||||
aGroupableItem.copy(id = UniqueId("0")),
|
||||
aGroupableItem.copy(id = UniqueId("1")),
|
||||
).toImmutableList(),
|
||||
aggregatedReadReceipts = emptyList<ReadReceiptData>().toImmutableList(),
|
||||
),
|
||||
|
|
@ -161,13 +162,13 @@ class TimelineItemGrouperTest {
|
|||
fun `when calling multiple time the method group over a growing list of groupable items, then groupId is stable`() {
|
||||
// When
|
||||
val groupableItems = mutableListOf(
|
||||
aGroupableItem.copy(id = "1"),
|
||||
aGroupableItem.copy(id = "2")
|
||||
aGroupableItem.copy(id = UniqueId("1")),
|
||||
aGroupableItem.copy(id = UniqueId("2"))
|
||||
)
|
||||
val expectedGroupId = sut.group(groupableItems).first().identifier()
|
||||
groupableItems.add(0, aGroupableItem.copy("3"))
|
||||
groupableItems.add(2, aGroupableItem.copy("4"))
|
||||
groupableItems.add(aGroupableItem.copy("5"))
|
||||
groupableItems.add(0, aGroupableItem.copy(UniqueId("3")))
|
||||
groupableItems.add(2, aGroupableItem.copy(UniqueId("4")))
|
||||
groupableItems.add(aGroupableItem.copy(UniqueId("5")))
|
||||
val actualGroupId = sut.group(groupableItems).first().identifier()
|
||||
// Then
|
||||
assertThat(actualGroupId).isEqualTo(expectedGroupId)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.voicemessages.timeline
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
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.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
|
|
@ -80,7 +81,7 @@ fun TestScope.aDefaultRedactedVoiceMessageManager(
|
|||
|
||||
fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = "0",
|
||||
uniqueId = UniqueId("0"),
|
||||
event = EventTimelineItem(
|
||||
eventId = eventId,
|
||||
transactionId = null,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.poll.impl
|
||||
|
||||
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.poll.PollAnswer
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
|
|
@ -32,8 +33,8 @@ fun aPollTimelineItems(
|
|||
return flowOf(
|
||||
polls.map { entry ->
|
||||
MatrixTimelineItem.Event(
|
||||
entry.key.value,
|
||||
anEventTimelineItem(
|
||||
uniqueId = UniqueId(entry.key.value),
|
||||
event = anEventTimelineItem(
|
||||
eventId = entry.key,
|
||||
content = entry.value,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ jsoup = "org.jsoup:jsoup:1.18.1"
|
|||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
|
||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.40"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.41"
|
||||
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
|
|
|
|||
|
|
@ -18,6 +18,6 @@ package io.element.android.libraries.matrix.api.auth
|
|||
|
||||
sealed class AuthenticationException(message: String) : Exception(message) {
|
||||
class InvalidServerName(message: String) : AuthenticationException(message)
|
||||
class SlidingSyncNotAvailable(message: String) : AuthenticationException(message)
|
||||
class SlidingSyncVersion(message: String) : AuthenticationException(message)
|
||||
class Generic(message: String) : AuthenticationException(message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class UniqueId(val value: String) : Serializable {
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
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.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
|
|
@ -155,7 +156,7 @@ interface MatrixRoom : Closeable {
|
|||
|
||||
suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
|
||||
|
||||
suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit>
|
||||
suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit>
|
||||
|
||||
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,15 +18,16 @@ package io.element.android.libraries.matrix.api.timeline
|
|||
|
||||
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.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
|
||||
sealed interface MatrixTimelineItem {
|
||||
data class Event(val uniqueId: String, val event: EventTimelineItem) : MatrixTimelineItem {
|
||||
data class Event(val uniqueId: UniqueId, val event: EventTimelineItem) : MatrixTimelineItem {
|
||||
val eventId: EventId? = event.eventId
|
||||
val transactionId: TransactionId? = event.transactionId
|
||||
}
|
||||
|
||||
data class Virtual(val uniqueId: String, val virtual: VirtualTimelineItem) : MatrixTimelineItem
|
||||
data class Virtual(val uniqueId: UniqueId, val virtual: VirtualTimelineItem) : MatrixTimelineItem
|
||||
data object Other : MatrixTimelineItem
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
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.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
|
|
@ -97,7 +98,7 @@ interface Timeline : AutoCloseable {
|
|||
|
||||
suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
|
||||
|
||||
suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit>
|
||||
suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit>
|
||||
|
||||
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ data class TracingFilterConfiguration(
|
|||
Target.MATRIX_SDK_SLIDING_SYNC to LogLevel.INFO,
|
||||
Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.INFO,
|
||||
Target.MATRIX_SDK_UI_TIMELINE to LogLevel.INFO,
|
||||
Target.MATRIX_SDK_BASE_CLIENT to LogLevel.TRACE,
|
||||
// To debug OIDC logouts
|
||||
Target.MATRIX_SDK_OIDC to LogLevel.TRACE,
|
||||
)
|
||||
|
|
@ -69,6 +70,7 @@ enum class Target(open val filter: String) {
|
|||
MATRIX_SDK_BASE_SLIDING_SYNC("matrix_sdk_base::sliding_sync"),
|
||||
MATRIX_SDK_UI_TIMELINE("matrix_sdk_ui::timeline"),
|
||||
MATRIX_SDK_BASE_READ_RECEIPTS("matrix_sdk_base::read_receipts"),
|
||||
MATRIX_SDK_BASE_CLIENT("matrix_sdk_base"),
|
||||
}
|
||||
|
||||
enum class LogLevel(open val filter: String) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl
|
||||
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.CacheDirectory
|
||||
import io.element.android.libraries.matrix.impl.analytics.UtdTracker
|
||||
|
|
@ -34,6 +35,8 @@ import kotlinx.coroutines.flow.first
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncVersion
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
|
@ -56,11 +59,11 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
val client = getBaseClientBuilder(
|
||||
sessionPaths = sessionData.getSessionPaths(),
|
||||
passphrase = sessionData.passphrase,
|
||||
slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) {
|
||||
ClientBuilderSlidingSync.Simplified
|
||||
} else {
|
||||
ClientBuilderSlidingSync.Restored
|
||||
},
|
||||
slidingSync = when {
|
||||
AuthenticationConfig.SLIDING_SYNC_PROXY_URL != null -> ClientBuilderSlidingSync.CustomProxy(AuthenticationConfig.SLIDING_SYNC_PROXY_URL!!)
|
||||
appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first() -> ClientBuilderSlidingSync.Simplified
|
||||
else -> ClientBuilderSlidingSync.Restored
|
||||
}
|
||||
)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
.username(sessionData.userId)
|
||||
|
|
@ -91,7 +94,6 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
internal fun getBaseClientBuilder(
|
||||
sessionPaths: SessionPaths,
|
||||
passphrase: String?,
|
||||
slidingSyncProxy: String? = null,
|
||||
slidingSync: ClientBuilderSlidingSync,
|
||||
): ClientBuilder {
|
||||
return ClientBuilder()
|
||||
|
|
@ -100,16 +102,17 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
cachePath = sessionPaths.cacheDirectory.absolutePath,
|
||||
)
|
||||
.passphrase(passphrase)
|
||||
.slidingSyncProxy(slidingSyncProxy)
|
||||
.userAgent(userAgentProvider.provide())
|
||||
.addRootCertificates(userCertificatesProvider.provides())
|
||||
.autoEnableBackups(true)
|
||||
.autoEnableCrossSigning(true)
|
||||
.run {
|
||||
// Apply sliding sync version settings
|
||||
when (slidingSync) {
|
||||
ClientBuilderSlidingSync.Restored -> this
|
||||
ClientBuilderSlidingSync.Discovered -> requiresSlidingSync()
|
||||
ClientBuilderSlidingSync.Simplified -> simplifiedSlidingSync(true)
|
||||
is ClientBuilderSlidingSync.CustomProxy -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Proxy(slidingSync.url))
|
||||
ClientBuilderSlidingSync.Discovered -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.DiscoverProxy)
|
||||
ClientBuilderSlidingSync.Simplified -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Native)
|
||||
}
|
||||
}
|
||||
.run {
|
||||
|
|
@ -119,15 +122,18 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
enum class ClientBuilderSlidingSync {
|
||||
sealed interface ClientBuilderSlidingSync {
|
||||
// The proxy is set by the user.
|
||||
data class CustomProxy(val url: String) : ClientBuilderSlidingSync
|
||||
|
||||
// The proxy will be supplied when restoring the Session.
|
||||
Restored,
|
||||
data object Restored : ClientBuilderSlidingSync
|
||||
|
||||
// A proxy must be discovered whilst building the session.
|
||||
Discovered,
|
||||
data object Discovered : ClientBuilderSlidingSync
|
||||
|
||||
// Use Simplified Sliding Sync (discovery isn't a thing yet).
|
||||
Simplified,
|
||||
data object Simplified : ClientBuilderSlidingSync
|
||||
}
|
||||
|
||||
private fun SessionData.toSession() = Session(
|
||||
|
|
@ -136,6 +142,6 @@ private fun SessionData.toSession() = Session(
|
|||
userId = userId,
|
||||
deviceId = deviceId,
|
||||
homeserverUrl = homeserverUrl,
|
||||
slidingSyncProxy = slidingSyncProxy,
|
||||
slidingSyncVersion = slidingSyncProxy?.let(SlidingSyncVersion::Proxy) ?: SlidingSyncVersion.Native,
|
||||
oidcData = oidcData,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fun Throwable.mapAuthenticationException(): AuthenticationException {
|
|||
return when (this) {
|
||||
is RustAuthenticationException.Generic -> AuthenticationException.Generic(message)
|
||||
is RustAuthenticationException.InvalidServerName -> AuthenticationException.InvalidServerName(message)
|
||||
is RustAuthenticationException.SlidingSyncNotAvailable -> AuthenticationException.SlidingSyncNotAvailable(message)
|
||||
is RustAuthenticationException.SlidingSyncVersion -> AuthenticationException.SlidingSyncVersion(message)
|
||||
else -> AuthenticationException.Generic(message)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
|
@ -223,7 +222,6 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
val client = rustMatrixClientFactory.getBaseClientBuilder(
|
||||
sessionPaths = emptySessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
|
||||
slidingSync = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
.buildWithQrCode(
|
||||
|
|
@ -267,7 +265,6 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
|
||||
slidingSync = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
|||
import io.element.android.libraries.sessionstorage.api.LoginType
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncVersion
|
||||
import java.util.Date
|
||||
|
||||
internal fun Session.toSessionData(
|
||||
|
|
@ -35,7 +36,7 @@ internal fun Session.toSessionData(
|
|||
refreshToken = refreshToken,
|
||||
homeserverUrl = homeserverUrl ?: this.homeserverUrl,
|
||||
oidcData = oidcData,
|
||||
slidingSyncProxy = slidingSyncProxy,
|
||||
slidingSyncProxy = (slidingSyncVersion as? SlidingSyncVersion.Proxy)?.url,
|
||||
loginTimestamp = Date(),
|
||||
isTokenValid = isTokenValid,
|
||||
loginType = loginType,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
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.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
|
|
@ -459,8 +460,8 @@ class RustMatrixRoom(
|
|||
return liveTimeline.sendFile(file, fileInfo, progressCallback)
|
||||
}
|
||||
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> {
|
||||
return liveTimeline.toggleReaction(emoji, eventId)
|
||||
override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit> {
|
||||
return liveTimeline.toggleReaction(emoji, uniqueId)
|
||||
}
|
||||
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.libraries.matrix.impl.timeline
|
||||
|
||||
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.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
|
||||
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
|
||||
|
|
@ -31,7 +32,7 @@ class MatrixTimelineItemMapper(
|
|||
private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(),
|
||||
) {
|
||||
fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use {
|
||||
val uniqueId = timelineItem.uniqueId()
|
||||
val uniqueId = UniqueId(timelineItem.uniqueId())
|
||||
val asEvent = it.asEvent()
|
||||
if (asEvent != null) {
|
||||
val eventTimelineItem = eventTimelineItemMapper.map(asEvent)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
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.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
|
|
@ -391,9 +392,9 @@ class RustTimeline(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> = withContext(dispatcher) {
|
||||
override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.toggleReaction(key = emoji, eventId = eventId.value)
|
||||
inner.toggleReaction(key = emoji, uniqueId = uniqueId.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime
|
|||
class LastForwardIndicatorsPostProcessor(
|
||||
private val isTimelineLive: Boolean,
|
||||
) {
|
||||
private val lastForwardIdentifiers = LinkedHashSet<String>()
|
||||
private val lastForwardIdentifiers = LinkedHashSet<UniqueId>()
|
||||
|
||||
fun process(
|
||||
items: List<MatrixTimelineItem>,
|
||||
|
|
@ -56,17 +57,17 @@ class LastForwardIndicatorsPostProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createLastForwardIndicator(identifier: String): MatrixTimelineItem {
|
||||
private fun createLastForwardIndicator(identifier: UniqueId): MatrixTimelineItem {
|
||||
return MatrixTimelineItem.Virtual(
|
||||
uniqueId = "last_forward_indicator_$identifier",
|
||||
uniqueId = UniqueId("last_forward_indicator_$identifier"),
|
||||
virtual = VirtualTimelineItem.LastForwardIndicator
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<MatrixTimelineItem>.latestEventIdentifier(): String {
|
||||
private fun List<MatrixTimelineItem>.latestEventIdentifier(): UniqueId {
|
||||
return findLast {
|
||||
it is MatrixTimelineItem.Event
|
||||
}?.let {
|
||||
(it as MatrixTimelineItem.Event).uniqueId
|
||||
} ?: "fake_id"
|
||||
} ?: UniqueId("fake_id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
|
|
@ -33,7 +34,7 @@ class LoadingIndicatorsPostProcessor(private val systemClock: SystemClock) {
|
|||
return buildList {
|
||||
if (shouldAddBackwardLoadingIndicator) {
|
||||
val backwardLoadingIndicator = MatrixTimelineItem.Virtual(
|
||||
uniqueId = "BackwardLoadingIndicator",
|
||||
uniqueId = UniqueId("BackwardLoadingIndicator"),
|
||||
virtual = VirtualTimelineItem.LoadingIndicator(
|
||||
direction = Timeline.PaginationDirection.BACKWARDS,
|
||||
timestamp = currentTimestamp
|
||||
|
|
@ -44,7 +45,7 @@ class LoadingIndicatorsPostProcessor(private val systemClock: SystemClock) {
|
|||
addAll(items)
|
||||
if (shouldAddForwardLoadingIndicator) {
|
||||
val forwardLoadingIndicator = MatrixTimelineItem.Virtual(
|
||||
uniqueId = "ForwardLoadingIndicator",
|
||||
uniqueId = UniqueId("ForwardLoadingIndicator"),
|
||||
virtual = VirtualTimelineItem.LoadingIndicator(
|
||||
direction = Timeline.PaginationDirection.FORWARDS,
|
||||
timestamp = currentTimestamp
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
|
||||
|
|
@ -77,7 +78,7 @@ class RoomBeginningPostProcessor {
|
|||
@VisibleForTesting
|
||||
fun createRoomBeginningItem(): MatrixTimelineItem.Virtual {
|
||||
return MatrixTimelineItem.Virtual(
|
||||
uniqueId = VirtualTimelineItem.RoomBeginning.toString(),
|
||||
uniqueId = UniqueId("RoomBeginning"),
|
||||
virtual = VirtualTimelineItem.RoomBeginning
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
|
@ -23,6 +24,8 @@ import kotlinx.coroutines.withContext
|
|||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
|
||||
internal val encryptedHistoryBannerId = UniqueId("EncryptedHistoryBannerId")
|
||||
|
||||
class TimelineEncryptedHistoryPostProcessor(
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
private val lastLoginTimestamp: Date?,
|
||||
|
|
@ -44,7 +47,7 @@ class TimelineEncryptedHistoryPostProcessor(
|
|||
}
|
||||
return if (lastEncryptedHistoryBannerIndex >= 0) {
|
||||
val sublist = list.drop(lastEncryptedHistoryBannerIndex + 1).toMutableList()
|
||||
sublist.add(0, MatrixTimelineItem.Virtual(VirtualTimelineItem.EncryptedHistoryBanner.toString(), VirtualTimelineItem.EncryptedHistoryBanner))
|
||||
sublist.add(0, MatrixTimelineItem.Virtual(encryptedHistoryBannerId, VirtualTimelineItem.EncryptedHistoryBanner))
|
||||
sublist
|
||||
} else {
|
||||
list
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ class AuthenticationExceptionMappingTest {
|
|||
assertThat(ClientBuildException.Sdk("SDK issue").mapAuthenticationException())
|
||||
.isException<AuthenticationException.Generic>("SDK issue")
|
||||
|
||||
assertThat(ClientBuildException.SlidingSyncNotAvailable("Sliding sync not available").mapAuthenticationException())
|
||||
.isException<AuthenticationException.SlidingSyncNotAvailable>("Sliding sync not available")
|
||||
assertThat(ClientBuildException.SlidingSyncVersion("Sliding sync not available").mapAuthenticationException())
|
||||
.isException<AuthenticationException.SlidingSyncVersion>("Sliding sync not available")
|
||||
}
|
||||
|
||||
private inline fun <reified T> ThrowableSubject.isException(message: String) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
|
||||
|
|
@ -33,8 +34,8 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor removes room creation event and self-join event from DM timeline`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false)
|
||||
|
|
@ -44,14 +45,20 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor removes room creation event and self-join event from DM timeline even if they're not the first items`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event(
|
||||
UniqueId("m.room.member_other"),
|
||||
anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))
|
||||
),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.message"), anEventTimelineItem(content = aMessageContent("hi"))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val expected = listOf(
|
||||
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
|
||||
MatrixTimelineItem.Event(
|
||||
UniqueId("m.room.member_other"),
|
||||
anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))
|
||||
),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.message"), anEventTimelineItem(content = aMessageContent("hi"))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false)
|
||||
|
|
@ -61,8 +68,8 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor will add beginning of room item if it's not a DM`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = false)
|
||||
|
|
@ -74,7 +81,7 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor will not add beginning of room item if it's not a DM and EncryptedHistoryBanner item is found`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Virtual("EncryptedHistoryBanner", VirtualTimelineItem.EncryptedHistoryBanner),
|
||||
MatrixTimelineItem.Virtual(UniqueId("EncryptedHistoryBanner"), VirtualTimelineItem.EncryptedHistoryBanner),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = false)
|
||||
|
|
@ -84,8 +91,8 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor won't remove items if it's not at the start of the timeline`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true)
|
||||
|
|
@ -95,7 +102,7 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor won't remove the first member join event if it can't find the room creation event`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true)
|
||||
|
|
@ -105,8 +112,11 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor won't remove the first member join event if it's not from the room creator`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event(
|
||||
UniqueId("m.room.member"),
|
||||
anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))
|
||||
),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
|
||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
|
@ -26,8 +27,6 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.Test
|
||||
import java.util.Date
|
||||
|
||||
private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID"
|
||||
|
||||
class TimelineEncryptedHistoryPostProcessorTest {
|
||||
private val defaultLastLoginTimestamp = Date(1_689_061_264L)
|
||||
|
||||
|
|
@ -35,7 +34,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
|||
fun `given an unencrypted room, nothing is done`() = runTest {
|
||||
val processor = createPostProcessor(isRoomEncrypted = false)
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())
|
||||
)
|
||||
assertThat(processor.process(items)).isSameInstanceAs(items)
|
||||
}
|
||||
|
|
@ -44,7 +43,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
|||
fun `given an encrypted room, and key backup enabled, nothing is done`() = runTest {
|
||||
val processor = createPostProcessor(isKeyBackupEnabled = true)
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())
|
||||
)
|
||||
assertThat(processor.process(items)).isSameInstanceAs(items)
|
||||
}
|
||||
|
|
@ -53,7 +52,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
|||
fun `given a null lastLoginTimestamp, nothing is done`() = runTest {
|
||||
val processor = createPostProcessor(lastLoginTimestamp = null)
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())
|
||||
)
|
||||
assertThat(processor.process(items)).isSameInstanceAs(items)
|
||||
}
|
||||
|
|
@ -69,7 +68,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
|||
fun `given a list with no items before lastLoginTimestamp, nothing is done`() = runTest {
|
||||
val processor = createPostProcessor()
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
|
||||
)
|
||||
assertThat(processor.process(items)).isSameInstanceAs(items)
|
||||
}
|
||||
|
|
@ -78,20 +77,20 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
|||
fun `given a list with an item with equal timestamp as lastLoginTimestamp, it's replaced`() = runTest {
|
||||
val processor = createPostProcessor()
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time))
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time))
|
||||
)
|
||||
assertThat(processor.process(items))
|
||||
.isEqualTo(listOf(MatrixTimelineItem.Virtual(VirtualTimelineItem.EncryptedHistoryBanner.toString(), VirtualTimelineItem.EncryptedHistoryBanner)))
|
||||
.isEqualTo(listOf(MatrixTimelineItem.Virtual(encryptedHistoryBannerId, VirtualTimelineItem.EncryptedHistoryBanner)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a list with an item with a lower timestamp than lastLoginTimestamp, it's replaced`() = runTest {
|
||||
val processor = createPostProcessor()
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1))
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1))
|
||||
)
|
||||
assertThat(processor.process(items)).isEqualTo(
|
||||
listOf(MatrixTimelineItem.Virtual(VirtualTimelineItem.EncryptedHistoryBanner.toString(), VirtualTimelineItem.EncryptedHistoryBanner))
|
||||
listOf(MatrixTimelineItem.Virtual(encryptedHistoryBannerId, VirtualTimelineItem.EncryptedHistoryBanner))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -99,14 +98,14 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
|||
fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, then they're replaced`() = runTest {
|
||||
val processor = createPostProcessor()
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)),
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)),
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1)),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1)),
|
||||
)
|
||||
assertThat(processor.process(items)).isEqualTo(
|
||||
listOf(
|
||||
MatrixTimelineItem.Virtual(VirtualTimelineItem.EncryptedHistoryBanner.toString(), VirtualTimelineItem.EncryptedHistoryBanner),
|
||||
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
|
||||
MatrixTimelineItem.Virtual(encryptedHistoryBannerId, VirtualTimelineItem.EncryptedHistoryBanner),
|
||||
MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
|||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
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.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||
|
|
@ -55,7 +56,9 @@ val AN_EVENT_ID = EventId("\$anEventId")
|
|||
val AN_EVENT_ID_2 = EventId("\$anEventId2")
|
||||
val A_ROOM_ALIAS = RoomAlias("#alias1:domain")
|
||||
val A_TRANSACTION_ID = TransactionId("aTransactionId")
|
||||
const val A_UNIQUE_ID = "aUniqueId"
|
||||
|
||||
val A_UNIQUE_ID = UniqueId("aUniqueId")
|
||||
val A_UNIQUE_ID_2 = UniqueId("aUniqueId2")
|
||||
|
||||
const val A_ROOM_NAME = "A room name"
|
||||
const val A_ROOM_RAW_NAME = "A room raw name"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
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.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
|
|
@ -108,7 +109,7 @@ class FakeMatrixRoom(
|
|||
private val editMessageLambda: (EventId, String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
|
||||
private val sendMessageResult: (String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
||||
private val updateUserRoleResult: () -> Result<Unit> = { lambdaError() },
|
||||
private val toggleReactionResult: (String, EventId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val toggleReactionResult: (String, UniqueId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val retrySendMessageResult: (TransactionId) -> Result<Unit> = { lambdaError() },
|
||||
private val cancelSendResult: (TransactionId) -> Result<Boolean> = { lambdaError() },
|
||||
private val forwardEventResult: (EventId, List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
|
|
@ -230,8 +231,8 @@ class FakeMatrixRoom(
|
|||
sendMessageResult(body, htmlBody, intentionalMentions)
|
||||
}
|
||||
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> {
|
||||
return toggleReactionResult(emoji, eventId)
|
||||
override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit> {
|
||||
return toggleReactionResult(emoji, uniqueId)
|
||||
}
|
||||
|
||||
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
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.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
|
|
@ -219,8 +220,8 @@ class FakeTimeline(
|
|||
progressCallback
|
||||
)
|
||||
|
||||
var toggleReactionLambda: (emoji: String, eventId: EventId) -> Result<Unit> = { _, _ -> Result.success(Unit) }
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> = toggleReactionLambda(emoji, eventId)
|
||||
var toggleReactionLambda: (emoji: String, uniqueId: UniqueId) -> Result<Unit> = { _, _ -> Result.success(Unit) }
|
||||
override suspend fun toggleReaction(emoji: String, uniqueId: UniqueId): Result<Unit> = toggleReactionLambda(emoji, uniqueId)
|
||||
|
||||
var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> Result.success(Unit) }
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = forwardEventLambda(eventId, roomIds)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue