From bc32cf1d4a14199bde376510f292c672cb8d5852 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:52:05 +0300 Subject: [PATCH 01/30] Let the user give a reason when removing a message The protocol carries a reason on a redaction and other clients let the sender give one, but removing a message here sent the redaction straight away with no reason and no confirmation. Removing a message now opens a dialog with an optional reason, and the redaction is only sent once it is confirmed. A blank reason is sent as none, so confirming without typing anything behaves exactly as before. A message which was never sent has nobody to give a reason to, so the send failure dialog's Remove still redacts it immediately. --- .../features/messages/impl/MessagesEvent.kt | 2 + .../messages/impl/MessagesPresenter.kt | 32 +++++++- .../features/messages/impl/MessagesState.kt | 3 + .../impl/MessagesStatePreviewParam.kt | 4 + .../features/messages/impl/MessagesView.kt | 14 ++++ .../messages/impl/MessagesPresenterTest.kt | 77 ++++++++++++++++++- 6 files changed, 128 insertions(+), 4 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt index 4d621e417f..38aa5b75ce 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt @@ -18,6 +18,8 @@ sealed interface MessagesEvent { data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvent data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvent data class OnUserClicked(val user: MatrixUser) : MessagesEvent + data class ConfirmRedact(val reason: String?) : MessagesEvent + data object CancelRedact : MessagesEvent data object StopLiveLocationShare : MessagesEvent data object ShowLiveLocationShare : MessagesEvent data object MarkAsFullyReadAndExit : MessagesEvent diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 3ba8099ecb..6ad00af627 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -240,6 +240,8 @@ class MessagesPresenter( onPauseOrDispose {} } + val eventToRedact = remember { mutableStateOf(null) } + fun handleEvent(event: MessagesEvent) { when (event) { is MessagesEvent.HandleAction -> { @@ -250,8 +252,21 @@ class MessagesPresenter( enableTextFormatting = composerState.showTextFormatting, timelineState = timelineState, timelineProtectionState = timelineProtectionState, + eventToRedact = eventToRedact, ) } + is MessagesEvent.ConfirmRedact -> { + val target = eventToRedact.value + eventToRedact.value = null + if (target != null) { + localCoroutineScope.launch { + redact(target, event.reason?.takeIf { it.isNotBlank() }) + } + } + } + MessagesEvent.CancelRedact -> { + eventToRedact.value = null + } is MessagesEvent.ToggleReaction -> { localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId) } @@ -330,6 +345,7 @@ class MessagesPresenter( hasUnreadThreads = false, ), showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom && timelineState.timelineMode !is Timeline.Mode.Thread, + eventToRedact = eventToRedact.value, eventSink = ::handleEvent, ) } @@ -368,12 +384,13 @@ class MessagesPresenter( timelineProtectionState: TimelineProtectionState, enableTextFormatting: Boolean, timelineState: TimelineState, + eventToRedact: MutableState, ) = launch { when (action) { TimelineItemAction.CopyText -> handleCopyContents(targetEvent) TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent) TimelineItemAction.CopyLink -> handleCopyLink(targetEvent) - TimelineItemAction.Redact -> handleActionRedact(targetEvent) + TimelineItemAction.Redact -> handleActionRedact(targetEvent, eventToRedact) TimelineItemAction.Edit, TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState) @@ -495,9 +512,18 @@ class MessagesPresenter( ) } - private suspend fun handleActionRedact(event: TimelineItem.Event) { + private suspend fun handleActionRedact(event: TimelineItem.Event, eventToRedact: MutableState) { + if (event.eventId == null) { + // The message was never sent, so there is nobody to give a reason to. + redact(event, reason = null) + } else { + eventToRedact.value = event + } + } + + private suspend fun redact(event: TimelineItem.Event, reason: String?) { timelineController.invokeOnCurrentTimeline { - redactEvent(eventOrTransactionId = event.eventOrTransactionId, reason = null) + redactEvent(eventOrTransactionId = event.eventOrTransactionId, reason = reason) .onFailure { Timber.e(it) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index d9ec875320..4fd3423b6c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -18,6 +18,7 @@ import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState +import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState @@ -61,6 +62,8 @@ data class MessagesState( val successorRoom: SuccessorRoom?, val threads: Threads, val showLiveLocationShareBanner: Boolean, + /** The event the user asked to remove, waiting for them to confirm and optionally give a reason. */ + val eventToRedact: TimelineItem.Event?, val eventSink: (MessagesEvent) -> Unit ) { val isTombstoned = successorRoom != null diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt index d792ab5ed4..c66d04564c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt @@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.messagecomposer.aMessageCompose import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.TimelineState +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent @@ -92,6 +93,7 @@ open class MessagesStatePreviewParam : PreviewParameterProvider { composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())) ), + aMessagesState(eventToRedact = aTimelineItemEvent()), ) } @@ -131,6 +133,7 @@ fun aMessagesState( isCurrentlySharingLiveLocationInRoom: Boolean = false, dmUserStatus: DisplayedStatus? = null, eventSink: (MessagesEvent) -> Unit = {}, + eventToRedact: TimelineItem.Event? = null, ) = MessagesState( roomId = RoomId("!id:domain"), roomName = roomName, @@ -161,6 +164,7 @@ fun aMessagesState( threads = threads, showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom, dmUserStatus = dmUserStatus, + eventToRedact = eventToRedact, eventSink = eventSink, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 16ca023d50..391db3c0d2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -104,6 +104,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertM import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayoutState import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.TextFieldDialog import io.element.android.libraries.designsystem.components.rememberExpandableBottomSheetLayoutState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -455,6 +456,19 @@ fun MessagesView( state.timelineState.eventSink(TimelineEvent.HideSendFailureDialog) }, ) + + if (state.eventToRedact != null) { + TextFieldDialog( + title = stringResource(CommonStrings.action_remove_message), + label = stringResource(CommonStrings.common_reason), + placeholder = null, + value = null, + submitText = stringResource(CommonStrings.action_remove), + destructiveSubmit = true, + onSubmit = { reason -> state.eventSink(MessagesEvent.ConfirmRedact(reason)) }, + onDismissRequest = { state.eventSink(MessagesEvent.CancelRedact) }, + ) + } } @Composable diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 227088f78c..b2682db464 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -78,10 +78,12 @@ 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.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_CAPTION +import io.element.android.libraries.matrix.test.A_REASON 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_THREAD_ID +import io.element.android.libraries.matrix.test.A_TRANSACTION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.core.FakeSendHandle @@ -557,13 +559,86 @@ class MessagesPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent() initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent)) - awaitItem() + advanceUntilIdle() + val confirmingState = expectMostRecentItem() + assertThat(confirmingState.eventToRedact).isEqualTo(messageEvent) + assert(redactEventLambda).isNeverCalled() + confirmingState.eventSink(MessagesEvent.ConfirmRedact(A_REASON)) + advanceUntilIdle() + assertThat(expectMostRecentItem().eventToRedact).isNull() + assert(redactEventLambda) + .isCalledOnce() + .with(value(messageEvent.eventOrTransactionId), value(A_REASON)) + } + } + + @Test + fun `present - handle action redact - a blank reason is not sent`() = runTest { + val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + val liveTimeline = FakeTimeline() + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } + liveTimeline.redactEventLambda = redactEventLambda + val presenter = createMessagesPresenter( + timeline = liveTimeline, + coroutineDispatchers = coroutineDispatchers, + ) + presenter.testWithLifecycleOwner { + val initialState = awaitItem() + val messageEvent = aMessageEvent() + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent)) + advanceUntilIdle() + expectMostRecentItem().eventSink(MessagesEvent.ConfirmRedact(" ")) + advanceUntilIdle() + assertThat(expectMostRecentItem().eventToRedact).isNull() assert(redactEventLambda) .isCalledOnce() .with(value(messageEvent.eventOrTransactionId), value(null)) } } + @Test + fun `present - handle action redact - cancelling does not redact`() = runTest { + val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + val liveTimeline = FakeTimeline() + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } + liveTimeline.redactEventLambda = redactEventLambda + val presenter = createMessagesPresenter( + timeline = liveTimeline, + coroutineDispatchers = coroutineDispatchers, + ) + presenter.testWithLifecycleOwner { + val initialState = awaitItem() + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, aMessageEvent())) + advanceUntilIdle() + expectMostRecentItem().eventSink(MessagesEvent.CancelRedact) + advanceUntilIdle() + assertThat(expectMostRecentItem().eventToRedact).isNull() + assert(redactEventLambda).isNeverCalled() + } + } + + @Test + fun `present - handle action redact - a message which was never sent is redacted without asking`() = runTest { + val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + val liveTimeline = FakeTimeline() + val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } + liveTimeline.redactEventLambda = redactEventLambda + val presenter = createMessagesPresenter( + timeline = liveTimeline, + coroutineDispatchers = coroutineDispatchers, + ) + presenter.testWithLifecycleOwner { + val initialState = awaitItem() + val localEcho = aMessageEvent(eventId = null, transactionId = A_TRANSACTION_ID) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, localEcho)) + advanceUntilIdle() + assertThat(expectMostRecentItem().eventToRedact).isNull() + assert(redactEventLambda) + .isCalledOnce() + .with(value(localEcho.eventOrTransactionId), value(null)) + } + } + @Test fun `present - handle action report content`() = runTest { val onReportContentClickLambda = lambdaRecorder { _: EventId, _: UserId -> } From 1f4d8b1733ddb5b292195f88774a6f2d31e32aa0 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:14:52 +0300 Subject: [PATCH 02/30] Update screenshots --- .../images/features.messages.impl_MessagesView_Day_12_en.png | 3 +++ .../images/features.messages.impl_MessagesView_Night_12_en.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png new file mode 100644 index 0000000000..187081f077 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0291b33e495c88fc86b90278dbaecf4811c04b17659422c4e3ac43d10817b011 +size 47592 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png new file mode 100644 index 0000000000..af6d9fa1fb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b80dd96314a0f61f1a8a92f9bd53582cb20de2f7835a825f8ea4cfa37859e06 +size 42085 From 74a91a73487fd0ea4c0363b7a588d73901e0e166 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:21:09 +0300 Subject: [PATCH 03/30] Give the removal confirmation the dialog the team asked for The dialog now reads "Confirm removal" over "Are you sure you wish to remove this message?", with the reason field marked as optional beneath it, rather than a bare title and a labelled field. TextFieldDialog gained a supporting text so the field can carry that note. The pending removal moves from a nullable timeline item to redactEventAction, an AsyncAction whose Confirming state carries the event id, so it is modelled the same way as every other action the user has to confirm. --- .../messages/impl/MessagesPresenter.kt | 35 ++++++++++--------- .../features/messages/impl/MessagesState.kt | 11 ++++-- .../impl/MessagesStatePreviewParam.kt | 9 ++--- .../features/messages/impl/MessagesView.kt | 9 ++--- .../messages/impl/MessagesPresenterTest.kt | 11 +++--- .../components/dialogs/TextFieldDialog.kt | 2 ++ .../components/list/TextFieldListItem.kt | 3 +- .../src/main/res/values/temporary.xml | 3 ++ 8 files changed, 50 insertions(+), 33 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 6ad00af627..7a5048d1cd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -57,6 +57,7 @@ import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.androidutils.clipboard.ClipboardHelper +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -83,6 +84,7 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.model.dmUserStatus import io.element.android.libraries.matrix.ui.model.getAvatarData @@ -240,7 +242,7 @@ class MessagesPresenter( onPauseOrDispose {} } - val eventToRedact = remember { mutableStateOf(null) } + val redactEventAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } fun handleEvent(event: MessagesEvent) { when (event) { @@ -252,20 +254,20 @@ class MessagesPresenter( enableTextFormatting = composerState.showTextFormatting, timelineState = timelineState, timelineProtectionState = timelineProtectionState, - eventToRedact = eventToRedact, + redactEventAction = redactEventAction, ) } is MessagesEvent.ConfirmRedact -> { - val target = eventToRedact.value - eventToRedact.value = null - if (target != null) { + val confirming = redactEventAction.value as? MessagesState.ConfirmingRedaction + redactEventAction.value = AsyncAction.Uninitialized + if (confirming != null) { localCoroutineScope.launch { - redact(target, event.reason?.takeIf { it.isNotBlank() }) + redact(confirming.eventId.toEventOrTransactionId(), event.reason?.takeIf { it.isNotBlank() }) } } } MessagesEvent.CancelRedact -> { - eventToRedact.value = null + redactEventAction.value = AsyncAction.Uninitialized } is MessagesEvent.ToggleReaction -> { localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId) @@ -345,7 +347,7 @@ class MessagesPresenter( hasUnreadThreads = false, ), showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom && timelineState.timelineMode !is Timeline.Mode.Thread, - eventToRedact = eventToRedact.value, + redactEventAction = redactEventAction.value, eventSink = ::handleEvent, ) } @@ -384,13 +386,13 @@ class MessagesPresenter( timelineProtectionState: TimelineProtectionState, enableTextFormatting: Boolean, timelineState: TimelineState, - eventToRedact: MutableState, + redactEventAction: MutableState>, ) = launch { when (action) { TimelineItemAction.CopyText -> handleCopyContents(targetEvent) TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent) TimelineItemAction.CopyLink -> handleCopyLink(targetEvent) - TimelineItemAction.Redact -> handleActionRedact(targetEvent, eventToRedact) + TimelineItemAction.Redact -> handleActionRedact(targetEvent, redactEventAction) TimelineItemAction.Edit, TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState) @@ -512,18 +514,19 @@ class MessagesPresenter( ) } - private suspend fun handleActionRedact(event: TimelineItem.Event, eventToRedact: MutableState) { - if (event.eventId == null) { + private suspend fun handleActionRedact(event: TimelineItem.Event, redactEventAction: MutableState>) { + val eventId = event.eventId + if (eventId == null) { // The message was never sent, so there is nobody to give a reason to. - redact(event, reason = null) + redact(event.eventOrTransactionId, reason = null) } else { - eventToRedact.value = event + redactEventAction.value = MessagesState.ConfirmingRedaction(eventId) } } - private suspend fun redact(event: TimelineItem.Event, reason: String?) { + private suspend fun redact(eventOrTransactionId: EventOrTransactionId, reason: String?) { timelineController.invokeOnCurrentTimeline { - redactEvent(eventOrTransactionId = event.eventOrTransactionId, reason = reason) + redactEvent(eventOrTransactionId = eventOrTransactionId, reason = reason) .onFailure { Timber.e(it) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 4fd3423b6c..2073b7f847 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -18,13 +18,14 @@ import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState -import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom @@ -62,8 +63,7 @@ data class MessagesState( val successorRoom: SuccessorRoom?, val threads: Threads, val showLiveLocationShareBanner: Boolean, - /** The event the user asked to remove, waiting for them to confirm and optionally give a reason. */ - val eventToRedact: TimelineItem.Event?, + val redactEventAction: AsyncAction, val eventSink: (MessagesEvent) -> Unit ) { val isTombstoned = successorRoom != null @@ -72,6 +72,11 @@ data class MessagesState( val hasThreads: Boolean, val hasUnreadThreads: Boolean, ) + + /** The user asked to remove [eventId] and has to confirm, optionally giving a reason. */ + data class ConfirmingRedaction( + val eventId: EventId, + ) : AsyncAction.Confirming } /** Type of "shared history" icon to show in the top bar. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt index c66d04564c..53f90ba7d8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt @@ -24,7 +24,6 @@ import io.element.android.features.messages.impl.messagecomposer.aMessageCompose import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.TimelineState -import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent @@ -42,10 +41,12 @@ import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ROOM_NAME +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState @@ -93,7 +94,7 @@ open class MessagesStatePreviewParam : PreviewParameterProvider { composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())) ), - aMessagesState(eventToRedact = aTimelineItemEvent()), + aMessagesState(redactEventAction = MessagesState.ConfirmingRedaction(EventId("\$anEventId"))), ) } @@ -133,7 +134,7 @@ fun aMessagesState( isCurrentlySharingLiveLocationInRoom: Boolean = false, dmUserStatus: DisplayedStatus? = null, eventSink: (MessagesEvent) -> Unit = {}, - eventToRedact: TimelineItem.Event? = null, + redactEventAction: AsyncAction = AsyncAction.Uninitialized, ) = MessagesState( roomId = RoomId("!id:domain"), roomName = roomName, @@ -164,7 +165,7 @@ fun aMessagesState( threads = threads, showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom, dmUserStatus = dmUserStatus, - eventToRedact = eventToRedact, + redactEventAction = redactEventAction, eventSink = eventSink, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 391db3c0d2..ebca98eaea 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -457,11 +457,12 @@ fun MessagesView( }, ) - if (state.eventToRedact != null) { + if (state.redactEventAction is MessagesState.ConfirmingRedaction) { TextFieldDialog( - title = stringResource(CommonStrings.action_remove_message), - label = stringResource(CommonStrings.common_reason), - placeholder = null, + title = stringResource(CommonStrings.screen_room_confirm_removal_title), + content = stringResource(CommonStrings.screen_room_confirm_removal_message), + placeholder = stringResource(CommonStrings.common_reason), + supportingText = stringResource(CommonStrings.common_optional), value = null, submitText = stringResource(CommonStrings.action_remove), destructiveSubmit = true, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index b2682db464..b979b8fd7b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -45,6 +45,7 @@ import io.element.android.features.messages.test.timeline.voicemessages.composer import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -561,11 +562,11 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent)) advanceUntilIdle() val confirmingState = expectMostRecentItem() - assertThat(confirmingState.eventToRedact).isEqualTo(messageEvent) + assertThat(confirmingState.redactEventAction).isEqualTo(MessagesState.ConfirmingRedaction(messageEvent.eventId!!)) assert(redactEventLambda).isNeverCalled() confirmingState.eventSink(MessagesEvent.ConfirmRedact(A_REASON)) advanceUntilIdle() - assertThat(expectMostRecentItem().eventToRedact).isNull() + assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized) assert(redactEventLambda) .isCalledOnce() .with(value(messageEvent.eventOrTransactionId), value(A_REASON)) @@ -589,7 +590,7 @@ class MessagesPresenterTest { advanceUntilIdle() expectMostRecentItem().eventSink(MessagesEvent.ConfirmRedact(" ")) advanceUntilIdle() - assertThat(expectMostRecentItem().eventToRedact).isNull() + assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized) assert(redactEventLambda) .isCalledOnce() .with(value(messageEvent.eventOrTransactionId), value(null)) @@ -612,7 +613,7 @@ class MessagesPresenterTest { advanceUntilIdle() expectMostRecentItem().eventSink(MessagesEvent.CancelRedact) advanceUntilIdle() - assertThat(expectMostRecentItem().eventToRedact).isNull() + assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized) assert(redactEventLambda).isNeverCalled() } } @@ -632,7 +633,7 @@ class MessagesPresenterTest { val localEcho = aMessageEvent(eventId = null, transactionId = A_TRANSACTION_ID) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, localEcho)) advanceUntilIdle() - assertThat(expectMostRecentItem().eventToRedact).isNull() + assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized) assert(redactEventLambda) .isCalledOnce() .with(value(localEcho.eventOrTransactionId), value(null)) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt index 1ce241d5d9..493ad87bc3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt @@ -47,6 +47,7 @@ fun TextFieldDialog( maxLines: Int = minLines, content: String? = null, label: String? = null, + supportingText: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, submitText: String = stringResource(CommonStrings.action_ok), destructiveSubmit: Boolean = false, @@ -84,6 +85,7 @@ fun TextFieldDialog( TextFieldListItem( placeholder = placeholder.orEmpty(), label = label, + supportingText = supportingText, text = textFieldContents, onTextChange = { error = if (!validation(it.text)) onValidationErrorMessage else null diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt index e149af5890..f2cace5eaf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt @@ -57,6 +57,7 @@ fun TextFieldListItem( minLines: Int = 1, maxLines: Int = minLines, label: String? = null, + supportingText: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, ) { @@ -66,7 +67,7 @@ fun TextFieldListItem( placeholder = placeholder, label = label, validity = if (error != null) TextFieldValidity.Invalid else TextFieldValidity.None, - supportingText = error, + supportingText = error ?: supportingText, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, maxLines = maxLines, diff --git a/libraries/ui-strings/src/main/res/values/temporary.xml b/libraries/ui-strings/src/main/res/values/temporary.xml index c704e811ff..acb6ff8a47 100644 --- a/libraries/ui-strings/src/main/res/values/temporary.xml +++ b/libraries/ui-strings/src/main/res/values/temporary.xml @@ -10,4 +10,7 @@ "Collapsed" Active Inactive + "(Optional)" + "Confirm removal" + "Are you sure you wish to remove this message?" From 991e801fd81ff4006067498e13c8e7bb31b1f550 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:28:09 +0300 Subject: [PATCH 04/30] Update screenshots --- .../images/features.messages.impl_MessagesView_Day_12_en.png | 4 ++-- .../features.messages.impl_MessagesView_Night_12_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png index 187081f077..11856752ae 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0291b33e495c88fc86b90278dbaecf4811c04b17659422c4e3ac43d10817b011 -size 47592 +oid sha256:25b37aa1c7ff867bcb9a3723436eff0209720476757899125226a9a6f854afad +size 53178 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png index af6d9fa1fb..ddfdf3dcd2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b80dd96314a0f61f1a8a92f9bd53582cb20de2f7835a825f8ea4cfa37859e06 -size 42085 +oid sha256:c9e395cd1f389f2a341539cc72923361a542a5aafe037859a57190448f484a53 +size 47216 From 476e863734fb789b47bafb7899d4bf21ad5d37ce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 07:53:31 +0000 Subject: [PATCH 05/30] Update plugin sonarqube to v7.5.0.8588 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d98f00b3a0..82c2495baf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -276,7 +276,7 @@ paparazzi = "app.cash.paparazzi:2.0.0-alpha05" roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } -sonarqube = "org.sonarqube:7.4.0.8496" +sonarqube = "org.sonarqube:7.5.0.8588" licensee = "app.cash.licensee:1.14.1" compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } gms_google_services = { id = "com.google.gms.google-services", version = "4.5.0" } From a7ff96c1c338f8f28932084a93885b1f424f3007 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Sep 2026 16:26:51 +0200 Subject: [PATCH 06/30] Update wording of the remove message confirmation dialog. And get strings from Localazy --- .../android/features/messages/impl/MessagesView.kt | 8 ++++---- .../messages/impl/src/main/res/values-pl/translations.xml | 4 ++++ .../messages/impl/src/main/res/values-ru/translations.xml | 4 ++++ .../messages/impl/src/main/res/values-zh/translations.xml | 4 ++++ features/messages/impl/src/main/res/values/localazy.xml | 6 ++++++ .../ui-strings/src/main/res/values-pl/translations.xml | 4 ---- .../ui-strings/src/main/res/values-ru/translations.xml | 4 ---- .../ui-strings/src/main/res/values-zh/translations.xml | 4 ---- libraries/ui-strings/src/main/res/values/localazy.xml | 4 ---- libraries/ui-strings/src/main/res/values/temporary.xml | 3 --- tools/localazy/config.json | 1 + 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index ebca98eaea..af8197c8c0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -459,10 +459,10 @@ fun MessagesView( if (state.redactEventAction is MessagesState.ConfirmingRedaction) { TextFieldDialog( - title = stringResource(CommonStrings.screen_room_confirm_removal_title), - content = stringResource(CommonStrings.screen_room_confirm_removal_message), - placeholder = stringResource(CommonStrings.common_reason), - supportingText = stringResource(CommonStrings.common_optional), + title = stringResource(R.string.screen_room_confirm_removal_title), + content = stringResource(R.string.screen_room_confirm_removal_message), + placeholder = stringResource(R.string.screen_room_confirm_removal_reason_placeholder_android), + supportingText = stringResource(R.string.screen_room_confirm_removal_reason_supporting_text), value = null, submitText = stringResource(CommonStrings.action_remove), destructiveSubmit = true, diff --git a/features/messages/impl/src/main/res/values-pl/translations.xml b/features/messages/impl/src/main/res/values-pl/translations.xml index 915eedf61e..1ea8dec888 100644 --- a/features/messages/impl/src/main/res/values-pl/translations.xml +++ b/features/messages/impl/src/main/res/values-pl/translations.xml @@ -48,6 +48,10 @@ "Udostępnij lokalizację" "Ankieta" "Formatowanie tekstu" + "Czy na pewno chcesz usunąć wiadomość?" + "Powód (opcjonalne)" + "Powód…" + "Potwierdź usunięcie" "Historia wiadomości jest obecnie niedostępna." "Historia wiadomości jest niedostępna w tym pokoju. Zweryfikuj to urządzenie, aby zobaczyć historię wiadomości." "Czy chcesz zaprosić ich z powrotem?" diff --git a/features/messages/impl/src/main/res/values-ru/translations.xml b/features/messages/impl/src/main/res/values-ru/translations.xml index d1dfceb6a3..83598bc325 100644 --- a/features/messages/impl/src/main/res/values-ru/translations.xml +++ b/features/messages/impl/src/main/res/values-ru/translations.xml @@ -48,6 +48,10 @@ "Поделиться местоположением" "Опрос" "Форматирование текста" + "Вы уверены, что хотите удалить это сообщение?" + "Причина (необязательно)" + "Введите…" + "Подтвердите удаление" "В настоящее время история сообщений недоступна в этой комнате." "История сообщений в этой комнате недоступна. Проверьте это устройство, чтобы увидеть историю сообщений." "Хотите пригласить их снова?" diff --git a/features/messages/impl/src/main/res/values-zh/translations.xml b/features/messages/impl/src/main/res/values-zh/translations.xml index 609db9f40b..fd339cf906 100644 --- a/features/messages/impl/src/main/res/values-zh/translations.xml +++ b/features/messages/impl/src/main/res/values-zh/translations.xml @@ -46,6 +46,10 @@ "共享位置" "投票" "文本格式化" + "确定要移除此消息?" + "理由(可选)" + "输入…" + "确认移除" "消息历史记录当前不可用。" "消息历史在此房间不可用。请验证此设备以查看。" "你想邀请他们回来吗?" diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 0c45fc01e1..392b90d067 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -47,6 +47,12 @@ "Share location" "Poll" "Text Formatting" + "Are you sure you wish to remove this message?" + "Reason (optional)" + "Enter…" + "Reason…" + "(Optional)" + "Confirm removal" "Message history is currently unavailable." "Message history is unavailable in this room. Verify this device to see your message history." "Would you like to invite them back?" diff --git a/libraries/ui-strings/src/main/res/values-pl/translations.xml b/libraries/ui-strings/src/main/res/values-pl/translations.xml index 5aaae1f5d1..3865706e00 100644 --- a/libraries/ui-strings/src/main/res/values-pl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pl/translations.xml @@ -564,10 +564,6 @@ Czy na pewno chcesz kontynuować?" "Jedno lub więcej z Twoich urządzeń jest niezweryfikowanych. Wyślij wiadomość mimo to lub anuluj i spróbuj ponownie po zweryfikowaniu wszystkich swoich urządzeń." "Twoja wiadomość nie została wysłana, ponieważ nie zweryfikowałeś jednego lub więcej swoich urządzeń." "Edytuj administratorów lub właścicieli" - "Czy na pewno chcesz usunąć wiadomość?" - "Powód (opcjonalne)" - "Powód…" - "Potwierdź usunięcie" "Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie." "Nie można pobrać danych użytkownika" "Wiadomość w %1$s" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index d2fc35c7b3..6a8a7a7b9d 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -564,10 +564,6 @@ "Одно или несколько ваших устройств не подтверждены. Вы можете отправить сообщение в любом случае или отменить его пока и повторить попытку позже, подтвердив все свои устройства." "Ваше сообщение не было отправлено, поскольку вы не подтвердили одно или несколько своих устройств." "Редактировать владельцев и администраторов" - "Вы уверены, что хотите удалить это сообщение?" - "Причина (необязательно)" - "Введите…" - "Подтвердите удаление" "Не удалось обработать медиафайл для загрузки, попробуйте ещё раз." "Не удалось получить данные о пользователе" "Сообщение в %1$s" diff --git a/libraries/ui-strings/src/main/res/values-zh/translations.xml b/libraries/ui-strings/src/main/res/values-zh/translations.xml index 7f5cb8d120..39e5382913 100644 --- a/libraries/ui-strings/src/main/res/values-zh/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh/translations.xml @@ -536,10 +536,6 @@ "你有至少 1 个未经验证的设备。你可以照常发送消息,也可以暂时取消,并在验证所有设备后重试。" "你的消息未能发送,因为你有尚未验证的设备。" "编辑管理员或所有者" - "确定要移除此消息?" - "理由(可选)" - "输入…" - "确认移除" "处理要上传的媒体失败,请重试。" "无法获取用户信息" "%1$s 中的消息" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 0493817f61..8d76ead30e 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -550,10 +550,6 @@ Are you sure you want to continue?" "One or more of your devices are unverified. You can send the message anyway, or you can cancel for now and try again later after you have verified all of your devices." "Your message was not sent because you have not verified one or more of your devices" "Edit Admins or Owners" - "Are you sure you wish to remove this message?" - "Reason (optional)" - "Enter…" - "Confirm removal" "Failed processing media to upload, please try again." "Could not retrieve user details" "Message in %1$s" diff --git a/libraries/ui-strings/src/main/res/values/temporary.xml b/libraries/ui-strings/src/main/res/values/temporary.xml index acb6ff8a47..c704e811ff 100644 --- a/libraries/ui-strings/src/main/res/values/temporary.xml +++ b/libraries/ui-strings/src/main/res/values/temporary.xml @@ -10,7 +10,4 @@ "Collapsed" Active Inactive - "(Optional)" - "Confirm removal" - "Are you sure you wish to remove this message?" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 645c3349b9..98e997c520 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -275,6 +275,7 @@ "screen\\.room\\.timeline.*", "error\\.sending_failure\\..*", "screen\\.room_timeline.*", + "screen\\.room\\.confirm_removal.*", "screen_room_typing.*", "screen\\.image_edition\\..*", "screen\\.media_upload.*" From 5df0dca56b7131e9e7679c7e316edd9d10cf3912 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 9 Sep 2026 15:26:04 +0000 Subject: [PATCH 07/30] Update screenshots --- ...nts.preview.imageeditor_AttachmentImageEditorView_2_en.png | 4 ++-- ...nts.preview.imageeditor_AttachmentImageEditorView_5_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_0_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_1_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_2_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_0_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_1_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_2_en.png | 4 ++-- ...re.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png | 4 ++-- ...re.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png | 4 ++-- ....resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png | 4 ++-- ....resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png | 4 ++-- ...meline.components.event_ATimelineItemEventRow_Day_0_en.png | 4 ++-- ...line.components.event_ATimelineItemEventRow_Night_0_en.png | 4 ++-- ....components_TimelineItemEventRowDisambiguated_Day_0_en.png | 4 ++-- ...omponents_TimelineItemEventRowDisambiguated_Night_0_en.png | 4 ++-- ...ents_TimelineItemEventRowWithReplyInformative_Day_0_en.png | 4 ++-- ...ents_TimelineItemEventRowWithReplyInformative_Day_1_en.png | 4 ++-- ...ts_TimelineItemEventRowWithReplyInformative_Night_0_en.png | 4 ++-- ...ts_TimelineItemEventRowWithReplyInformative_Night_1_en.png | 4 ++-- ...components_TimelineItemEventRowWithReplyOther_Day_0_en.png | 4 ++-- ...components_TimelineItemEventRowWithReplyOther_Day_1_en.png | 4 ++-- ...mponents_TimelineItemEventRowWithReplyOther_Night_0_en.png | 4 ++-- ...mponents_TimelineItemEventRowWithReplyOther_Night_1_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_0_en.png | 4 ++-- ...ine.components_TimelineItemEventRowWithReply_Day_10_en.png | 4 ++-- ...ine.components_TimelineItemEventRowWithReply_Day_11_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_1_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_2_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_3_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_4_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_5_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_6_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_7_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_8_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_9_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_0_en.png | 4 ++-- ...e.components_TimelineItemEventRowWithReply_Night_10_en.png | 4 ++-- ...e.components_TimelineItemEventRowWithReply_Night_11_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_1_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_2_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_3_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_4_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_5_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_6_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_7_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_8_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_9_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_12_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_0_en.png | 4 ++-- .../features.messages.impl_MessagesView_Night_10_en.png | 4 ++-- .../features.messages.impl_MessagesView_Night_12_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_2_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_4_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_6_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_8_en.png | 4 ++-- ...references.impl.blockedusers_BlockedUsersView_Day_4_en.png | 4 ++-- ...references.impl.blockedusers_BlockedUsersView_Day_5_en.png | 4 ++-- ...ferences.impl.blockedusers_BlockedUsersView_Night_4_en.png | 4 ++-- ...ferences.impl.blockedusers_BlockedUsersView_Night_5_en.png | 4 ++-- ...es.preferences.impl.userstatus_UserStatusView_Day_5_en.png | 4 ++-- ...es.preferences.impl.userstatus_UserStatusView_Day_6_en.png | 4 ++-- ....preferences.impl.userstatus_UserStatusView_Night_5_en.png | 4 ++-- ....preferences.impl.userstatus_UserStatusView_Night_6_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_5_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_7_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_5_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_7_en.png | 4 ++-- ...eme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png | 4 ++-- ...e.components_HorizontalFloatingToolbarNoFab_Night_0_en.png | 4 ++-- ...em.theme.components_HorizontalFloatingToolbar_Day_0_en.png | 4 ++-- ....theme.components_HorizontalFloatingToolbar_Night_0_en.png | 4 ++-- 72 files changed, 144 insertions(+), 144 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png index 8edee63bc1..3192cf2a5e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de82970da241172b94f822f8792e6fc721dcfc7f0f67e3523962e253e9750884 -size 255933 +oid sha256:1885bbe8b0ea497ac20d7bcdd0c6a93ba0533e898378d35e1cafdc8c9924fd56 +size 279984 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png index ccfdd7d9ed..f899ad5d43 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe6cb72de5079be2ac02ec39f74bac0d20492cd6f5509b71ad9cfc4cc52955d3 -size 326468 +oid sha256:1ca032575e4ebd75a2ebc5a4cb9f966e48cdfa56f4ff42d6d9309fa3577a1adb +size 310609 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png index 5f51b27f5e..693a36b7dd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3274314ab9e39c23a6b9d66adf80ac8bb7b364ce60a300f96582374a18bc821f -size 54590 +oid sha256:3dbb8ef39b6a7a0c2f576d8bd101aa97c89da696f23e6d5836846494471c01a1 +size 55799 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png index e1f6ee6430..b1850a50a5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e27c7c51fe07cca51b92a601aa9c58deab609bfa5e82f8f6f88eaab6ae59ec3 -size 64307 +oid sha256:f1dd0da147212e13a6c0ad5ad8f85ce55666c444076c238ea27eba5605cfab1f +size 56909 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png index 5289eaca63..4185f46b51 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65f9ee083a73669a0d3f9d88141a610ea53f3c39e89d8f706d414b04555b4764 -size 59321 +oid sha256:085963fcb425147d35a9ad947a7a3e4ba4712bd3d2f32e93099ff15cffe83b88 +size 64761 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png index d3f7da6320..2e70435fa5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a94a4cc176b5d52ef9b21339749324adeef9857b6cdaa2ae973903e2408662a -size 55904 +oid sha256:7a3667390d9d88894bbdd5d72b0b714b29c9ff160653e310e97b0da87b6ba8b0 +size 54724 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png index bd3b0196d6..0f6de1b844 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f7f7415abbe51858f938e16ad03c6aab983fb3d5e2a53a028bae3c031446646 -size 67848 +oid sha256:28512b0fda0b71b98eab03cee15c9d3163825b1a3545670cfa277ddd5434c87a +size 58417 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png index f4d51ccc61..6c774d4cec 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a8f5aaaa7ebb0e61b07867590d99a8791c5d04cad1ce0f334d23dce6f26c6e6 -size 67276 +oid sha256:6ed6a5f1a2055e19a6b8fb0af4738845bd2184f755c323022c52154cdfa07e05 +size 60909 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png index bd73e322ab..02d0fc8eeb 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 -size 3657 +oid sha256:f77d8243c23d3f37dff86dec53671cbde9fafe1314f6e6b445abdfe47d751ad6 +size 56187 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png index 93622cedfe..bd73e322ab 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87193a7c9eb9d40234970e5f5be02e80f2b3ffe91e2c9ca6afd58b8bccdd5911 -size 55075 +oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 +size 3657 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png index bd73e322ab..0cf6c10c4e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 -size 3657 +oid sha256:49f0bb16d3d57b59242e704e3dd4e8885c59721a42129837ff6a4f0d7d02de11 +size 54433 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png index bd73e322ab..adec213a8c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 -size 3657 +oid sha256:8ebee4aec3dbab6e1bb05fda7c5e423f0e931560743d0f0ccbac6b70fa9ad808 +size 52921 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png index 0ab00359b7..12bd190dd1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b5e6f4c5ec97e67bb20f03f25f18d9f32d5d1aed5ff30d161b331894ad03443 -size 647384 +oid sha256:ce15ca9f89274620728715e3448ce671e2253f4fed22a44035460e9a3709ca0b +size 647404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png index 0ccdcbb603..160d58f9ec 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0424685437a433b35b589668b409d55f26ab41bcae01af0121a54033c56a8c2 -size 646194 +oid sha256:c4e7b28b78cfbcbb6abf00317eea945859ac6465cecf81a3ad15801b0de721e4 +size 646216 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png index ab6392a424..0435f55de6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94f89a37549a3f198442db467bc7d8539f0b5649d97927bfe731f090d6dfb03d -size 379505 +oid sha256:ff839731834d7d0c94fa9a2c412014ddbaeaa053c5af1c8cbd00a3f3a583f364 +size 379511 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png index f0b5506b89..9cbd5c1d95 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04ea6c2947a21d3d4a3d43e9c4750c4f2a49056536a4da3f34c95df8353e9de3 -size 377990 +oid sha256:d0e170b4bea262de612bf3374d475fcd25470ff91b7554d20809c51857a78cec +size 377963 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png index 3ac59caeea..1f0fdabe6d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a94d5198e27338675c0e15df5874ba3272ee315b8d77291dc7ccc7c754ed1f4 -size 365082 +oid sha256:b8bb5a6de6601da8d7fe59f110a054577e2e25dae4bb449f60c04e7d7a1b9551 +size 365073 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png index 195aa04fe2..86018764ac 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76e5ae44362792d570cd82f3910c8bd0acde509b8418c5892e0385e7d636f042 -size 370223 +oid sha256:8344427a0fe87cbf0883ff53a9ee4dcf90976a7011f6618d85961df48b2c53a9 +size 370228 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png index 29771235f0..058a3992c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60b9f2b920cd2a2a414805d1f0f77925232d9ae60bd607fa18d4395cda2dd73d -size 363291 +oid sha256:00fe1e676086766a3eac3e4457eff52c989789fc5d8d0df2624c019ed25ce9af +size 363322 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png index db4ea8296a..c6d4541184 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f0d24b8011564ee7a8b2f8c2e6a24eda668ee42d451d862f884acb7926aa06c -size 368529 +oid sha256:c00e504593d8eb61b598f05f46ee4d591757975a80c0b4227b240847d2cd4868 +size 368510 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png index 94428abb56..84d9d4e5c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36d7cf2510d546d2b330b1d1b1674ce58df0558c547e443794a1e7b2222add87 -size 347897 +oid sha256:7c609a633ab23eae5729ecb57a17a385bac65e81cbf0180c01e0847f1028039e +size 347890 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png index 5400a98a50..40fa5d563f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40a23ddff1fe46a486a5d34d94e0e67ec14d0b2b9c6bee2ddbadc17ce0948b7b -size 367131 +oid sha256:3ff97eb99daf65f74b469d51b93bd77d304d0c780b9406e1c4d7893b71e3c68d +size 367163 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png index 22ff4965d1..bac23aab98 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:787151f8f242a8794a804bf14831f6ca680814d617e85feb3652103e8a0f20cd -size 346565 +oid sha256:e30686c7c037ef102717e0316543faf4d3ed2671c21eca96a667150daab380d6 +size 346614 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png index c3b6a086fe..2348755afd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e615c14cd23f3efd217ec20ce05ed32be76dc3317091e8221f1a72a5a594d92 -size 364175 +oid sha256:e907f67ef884edb61df187f0f229ed4ff4cb31f8c276f807698f861c0e7a4c27 +size 364124 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png index 67490ba700..01d4dca84b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4431e2d9f30123949ea5676ce60f642234adcf9b5a33f2acc3f80ebf6609a66 -size 369730 +oid sha256:ef2bf449671b652cb152b3baab92ab100eab77a54229ab49daeab156f2f484d0 +size 369724 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png index 1600afff71..f0dfbc3321 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:273351cc9c533b2424859a0de00a8a3786628abc3a88175705c4205dedf7e9aa -size 354737 +oid sha256:f7a6302320bd1e4258e03487539bf46b0b12781dfc8a22b3f9f267017a6146fa +size 354751 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png index c4943b892c..e38cf03ad6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c410e853089cc2c4a2c48a00dd9a8babc8095aed72bb4625057444710559c6c -size 368129 +oid sha256:41a0e404dea7f52359b24b3ec3926b1405429b165cfed44c63b3922ea6d3cb46 +size 368114 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png index b5dac8434c..68f8148879 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a92f8af94682facac66d4aacb6e49f345561c15be2f8b689041b78aeaaae831 -size 345062 +oid sha256:6ca8cf82aa8ab1bdad56a27584b27789402aeb7ad8fadd038280a376fb75a72c +size 345094 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png index 1b99468924..8aa2cca147 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a9509411777f342261cab58b92a022b3c9f8f0f7a9e4d0527120a91976575f0 -size 357733 +oid sha256:02ecb903124273e24151ea2088f6b99144f7dcadba00a582b6210dd23835b5c9 +size 357743 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png index cececc229a..b5032c29b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0c6dafaa41849af00d5097a85bd88e3a054a0a9e41c0466a08e6ff8dd6102b2 -size 356857 +oid sha256:dce23463c335d01eb67293cfcede5d85e96ab2ba035cc9a188d42e8e6866bc63 +size 356831 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png index 14a40a5676..a332744358 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a11044fe90227f1f316a1ade8587e545bb620e11cca76865fc2eb83cdb817fc5 -size 364655 +oid sha256:588967869cbd25ed8b82aff5618175660a8a9e45a5a861b1c7867cf1913f575b +size 364646 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png index d7b969fd5b..133fd3903f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2528df873713755f542f61cd794080929586c1bd5f316c0378ce9e837c21d50b -size 397145 +oid sha256:426d1c844a8c88281c5fa4c61916d93278fdfd38f8cdf2dc54bf1a821f2de3eb +size 397167 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png index 63bd9c3f6d..0041a870b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6cd94664f89bdad2a9761ea8b20acb83fed3ed7cb5be41af8fe4852643c0bfc -size 356175 +oid sha256:a2bd77c08be705c66225f16c29fd6826decd92b41922d0f8ffc0d1a63d8c59d2 +size 356193 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png index 9fcf389708..f67906a37c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9daeee1aaa04247da3c8bb5aaae9947aab1ed2d2057995e53d6ebdb63e5cd356 -size 355848 +oid sha256:ffa68c8398d789444151855496e5df4ffa03218a383db6ea46fd4f979829b823 +size 355831 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png index d235670def..6e83d6491d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a00bd766a00b7e7c6615fc53bf469b3ca5056a6c514c096431ae38d99d604306 -size 364845 +oid sha256:34152c742abc486a48234e22101833bc163cf11979890d06aac41e68e3c404d0 +size 364846 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png index 0021619c44..b10f9741b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b23d3a37774283067a8919b8c332b1bb5a79e90dceb7216b484b985491282e5 -size 355389 +oid sha256:3ec1b0244dab22414c6719b3309426e13128d0c70fb67dc66cd8a60d42521fd0 +size 355411 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png index 422c324695..77abe20982 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1df8d22766981fc76d6d767dc3f40d98cadc7c51be20417b067b91af604232b -size 367884 +oid sha256:5c3d87862c6d7fc88d0f23cd54c5b9f77028992fdd7c282a195b964dd17e85ac +size 367855 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png index 448bcec677..1d34761a9b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe0daf9161bc2da194992a2fc9505bf2009c5dbed78cdb35d2eb7ed1380a7df5 -size 353037 +oid sha256:f7d9b5fc0ff5d7828ad6f4dc0c9bd9520b4920553da97ed7b4e030ee844c0208 +size 353054 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png index c081fa16a5..5243218ae3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a92bcf5885322e11a706071969fb674b1f6e60e021af153aaa125baa4a602ae -size 366285 +oid sha256:9a85db2f5958db867578663533f204b2e803372d470da890fd8056ffed0cf46c +size 366254 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png index 47a9a6b630..b6d1f07279 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54372845e922d047408dd960d048b9b5eae2263d589117d8e7d197741aa34f32 -size 342827 +oid sha256:4bee12a6bdb18fea5ac6997671bfcb903296da7278cf69b2456330066ca31dfb +size 342870 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png index 5560c21967..ceca184bd7 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af4a48ee80535f2c0a226c4ffa4344054fb63b7b0a88ea2c030a4eb8dd5e9644 -size 356078 +oid sha256:8e82011a72f36889c53ab61ed2b9947c7fb5f868c9b7010904fc10f4ca587082 +size 356082 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png index 5cc5cfe4fc..5cdad1df92 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6524cc87257cc9d3f135904f4e14594b56df695e71fe576418d2319c472d710b -size 355285 +oid sha256:9e2d8caf5aaae7d3406c703288e491f328552f0acd18e56afd5265b4ceaf1cd1 +size 355272 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png index e9d85990e8..a431a9429a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66d4b6ea4a697735bbb0fac936b89cde99c5077973ba828894bf50b5dae233b3 -size 362848 +oid sha256:93c2df04d65f6078ce3f7067fbae8a7317ec42ed0fc013dee962a3953269f3b7 +size 362813 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png index b51a89586b..0eb106f4a9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e642229c58169775fe702769a306bbef22698516ae6f7ac75a761213c3f195fa -size 395446 +oid sha256:217ac95bf476c53d2dc6d3d0143b21312bf38b8ab86c8dfd4f9360582b584f9d +size 395476 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png index aa322fc2e4..ae8495618a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fae87094ea13b14e2dc7c16ca96e4f8096ff39e962799468fb4ead4c666620f -size 354381 +oid sha256:08057fdc1e1184720b438ddd2a8753f00791baf2456cc30b45de8e6bdeb57e8e +size 354404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png index 40105c1498..9d617f6688 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ed24d190f10c079f8cf176404372d40eb25cc78ce595a22febc540315057b86 -size 354075 +oid sha256:dabad9dcd5b8f0d4bb964a0187ad7c94b023b04552dacd29a077daddfd48d550 +size 354052 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png index 0cfd1dc985..1a8ea3e4e9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:545c9e8933e420cc3f8fbba0fbd5162c946c65a20721faeb77c568596ca720cd -size 363045 +oid sha256:66aba7b4271a990fac85b0bbc7cbdc371fa1e38a4da5bb4e2f62a80b21cb3a8f +size 363012 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png index c9227ebf94..c8306263a0 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e614f4a8657637c584a283d69124236d00fe1251c2b8d739c5688a57a6fd152e -size 353693 +oid sha256:756f0cff90ddd61adce2f467c0384d74d430f6d51c091e4980e45bcc9ee6f0fd +size 353705 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png index 11856752ae..0a41aba67c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25b37aa1c7ff867bcb9a3723436eff0209720476757899125226a9a6f854afad -size 53178 +oid sha256:9427e90f0865c7427006a73af5b64a799dee8ef9873994b44471e3494d156afb +size 53265 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png index cfa6e4dbc3..89fc6708d2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a3281f35461edb900ea434c68543dc684a5fad7dab7c9aabac510bc59d442b3 -size 56479 +oid sha256:89467c5b84d0a15210d4013013598d9994d3005a2299e44583d78355e6718ad8 +size 55538 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png index 5f3a619097..17df34a50a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19d14e3d779b163053c28e66d3381f0cabe3b540744606e955cd733f9e044089 -size 51713 +oid sha256:6548f5bf1906e1dddcbcc03bb8f76b42e604075751ea8efbc3ead826bf4cfad7 +size 51258 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png index ddfdf3dcd2..350c877e8a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9e395cd1f389f2a341539cc72923361a542a5aafe037859a57190448f484a53 -size 47216 +oid sha256:1d910cdbd02c49ef5decf23e3a804d15be047522f6260505bc696b856379891a +size 47310 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png index f4ab74029b..f425dd2c4c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51a5b682243b969bc2d7d609decb823b0509d240f3ff14ee78319260af76a47f -size 58740 +oid sha256:bd6dc088e16f722b36c2198d6a8611af87dec2451f94ca3c2b6880b97f709b98 +size 54095 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png index 0ad1e67deb..486d5da5c6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82e6e38576a507b19e58fce9dda0734ec00bcedcb0baf8f4c7919dbaaf7d2b85 -size 55046 +oid sha256:e482603e6d03fffb57d4eb135edb6209c748c51b1f07e638a4ccf133b1b87a79 +size 52920 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png index f82a7a8219..0383bb8f95 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22ea7fe121a63b35a2c2bb47609e692f8bf2b913871a6baf3d420c04334c6c4a -size 44344 +oid sha256:7c88b3ed8cc69dee59d4d0a726a9e383d00f2815b6525315f7dcd96f9c25ec2a +size 45618 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png index cfa62a6626..0f7fe2db38 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfe848e4c365cf4b4d663145f74e5f8b3f47a4c8994961c530603f0248400316 -size 57117 +oid sha256:a576d52b840730334a38046cf8b57993693fc760b4fbdc00376cb48b7268d76e +size 55890 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png index 7cca911c45..074820e157 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 -size 52770 +oid sha256:7caacb336793b8ae4b83e9a10a82325e25e8b3c036f881898ed28688250eb6e0 +size 55744 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png index 2af459c158..7cca911c45 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28a95d4b7ebca92c4e0467f3ecd9acdaf7f6257604d190f5d088a9a25e87d637 -size 55305 +oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 +size 52770 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png index 616c06bf46..a68f886f73 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 -size 53966 +oid sha256:e5a2e306087a967849f61872b56a83f194d1a76df5723f0bbae6e6e2f4a98c63 +size 55725 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png index 616c06bf46..36d19c6b72 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 -size 53966 +oid sha256:7dfa2767c55a81d3f121b8fa55443b7b05f9369e91639b80e0be349863799ad7 +size 55438 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png index 82f344f714..2c15fd6a37 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d96eefa01a5de803b0cc9d9109c20df86eaff01b73ff24a91945b43d53d5d01 -size 11960 +oid sha256:7260abe16fc2ab4f6ada97ea0f756dc5c9908ff3b61d6ec1723ffde302bb3dc5 +size 11955 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png index fdd8fcd5a5..7c422591ad 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22678a2a8505e1d9d5bfcb44459b6fac69c11a4bf72fae09c48ffa2e54f5a110 -size 12128 +oid sha256:2e16fc1daa0f284c442f66591e1d1f59161272a67998d26740b0b403bfe82495 +size 11866 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png index f6e27475da..acb8afa74a 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ee89826c8aa2ac0371ed31959243f62bf9968806a7b27117cd7b4d849423a33 -size 11667 +oid sha256:bed7565420e11c43acc06896ef298683d2f2dd1dc16c430eab7018e9ead9937a +size 11652 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png index b1bb69efb8..e0a4cb1c97 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e27cb9e501ccbac54926f0252b4098867643a2d2683c5b267f86ad99edf6f5d7 -size 11817 +oid sha256:87811df502c760816e74beb006341627857c39380ce715f1a747ac9fce77b226 +size 11516 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png index 73b8b8e50c..5c597300e4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8a3ad99763929d2783147814e44fa9ec7e7efb7b99949e3580f23c38fb5d971 -size 9398 +oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 +size 3853 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png index 5c597300e4..f8a1f24b34 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 -size 3853 +oid sha256:1342b6427f5b2e3075ae3ccee60bf1d3b11afaccf3e152643d6ef2d65c21b54e +size 9108 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png index d47bf90664..4ae7279fe7 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c -size 3664 +oid sha256:410a8596d4c70b9a3856d959d6d71ade8e831e8581ff410c9900957a93b3eb50 +size 8114 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png index a87d89c701..d47bf90664 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f3a0cb5d9bae6725a1c527f3d9575aabf85d3edf63a28d9faa23f4f0b65e2d0 -size 7624 +oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c +size 3664 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png index ee5c582240..58546b8522 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75de341f705bb22955f9399180519b65ad59c2e1917317d56e50ea4db78f35bc -size 12102 +oid sha256:30357bf18792db5ab20085010251170a724ffe8c00313facd04ee704e806af64 +size 10982 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png index 52fd9b9693..fedead0fa8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58b9491aaa5554b89d86c62a5c9c44975367538c6baa5562f8dc5768f76d8264 -size 8705 +oid sha256:4811667dec0f32accc5e2c8a5dd5c0b34017fcff8d1e0376ee7a4708426bca0f +size 9838 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png index 38ad0e4b56..83302d6ba4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:326d59cf768b2dbd96eb7964d54c8eda509a5cd57c6e62087625e3314da0bb4d -size 13658 +oid sha256:16dee1dfe746cd40dac9fe8f0af3ecb42cd14bab5cc490917b589beed065ed1b +size 14686 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png index 687e0d436a..4da2a98aca 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d57df7110371c9224e232b707d6a1b10ce14afcb56c803a78220792f2a0d4cf2 -size 11354 +oid sha256:ef0dc4d53daeb986f378b6e4d85928b4b6729336c89cf21e642c88c53688a3ab +size 10292 From 5b09fdf13f128b6b51c28b4a5a091c2f45f77916 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2026 16:57:01 +0200 Subject: [PATCH 08/30] Replace git flow with regular git commands in the release script V2 The script was relying on the external git-flow tool and on a local `git config gitflow.*` initialisation, which made its behaviour depend on the machine setup and hid what was actually done to the branches. The release branch is now created with `git checkout -b`, and the release is finished with explicit merge, tag, back-merge and branch deletion commands. The merge and tag messages are provided explicitly so that the generated history stays identical to the previous releases. Each step now aborts the script with a clear message when it fails, instead of letting the script continue. Note that tools/release/release.sh (V1) still uses git flow. Co-Authored-By: Claude Opus 5 (1M context) --- tools/release/releaseV2.sh | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/tools/release/releaseV2.sh b/tools/release/releaseV2.sh index 23f3910311..85d96414e9 100755 --- a/tools/release/releaseV2.sh +++ b/tools/release/releaseV2.sh @@ -5,7 +5,7 @@ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. -# do not exit when any command fails (issue with git flow) +# do not exit when any command fails, failures are handled explicitly by the script set +e printf "\n================================================================================\n" @@ -63,6 +63,12 @@ if [ ${envError} == 1 ]; then exit 1 fi +# Abort the script with an error message. +fail() { + printf "\nFatal: %s\n" "$1" + exit 1 +} + # Handle the result of a version check. ${checkError} must be set to 1 when a check has failed. checkVersionResult() { if [[ ${checkError} -ne 0 ]]; then @@ -97,16 +103,6 @@ if [[ ${unmergedPrs} != "" ]]; then fi -# Check if git flow is enabled -gitFlowDevelop=$(git config gitflow.branch.develop) -if [[ ${gitFlowDevelop} != "" ]] -then - printf "Git flow is initialized\n" -else - printf "Git flow is not initialized. Initializing...\n" - ./tools/gitflow/gitflow-init.sh -fi - printf "OK\n" printf "\n================================================================================\n" @@ -149,7 +145,7 @@ versionReleaseNumber=$(echo "${version}" | cut -d "." -f3) printf "\n================================================================================\n" printf "Starting the release %s\n" "${version}" -git flow release start "${version}" +git checkout -b "release/${version}" develop # Note: in case the release is already started and the script is started again, checkout the release branch again. ret=$? @@ -160,7 +156,7 @@ if [[ $ret -ne 0 ]]; then read -r -p "Continue (yes/no) default to yes? " doContinue doContinue=${doContinue:-yes} if [ "${doContinue}" == "no" ]; then - printf "OK, exiting, you can start the release again with the command 'git flow release start %s'\n" "${version}" + printf "OK, exiting, you can start the release again with the command 'git checkout -b release/%s develop'\n" "${version}" exit 1 fi git checkout "release/${version}" @@ -197,10 +193,17 @@ git commit -a -m "Adding fastlane file for version ${version}" printf "\n================================================================================\n" printf "OK, finishing the release...\n" -# GIT_MERGE_AUTOEDIT avoids opening the editor for the 2 merge commits, whose default message is -# always used, and -m provides the message of the annotated tag. git flow appends the tag name to -# it, so the tag message ends up being "Release v${version}". -GIT_MERGE_AUTOEDIT=no git flow release finish -m "Release" "${version}" +# Merge the release branch into main, tag it, then merge the tag back into develop and delete the +# release branch. The merge messages are provided explicitly to keep the history consistent with +# the previous releases. +git checkout main || fail "unable to checkout main." +git merge --no-ff --no-edit -m "Merge branch 'release/${version}'" "release/${version}" \ + || fail "unable to merge the branch release/${version} into main. Please fix the conflicts and finish the release manually." +git tag -a "v${version}" -m "Release v${version}" || fail "unable to create the tag v${version}." +git checkout develop || fail "unable to checkout develop." +git merge --no-ff --no-edit -m "Merge tag 'v${version}' into develop" "v${version}" \ + || fail "unable to merge the tag v${version} into develop. Please fix the conflicts and finish the release manually." +git branch -d "release/${version}" || fail "unable to delete the branch release/${version}." printf "\n================================================================================\n" read -r -p "Done, push the branch 'main' and the new tag (yes/no) default to yes? " doPush From 6887092ce45bc16a8e1c301c7f51f9f0cc812df3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 2 Sep 2026 17:54:08 +0200 Subject: [PATCH 09/30] Replace String by AccountProvider to be able to get baseUrl and serverName. --- .../android/appconfig/AuthenticationConfig.kt | 7 - .../io/element/android/appnav/RootFlowNode.kt | 5 +- enterprise | 2 +- .../enterprise/api/EnterpriseService.kt | 30 ++-- .../impl/DefaultEnterpriseService.kt | 6 +- .../impl/DefaultEnterpriseServiceTest.kt | 15 +- .../enterprise/test/FakeEnterpriseService.kt | 18 ++- features/login/api/build.gradle.kts | 1 + .../AccountProviderAccessControl.kt | 6 +- .../DefaultAccountProviderAccessControl.kt | 20 +-- .../impl/accountprovider/AccountProvider.kt | 17 --- .../AccountProviderDataSource.kt | 31 ++--- .../AccountProviderPreviewParam.kt | 32 ++--- .../accountprovider/AccountProviderView.kt | 43 +----- .../SaveAccountProviderToHistory.kt | 9 +- .../impl/changeserver/ChangeServerEvent.kt | 2 +- .../changeserver/ChangeServerPresenter.kt | 11 +- .../ChooseAccountProviderEvent.kt | 2 +- .../ChooseAccountProviderPresenter.kt | 20 +-- .../ChooseAccountProviderState.kt | 2 +- .../ChooseAccountProviderStatePreviewParam.kt | 28 ++-- .../ChooseAccountProviderView.kt | 10 +- .../ConfirmAccountProviderPresenter.kt | 29 ++-- .../loginpassword/LoginPasswordState.kt | 2 +- .../LoginPasswordStatePreviewParam.kt | 6 +- .../loginpassword/LoginPasswordView.kt | 2 +- .../screens/onboarding/OnBoardingEvent.kt | 4 +- .../screens/onboarding/OnBoardingPresenter.kt | 45 +++--- .../screens/onboarding/OnBoardingState.kt | 3 +- .../onboarding/OnBoardingStatePreviewParam.kt | 9 +- .../impl/screens/onboarding/OnBoardingView.kt | 2 +- .../impl/src/main/res/drawable/ic_matrix.xml | 12 -- ...DefaultAccountProviderAccessControlTest.kt | 90 ++++++------ .../AccountProviderDataSourceTest.kt | 131 +++++++----------- .../SaveAccountProviderToHistoryTest.kt | 6 +- .../changeserver/ChangeServerPresenterTest.kt | 45 +++--- .../ChooseAccountProviderPresenterTest.kt | 25 +--- .../ChooseAccountProviderStateTest.kt | 8 +- .../ChooseAccountProviderViewTest.kt | 6 +- .../ConfirmAccountProviderPresenterTest.kt | 59 +++++--- .../LoginPasswordPresenterTest.kt | 6 +- .../onboarding/OnBoardingPresenterTest.kt | 79 +++++++++-- .../screens/onboarding/OnboardingViewTest.kt | 7 +- features/login/test/build.gradle.kts | 1 + .../FakeAccountProviderAccessControl.kt | 7 +- .../api/accountprovider/AccountProvider.kt | 67 +++++++++ .../accountprovider/AccountProviderTest.kt | 89 ++++++++++++ .../accountprovider/AccountProviderFixture.kt | 28 ++++ 48 files changed, 608 insertions(+), 477 deletions(-) delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt delete mode 100644 features/login/impl/src/main/res/drawable/ic_matrix.xml create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt create mode 100644 libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProviderTest.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/accountprovider/AccountProviderFixture.kt diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt index 7432a6904f..a04abe1417 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt @@ -9,15 +9,8 @@ package io.element.android.appconfig object AuthenticationConfig { - const val MATRIX_ORG_URL = "https://matrix.org" - /** * URL with some docs that explain what's sliding sync and how to add it to your home server. */ const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md" - - /** - * Force a sliding sync proxy url, if not null, the proxy url in the .well-known file will be ignored. - */ - val SLIDING_SYNC_PROXY_URL: String? = null } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index c9deaeb3b3..5f2dc837ad 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -52,11 +52,11 @@ import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.waitForChildAttached -import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.deeplink.api.DeeplinkData import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId @@ -410,7 +410,8 @@ class RootFlowNode( } private suspend fun onLoginLink(params: LoginParams) { - if (accountProviderAccessControl.isAllowedToConnectToAccountProvider(params.accountProvider.ensureProtocol())) { + val accountProvider = AccountProvider.Generic(params.accountProvider) + if (accountProviderAccessControl.isAllowedToConnectToAccountProvider(accountProvider)) { // Is there a session already? val sessions = sessionStore.getAllSessions() if (sessions.isNotEmpty()) { diff --git a/enterprise b/enterprise index f463003f68..307b0fd01f 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit f463003f6813ad33fc00f80786bc83b6a7f0d8e2 +Subproject commit 307b0fd01f36191dddf6b51ed9ceb92887b360a2 diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt index 2cb4a356be..5483d1e60e 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt @@ -11,6 +11,7 @@ package io.element.android.features.enterprise.api import androidx.compose.ui.graphics.Color import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.libraries.matrix.api.ClientUrlContentFetcher +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.Flow @@ -36,18 +37,21 @@ interface EnterpriseService { suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher): String /** - * Returns the list of homeservers the user is allowed to sign in to. - * - * If the list is empty or contains the special value [ANY_ACCOUNT_PROVIDER], the user is allowed to sign in to any homeserver. + * Returns the list of account provider the user is allowed to sign in to. */ - fun homeserverAllowList(): List + fun accountProviderAllowList(): List /** - * Whether the user is allowed to sign in to a given homeserver, according to [homeserverAllowList]. - * - * @param homeserverUrl the server the user is trying to use. + * Whether the user is allowed to sign in to any account provider. */ - suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean + fun canConnectToAnyAccountProvider(): Boolean + + /** + * Whether the user is allowed to sign in to a given homeserver, according to [accountProviderAllowList]. + * + * @param accountProvider the account provider the user is trying to use. + */ + suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean /** * Whether the given homeserver enforces the use of Element Pro or a derived app. @@ -97,14 +101,4 @@ interface EnterpriseService { * @param sessionId the session whose channel is requested. */ fun getNoisyNotificationChannelId(sessionId: SessionId): String? - - companion object { - const val ANY_ACCOUNT_PROVIDER = "*" - } -} - -fun EnterpriseService.canConnectToAnyHomeserver(): Boolean { - return homeserverAllowList().let { - it.isEmpty() || it.contains(EnterpriseService.ANY_ACCOUNT_PROVIDER) - } } diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt index 6ccf70cd02..02fa9b282c 100644 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.matrix.api.ClientUrlContentFetcher import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @@ -33,8 +34,9 @@ class DefaultEnterpriseService( ) : EnterpriseService { override suspend fun isEnterpriseUser(sessionId: SessionId) = false override suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher) = url - override fun homeserverAllowList(): List = emptyList() - override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true + override fun accountProviderAllowList(): List = emptyList() + override fun canConnectToAnyAccountProvider(): Boolean = true + override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider) = true override suspend fun isElementProEnforced(serverName: String): Boolean { val temporaryMatrixClient = temporaryMatrixClientFactory.create(serverName).getOrElse { return false } return temporaryMatrixClient.use { client -> diff --git a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt index 026c9caf9e..f43284e0bd 100644 --- a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt +++ b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClient import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderGeneric import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.test.runTest @@ -26,15 +27,21 @@ import org.junit.Test class DefaultEnterpriseServiceTest { @Test - fun homeserverWhitelist() { + fun accountProviderAllowList() { val defaultEnterpriseService = createDefaultEnterpriseService() - assertThat(defaultEnterpriseService.homeserverAllowList()).isEmpty() + assertThat(defaultEnterpriseService.accountProviderAllowList()).isEmpty() } @Test - fun `isAllowedToConnectToHomeserver is true for all homeserver urls`() = runTest { + fun `canConnectToAnyAccountProvider is always true`() { val defaultEnterpriseService = createDefaultEnterpriseService() - assertThat(defaultEnterpriseService.isAllowedToConnectToHomeserver(A_HOMESERVER_URL)).isTrue() + assertThat(defaultEnterpriseService.canConnectToAnyAccountProvider()).isTrue() + } + + @Test + fun `isAllowedToConnectToAccountProvider is true for all account providers`() = runTest { + val defaultEnterpriseService = createDefaultEnterpriseService() + assertThat(defaultEnterpriseService.isAllowedToConnectToAccountProvider(anAccountProviderGeneric())).isTrue() } @Test diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index a8efcb5873..3ca30d410d 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -13,6 +13,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.matrix.api.ClientUrlContentFetcher +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask @@ -22,8 +23,9 @@ import kotlinx.coroutines.flow.asStateFlow class FakeEnterpriseService( private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() }, - private val defaultHomeserverListResult: () -> List = { emptyList() }, - private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() }, + private val accountProviderAllowListResult: () -> List = { emptyList() }, + private val canConnectToAnyAccountProviderResult: () -> Boolean = { true }, + private val isAllowedToConnectToAccountProviderResult: (AccountProvider) -> Boolean = { lambdaError() }, initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default, initialBrandColor: Color? = null, private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() }, @@ -44,12 +46,16 @@ class FakeEnterpriseService( tweakMasUrlResult(url, urlContentFetcher) } - override fun homeserverAllowList(): List { - return defaultHomeserverListResult() + override fun accountProviderAllowList(): List { + return accountProviderAllowListResult() } - override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean = simulateLongTask { - isAllowedToConnectToHomeserverResult(homeserverUrl) + override fun canConnectToAnyAccountProvider(): Boolean { + return canConnectToAnyAccountProviderResult() + } + + override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean = simulateLongTask { + isAllowedToConnectToAccountProviderResult(accountProvider) } override suspend fun isElementProEnforced(serverName: String): Boolean = simulateLongTask { diff --git a/features/login/api/build.gradle.kts b/features/login/api/build.gradle.kts index 2a4e3ecb7c..a0683c814e 100644 --- a/features/login/api/build.gradle.kts +++ b/features/login/api/build.gradle.kts @@ -16,4 +16,5 @@ android { dependencies { implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) } diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt index 0c35c1655f..55cfa4b1ba 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt @@ -8,6 +8,8 @@ package io.element.android.features.login.api.accesscontrol +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider + /** * Enforces the restriction an enterprise deployment can put on which account providers the user may sign in to. */ @@ -15,7 +17,7 @@ interface AccountProviderAccessControl { /** * Whether sign-in to this provider is permitted; `true` on a build with no such restriction. * - * @param accountProviderUrl the server the user is trying to use. + * @param accountProvider the account provider the user is trying to use. */ - suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String): Boolean + suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt index a3a475df2f..58fbeed4bc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt @@ -14,16 +14,16 @@ import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.IsEnterpriseBuild import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl import io.element.android.features.login.impl.changeserver.AccountProviderAccessException +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider @ContributesBinding(AppScope::class) class DefaultAccountProviderAccessControl( private val isEnterpriseBuild: IsEnterpriseBuild, private val enterpriseService: EnterpriseService, ) : AccountProviderAccessControl { - override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try { + override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider) = try { assertIsAllowedToConnectToAccountProvider( - title = accountProviderUrl, - accountProviderUrl = accountProviderUrl, + accountProvider = accountProvider, ) true } catch (_: AccountProviderAccessException) { @@ -32,22 +32,22 @@ class DefaultAccountProviderAccessControl( @Throws(AccountProviderAccessException::class) suspend fun assertIsAllowedToConnectToAccountProvider( - title: String, - accountProviderUrl: String, + accountProvider: AccountProvider, ) { if (isEnterpriseBuild().not()) { // Ensure that Element Pro is not required for this account provider - if (enterpriseService.isElementProEnforced(accountProviderUrl)) { + if (enterpriseService.isElementProEnforced(accountProvider.serverNameOrBaseUrl())) { throw AccountProviderAccessException.NeedElementProException( - unauthorisedAccountProviderTitle = title, + unauthorisedAccountProviderTitle = accountProvider.friendlyServerName(), applicationId = ELEMENT_PRO_APPLICATION_ID, ) } } - if (enterpriseService.isAllowedToConnectToHomeserver(accountProviderUrl).not()) { + if (enterpriseService.isAllowedToConnectToAccountProvider(accountProvider).not()) { throw AccountProviderAccessException.UnauthorizedAccountProviderException( - unauthorisedAccountProviderTitle = title, - authorisedAccountProviderTitles = enterpriseService.homeserverAllowList(), + unauthorisedAccountProviderTitle = accountProvider.friendlyServerName(), + authorisedAccountProviderTitles = enterpriseService.accountProviderAllowList() + .map { it.friendlyServerName() }, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt deleted file mode 100644 index 7ca467da7d..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.login.impl.accountprovider - -data class AccountProvider( - val url: String, - val title: String = url.removePrefix("https://").removePrefix("http://"), - val subtitle: String? = null, - val isPublic: Boolean = false, - val isMatrixOrg: Boolean = false, -) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index 6d85f2c5ff..3e8e7657a4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -11,10 +11,10 @@ package io.element.android.features.login.impl.accountprovider import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn -import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.enterprise.api.EnterpriseService -import io.element.android.features.enterprise.api.canConnectToAnyHomeserver import io.element.android.libraries.di.annotations.AppCoroutineScope +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider +import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -33,11 +33,9 @@ class AccountProviderDataSource( ) { // The provider used when the user has not selected one: an enterprise/MDM-configured provider, // else matrix.org. The most recently used provider (from history) can override it, see init. - private val configuredAccountProvider = createAccountProvider( - url = enterpriseService.homeserverAllowList() - .firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } - ?: AuthenticationConfig.MATRIX_ORG_URL - ) + private val configuredAccountProvider = enterpriseService.accountProviderAllowList() + .firstOrNull() + ?: matrixOrgAccountProvider private val accountProvider: MutableStateFlow = MutableStateFlow(configuredAccountProvider) @@ -46,7 +44,7 @@ class AccountProviderDataSource( // The account provider the user last explicitly selected (via [setAccountProvider] / [setUrl]). // Unlike [flow], this is not recomputed by [reset], so it survives to be persisted to history on a // successful sign-in even when the login flow is torn down in between (e.g. across an OAuth round-trip). - var lastSelectedAccountProviderUrl: String? = null + var lastSelectedAccountProvider: AccountProvider? = null private set init { @@ -66,28 +64,19 @@ class AccountProviderDataSource( * connect to any provider, otherwise the enterprise/MDM-configured provider. */ private suspend fun defaultAccountProvider(): AccountProvider { - if (!enterpriseService.canConnectToAnyHomeserver()) { + if (!enterpriseService.canConnectToAnyAccountProvider()) { return configuredAccountProvider } val lastUsedProvider = appPreferencesStore.getHomeserverHistoryFlow().first().firstOrNull() - return lastUsedProvider?.let { createAccountProvider(it) } ?: configuredAccountProvider + return lastUsedProvider?.let { AccountProvider.Generic(it) } ?: configuredAccountProvider } suspend fun setUrl(url: String) { - setAccountProvider(createAccountProvider(url)) + setAccountProvider(AccountProvider.Generic(url)) } suspend fun setAccountProvider(data: AccountProvider) { - lastSelectedAccountProviderUrl = data.url + lastSelectedAccountProvider = data accountProvider.emit(data) } - - private fun createAccountProvider(url: String): AccountProvider { - return AccountProvider( - url = url, - subtitle = null, - isPublic = url == AuthenticationConfig.MATRIX_ORG_URL, - isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL, - ) - } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPreviewParam.kt index c14827d1a6..aca64b9db7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPreviewParam.kt @@ -9,27 +9,27 @@ package io.element.android.features.login.impl.accountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.appconfig.AuthenticationConfig +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider open class AccountProviderPreviewParam : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - anAccountProvider(), - anAccountProvider().copy(subtitle = null), - anAccountProvider().copy(subtitle = null, title = "invalid"), - anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false), - // Add other state here + anAccountProviderManaged(), ) } -fun anAccountProvider( - url: String = AuthenticationConfig.MATRIX_ORG_URL, - subtitle: String? = "Matrix.org is an open network for secure, decentralized communication.", - isPublic: Boolean = true, - isMatrixOrg: Boolean = true, -) = AccountProvider( - url = url, - subtitle = subtitle, - isPublic = isPublic, - isMatrixOrg = isMatrixOrg, +fun anAccountProviderManaged( + serverName: String = "matrix.org", + baseUrl: String = "https://matrix-client.matrix.org", + canUseServerName: Boolean = true, +) = AccountProvider.Managed( + serverName = serverName, + baseUrl = baseUrl, + canUseServerName = canUseServerName, +) + +fun anAccountProviderGeneric( + serverName: String = "matrix.org", +) = AccountProvider.Generic( + serverName = serverName, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 09b423f713..8365e9286c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -14,17 +14,14 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.login.impl.R import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize import io.element.android.libraries.designsystem.preview.ElementPreview @@ -32,6 +29,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider /** * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817 @@ -60,37 +58,19 @@ fun AccountProviderView( .heightIn(min = 44.dp), verticalAlignment = Alignment.CenterVertically ) { - if (item.isMatrixOrg) { - RoundedIconAtom( - size = RoundedIconAtomSize.Medium, - resourceId = R.drawable.ic_matrix, - tint = Color.Unspecified, - ) - } else { - RoundedIconAtom( - size = RoundedIconAtomSize.Medium, - imageVector = CompoundIcons.Host(), - tint = ElementTheme.colors.iconPrimary, - ) - } + RoundedIconAtom( + size = RoundedIconAtomSize.Medium, + imageVector = CompoundIcons.Host(), + tint = ElementTheme.colors.iconPrimary, + ) Text( modifier = Modifier .padding(start = 16.dp) .weight(1f), - text = item.title, + text = item.friendlyServerName(), style = ElementTheme.typography.fontBodyLgMedium, color = ElementTheme.colors.textPrimary, ) - if (item.isPublic) { - Icon( - modifier = Modifier - .padding(start = 10.dp) - .size(16.dp), - imageVector = CompoundIcons.Public(), - contentDescription = null, - tint = ElementTheme.colors.iconSecondary, - ) - } if (selected) { Icon( modifier = Modifier @@ -101,15 +81,6 @@ fun AccountProviderView( ) } } - if (item.subtitle != null) { - Text( - modifier = Modifier - .padding(start = 46.dp, bottom = 12.dp, end = 26.dp), - text = item.subtitle, - style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textSecondary, - ) - } } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistory.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistory.kt index b3b99115cc..14c1b0346b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistory.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistory.kt @@ -25,8 +25,11 @@ class SaveAccountProviderToHistory( suspend operator fun invoke() { // Persist the provider the user actually selected, not the currently-exposed default: the latter can be // reset back to the history default while the login flow is torn down across an OAuth round-trip. - val url = accountProviderDataSource.lastSelectedAccountProviderUrl - ?: accountProviderDataSource.flow.value.url - appPreferencesStore.addHomeserverToHistory(url) + val accountProvider = accountProviderDataSource.lastSelectedAccountProvider + ?: accountProviderDataSource.flow.value + // Persist the sanitized name the user knows the provider by rather than its base url, which can be an + // internal client API url (e.g. matrix.org resolves to matrix-client.matrix.org) the user would not + // recognise, and so that the same provider is not stored twice with and without its scheme. + appPreferencesStore.addHomeserverToHistory(accountProvider.serverNameOrBaseUrl()) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt index f3fd02713f..d1b68190c6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.login.impl.changeserver -import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider sealed interface ChangeServerEvent { data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvent diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index 9106d6e1c4..2e3a4c73b8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -14,13 +14,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import dev.zacsweers.metro.Inject import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails @@ -38,7 +38,7 @@ class ChangeServerPresenter( } val gateState = localNetworkPermissionGate.present( - urlOf = { provider -> provider.url }, + urlOf = { provider -> provider.serverNameOrBaseUrl() }, onProceed = { provider -> changeServer(provider, changeServerAction) }, ) @@ -63,11 +63,8 @@ class ChangeServerPresenter( changeServerAction: MutableState>, ) { suspend { - defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider( - title = data.title, - accountProviderUrl = data.url, - ) - val details = authenticationService.setHomeserver(data.url).getOrThrow() + defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(data) + val details = authenticationService.setHomeserver(data.serverNameOrBaseUrl()).getOrThrow() if (!details.isSupported) { throw ChangeServerError.UnsupportedServer } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt index bb50f123cb..d3fddeeb1f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider -import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider sealed interface ChooseAccountProviderEvent { data class SelectAccountProvider(val accountProvider: AccountProvider) : ChooseAccountProviderEvent diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt index e2863a295a..8984216ac8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt @@ -14,14 +14,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.enterprise.api.EnterpriseService -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.login.LoginModeEvent import io.element.android.features.login.impl.login.LoginModeState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.uri.ensureProtocol +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import kotlinx.collections.immutable.toImmutableList @Inject @@ -41,7 +39,7 @@ class ChooseAccountProviderPresenter( loginModeState.eventSink( LoginModeEvent.Submit( isAccountCreation = false, - homeserverUrl = provider.url, + homeserverUrl = provider.serverNameOrBaseUrl(), resolvedHomeserverUrl = null, loginHint = null, ) @@ -59,18 +57,8 @@ class ChooseAccountProviderPresenter( } val staticAccountProviderList = remember { - // The list cannot contains ANY_ACCOUNT_PROVIDER ("*") and cannot be empty at this point - enterpriseService.homeserverAllowList() - .map { it.ensureProtocol() } - .map { url -> - AccountProvider( - url = url, - subtitle = null, - isPublic = url == AuthenticationConfig.MATRIX_ORG_URL, - isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL, - ) - } - .toImmutableList() + // The list cannot be empty at this point + enterpriseService.accountProviderAllowList().toImmutableList() } return ChooseAccountProviderState( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt index 398e32d687..b1e84fe8ec 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt @@ -8,9 +8,9 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.login.LoginModeState import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import kotlinx.collections.immutable.ImmutableList data class ChooseAccountProviderState( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt index ceb1cfd6ea..9dc99b466c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt @@ -9,31 +9,22 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.login.impl.accountprovider.AccountProvider -import io.element.android.features.login.impl.accountprovider.anAccountProvider +import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged import io.element.android.features.login.impl.login.LoginModeState import io.element.android.features.login.impl.login.aLoginModeState import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import kotlinx.collections.immutable.toImmutableList open class ChooseAccountProviderStatePreviewParam : PreviewParameterProvider { - private val server1 = anAccountProvider( - url = "https://server1.io", - subtitle = null, - isPublic = false, - isMatrixOrg = false, + private val server1 = anAccountProviderManaged( + baseUrl = "https://server1.io", ) - private val server2 = anAccountProvider( - url = "https://server2.io", - subtitle = null, - isPublic = false, - isMatrixOrg = false, + private val server2 = anAccountProviderManaged( + baseUrl = "https://server2.io", ) - private val server3 = anAccountProvider( - url = "https://server3.io", - subtitle = null, - isPublic = false, - isMatrixOrg = false, + private val server3 = anAccountProviderManaged( + baseUrl = "https://server3.io", ) override val values: Sequence get() = sequenceOf( @@ -61,13 +52,12 @@ open class ChooseAccountProviderStatePreviewParam : PreviewParameterProvider = listOf( - anAccountProvider() + anAccountProviderManaged() ), selectedAccountProvider: AccountProvider? = null, loginModeState: LoginModeState = aLoginModeState(), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt index 552165a7b2..c2d0bd05b2 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt @@ -96,16 +96,8 @@ fun ChooseAccountProviderView( ) state.accountProviders.forEach { item -> - val alteredItem = if (item.isMatrixOrg) { - // Set the subtitle from the resource - item.copy( - subtitle = stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle), - ) - } else { - item - } AccountProviderView( - item = alteredItem, + item = item, selected = item == state.selectedAccountProvider, onClick = { state.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(item)) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 627620ff31..201571a8ff 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -20,9 +20,7 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject -import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.enterprise.api.EnterpriseService -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.changeserver.ChangeServerEvent import io.element.android.features.login.impl.changeserver.ChangeServerState @@ -30,8 +28,11 @@ import io.element.android.features.login.impl.login.LoginModeEvent import io.element.android.features.login.impl.login.LoginModeState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.uri.ensureProtocol +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider +import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import kotlinx.coroutines.flow.map @AssistedInject class ConfirmAccountProviderPresenter( @@ -56,23 +57,25 @@ class ConfirmAccountProviderPresenter( val accountProvider by accountProviderDataSource.flow.collectAsState() val loginModeState = loginModePresenter.present() val changeServerState = changeServerPresenter.present() - val homeserverHistory by appPreferencesStore.getHomeserverHistoryFlow().collectAsState(emptyList()) + val homeserverHistory by remember { + appPreferencesStore.getHomeserverHistoryFlow().map { history -> + history.map { AccountProvider.Generic(it) } + } + }.collectAsState(emptyList()) // The account providers offered for autocomplete: previously-used ones first, then the // enterprise/MDM-configured allow-list, then matrix.org (always available, even before any sign-in). - // The "*" wildcard is a routing marker, not a real provider, so it is filtered out. Everything is - // rendered without the https:// scheme (added back at connection time). + // Everything is rendered without the https:// scheme (added back at connection time). val autocompleteCandidates = remember(homeserverHistory) { - (homeserverHistory + enterpriseService.homeserverAllowList() + AuthenticationConfig.MATRIX_ORG_URL) - .filter { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } - .map { it.withoutScheme() } + (homeserverHistory + enterpriseService.accountProviderAllowList() + matrixOrgAccountProvider) + .map { it.friendlyServerName() } .distinct() } // Editable input, seeded from the current (history-defaulted) account provider until the user edits it. // Displayed without the scheme, so the field shows e.g. "matrix.org" rather than "https://matrix.org". var userInput by rememberSaveable { mutableStateOf(null) } - val accountProviderInput = userInput ?: accountProvider.url.withoutScheme() + val accountProviderInput = userInput ?: accountProvider.friendlyServerName() // Offer the first candidate that the current input is a (case-insensitive) prefix of. val accountProviderSuggestion = remember(accountProviderInput, autocompleteCandidates) { @@ -134,7 +137,7 @@ class ConfirmAccountProviderPresenter( val accountProviderUrl = host.ensureProtocol() submittedAccountProvider = accountProviderUrl changeServerState.eventSink( - ChangeServerEvent.ChangeServer(AccountProvider(url = accountProviderUrl)) + ChangeServerEvent.ChangeServer(AccountProvider.Generic(host)) ) } ConfirmAccountProviderEvent.ClearError -> { @@ -154,9 +157,3 @@ class ConfirmAccountProviderPresenter( ) } } - -/** - * The host part of an account provider URL, i.e. the value with any `https://` / `http://` scheme removed. - * Returns the input unchanged when it has no scheme (e.g. `matrix.org`). - */ -private fun String.withoutScheme(): String = substringAfter("://") diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt index db04e7b7ba..13d189af6f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt @@ -9,8 +9,8 @@ package io.element.android.features.login.impl.screens.loginpassword import android.os.Parcelable -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.parcelize.Parcelize diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt index ba48e57b5d..bae4ed4df3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt @@ -9,9 +9,9 @@ package io.element.android.features.login.impl.screens.loginpassword import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.login.impl.accountprovider.AccountProvider -import io.element.android.features.login.impl.accountprovider.anAccountProvider +import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.core.SessionId open class LoginPasswordStatePreviewParam : PreviewParameterProvider { @@ -26,7 +26,7 @@ open class LoginPasswordStatePreviewParam : PreviewParameterProvider = AsyncData.Uninitialized, eventSink: (LoginPasswordEvent) -> Unit = {}, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index 9a2dc3097b..c2a983a692 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -131,7 +131,7 @@ fun LoginPasswordView( iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()), title = stringResource( id = R.string.screen_account_provider_signin_title, - state.accountProvider.title + state.accountProvider.friendlyServerName() ), subTitle = stringResource(id = R.string.screen_login_subtitle) ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt index 4b3993e2d1..6d5979946d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt @@ -8,9 +8,11 @@ package io.element.android.features.login.impl.screens.onboarding +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider + sealed interface OnBoardingEvent { data class OnSignIn( - val defaultAccountProvider: String + val defaultAccountProvider: AccountProvider ) : OnBoardingEvent data object OnVersionClick : OnBoardingEvent diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index 7e48ae22e8..c108aa1de0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -22,7 +22,6 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import io.element.android.appconfig.OnBoardingConfig import io.element.android.features.enterprise.api.EnterpriseService -import io.element.android.features.enterprise.api.canConnectToAnyHomeserver import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.login.LoginModeEvent @@ -31,6 +30,7 @@ import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.ui.utils.MultipleTapToUnlock import kotlinx.coroutines.launch @@ -59,27 +59,34 @@ class OnBoardingPresenter( @Composable override fun present(): OnBoardingState { val localCoroutineScope = rememberCoroutineScope() - val forcedAccountProvider = remember { - // If homeserverAllowList() returns a singleton list, this is the default account provider. - // In this case, the user can sign in using this homeserver, or use QrCode login - enterpriseService.homeserverAllowList().singleOrNull() + val canConnectToAnyAccountProvider = remember { + enterpriseService.canConnectToAnyAccountProvider() } - val canConnectToAnyHomeserver = remember { - enterpriseService.canConnectToAnyHomeserver() + val forcedAccountProvider = remember { + // If accountProviderAllowList() returns a singleton list, and the user is not free to use another + // account provider, this is the default account provider. + // In this case, the user can sign in using this homeserver, or use QrCode login + if (canConnectToAnyAccountProvider) { + null + } else { + enterpriseService.accountProviderAllowList().singleOrNull() + } } val mustChooseAccountProvider = remember { - !canConnectToAnyHomeserver && enterpriseService.homeserverAllowList().size > 1 + !canConnectToAnyAccountProvider && enterpriseService.accountProviderAllowList().size > 1 } - val linkAccountProvider by produceState(initialValue = null) { + val linkAccountProvider by produceState(initialValue = null) { // Account provider from the link, if allowed by the enterprise service - value = params.accountProvider?.takeIf { - try { - defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(it, it) - true - } catch (_: Exception) { - false + value = params.accountProvider + ?.let { AccountProvider.Generic(it) } + ?.takeIf { + try { + defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(it) + true + } catch (_: Exception) { + false + } } - } } val defaultAccountProvider = remember(linkAccountProvider) { // If there is a forced account provider, this is the default account provider @@ -105,11 +112,11 @@ class OnBoardingPresenter( when (event) { is OnBoardingEvent.OnSignIn -> localCoroutineScope.launch { // Ensure that the current account provider is set - accountProviderDataSource.setUrl(event.defaultAccountProvider) + accountProviderDataSource.setAccountProvider(event.defaultAccountProvider) loginModeState.eventSink( LoginModeEvent.Submit( isAccountCreation = false, - homeserverUrl = event.defaultAccountProvider, + homeserverUrl = event.defaultAccountProvider.serverNameOrBaseUrl(), resolvedHomeserverUrl = null, loginHint = params.loginHint?.takeIf { forcedAccountProvider == null }, ) @@ -134,7 +141,7 @@ class OnBoardingPresenter( defaultAccountProvider = defaultAccountProvider, mustChooseAccountProvider = mustChooseAccountProvider, canLoginWithQrCode = canLoginWithQrCode, - canCreateAccount = defaultAccountProvider == null && canConnectToAnyHomeserver && OnBoardingConfig.CAN_CREATE_ACCOUNT, + canCreateAccount = defaultAccountProvider == null && canConnectToAnyAccountProvider && OnBoardingConfig.CAN_CREATE_ACCOUNT, canReportBug = canReportBug && showReportBug, loginModeState = loginModeState, version = buildMeta.versionName, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt index 97ebed67f6..87d4605ceb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt @@ -11,13 +11,14 @@ package io.element.android.features.login.impl.screens.onboarding import androidx.annotation.DrawableRes import io.element.android.features.login.impl.login.LoginModeState import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider data class OnBoardingState( val isAddingAccount: Boolean, val showBackButton: Boolean, val showDeveloperSettings: Boolean, val productionApplicationName: String, - val defaultAccountProvider: String?, + val defaultAccountProvider: AccountProvider?, val mustChooseAccountProvider: Boolean, val canLoginWithQrCode: Boolean, val canCreateAccount: Boolean, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt index c7d89b21d5..09a2c8a245 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.login.LoginModeState import io.element.android.features.login.impl.login.aLoginModeState import io.element.android.libraries.designsystem.R +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider open class OnBoardingStatePreviewParam : PreviewParameterProvider { override val values: Sequence @@ -22,7 +23,11 @@ open class OnBoardingStatePreviewParam : PreviewParameterProvider - - - diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt index fbfae984fc..86bbd0eadb 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt @@ -11,19 +11,30 @@ package io.element.android.features.login.impl.accesscontrol import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.changeserver.AccountProviderAccessException +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Test class DefaultAccountProviderAccessControlTest { + /** + * The account provider the assertions are run against: its server name is the title reported by the exceptions, + * its base url is what the enterprise service is queried with. + */ + private fun anAccountProviderUnderTest() = anAccountProviderManaged( + serverName = AN_ACCOUNT_PROVIDER, + baseUrl = AN_ACCOUNT_PROVIDER_URL, + ) + @Test fun `foss build should not allow using account provider that enforce enterprise build`() { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, - isAllowedToConnectToHomeserver = true, + isAllowedToConnectToAccountProvider = true, enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }), ) accessControl.expectNeedElementProException() @@ -34,7 +45,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, // false here. - isAllowedToConnectToHomeserver = false, + isAllowedToConnectToAccountProvider = false, enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }), ) accessControl.expectNeedElementProException() @@ -44,9 +55,9 @@ class DefaultAccountProviderAccessControlTest { fun `foss build should allow using account provider that does not enforce enterprise build`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, - isAllowedToConnectToHomeserver = true, + isAllowedToConnectToAccountProvider = true, enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false } ), ) @@ -57,7 +68,7 @@ class DefaultAccountProviderAccessControlTest { fun `foss build should allow using account provider twith missing key in wellknown`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, - isAllowedToConnectToHomeserver = true, + isAllowedToConnectToAccountProvider = true, ) accessControl.expectAllowed() } @@ -66,7 +77,7 @@ class DefaultAccountProviderAccessControlTest { fun `foss build should allow using account provider with missing wellknown`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, - isAllowedToConnectToHomeserver = true, + isAllowedToConnectToAccountProvider = true, ) accessControl.expectAllowed() } @@ -75,12 +86,12 @@ class DefaultAccountProviderAccessControlTest { fun `foss build should not allow using account provider that do not enforce enterprise build but is not allowed`() { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, - isAllowedToConnectToHomeserver = false, - allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), + isAllowedToConnectToAccountProvider = false, + allowedAccountProviders = listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)), enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { false }, + isAllowedToConnectToAccountProviderResult = { false }, isElementProEnforcedResult = { false }, - defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)) }, ), ) accessControl.expectUnauthorizedAccountProviderException() @@ -90,9 +101,9 @@ class DefaultAccountProviderAccessControlTest { fun `enterprise build should allow using account provider that enforce enterprise build`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, - isAllowedToConnectToHomeserver = true, + isAllowedToConnectToAccountProvider = true, enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { true }, ), ) @@ -103,9 +114,9 @@ class DefaultAccountProviderAccessControlTest { fun `enterprise build should allow using account provider that do not enforce enterprise build`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, - isAllowedToConnectToHomeserver = true, + isAllowedToConnectToAccountProvider = true, enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), ) @@ -116,12 +127,12 @@ class DefaultAccountProviderAccessControlTest { fun `enterprise build should not allow using account provider that enforce enterprise build but is not allowed`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, - isAllowedToConnectToHomeserver = false, - allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), + isAllowedToConnectToAccountProvider = false, + allowedAccountProviders = listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)), enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { false }, + isAllowedToConnectToAccountProviderResult = { false }, isElementProEnforcedResult = { true }, - defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)) }, ), ) accessControl.expectUnauthorizedAccountProviderException() @@ -131,12 +142,12 @@ class DefaultAccountProviderAccessControlTest { fun `enterprise build should not allow using account provider that do not enforce enterprise build but is not allowed`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, - isAllowedToConnectToHomeserver = false, - allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), + isAllowedToConnectToAccountProvider = false, + allowedAccountProviders = listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)), enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { false }, + isAllowedToConnectToAccountProviderResult = { false }, isElementProEnforcedResult = { false }, - defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)) }, ), ) accessControl.expectUnauthorizedAccountProviderException() @@ -144,11 +155,11 @@ class DefaultAccountProviderAccessControlTest { private fun createDefaultAccountProviderAccessControl( isEnterpriseBuild: Boolean = false, - isAllowedToConnectToHomeserver: Boolean = false, - allowedAccountProviders: List = emptyList(), + isAllowedToConnectToAccountProvider: Boolean = false, + allowedAccountProviders: List = emptyList(), enterpriseService: FakeEnterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver }, - defaultHomeserverListResult = { allowedAccountProviders }, + isAllowedToConnectToAccountProviderResult = { isAllowedToConnectToAccountProvider }, + accountProviderAllowListResult = { allowedAccountProviders }, isElementProEnforcedResult = { false }, ) ) = DefaultAccountProviderAccessControl( @@ -159,19 +170,14 @@ class DefaultAccountProviderAccessControlTest { private fun DefaultAccountProviderAccessControl.expectNeedElementProException() { val exception = assertThrows(AccountProviderAccessException.NeedElementProException::class.java) { runTest { - assertIsAllowedToConnectToAccountProvider( - title = AN_ACCOUNT_PROVIDER, - accountProviderUrl = AN_ACCOUNT_PROVIDER_URL, - ) + assertIsAllowedToConnectToAccountProvider(anAccountProviderUnderTest()) } } assertThat(exception.unauthorisedAccountProviderTitle).isEqualTo(AN_ACCOUNT_PROVIDER) assertThat(exception.applicationId).isEqualTo("io.element.enterprise") runTest { assertThat( - isAllowedToConnectToAccountProvider( - accountProviderUrl = AN_ACCOUNT_PROVIDER_URL, - ) + isAllowedToConnectToAccountProvider(anAccountProviderUnderTest()) ).isFalse() } } @@ -179,34 +185,24 @@ class DefaultAccountProviderAccessControlTest { private fun DefaultAccountProviderAccessControl.expectUnauthorizedAccountProviderException() { val exception = assertThrows(AccountProviderAccessException.UnauthorizedAccountProviderException::class.java) { runTest { - assertIsAllowedToConnectToAccountProvider( - title = AN_ACCOUNT_PROVIDER, - accountProviderUrl = AN_ACCOUNT_PROVIDER_URL, - ) + assertIsAllowedToConnectToAccountProvider(anAccountProviderUnderTest()) } } assertThat(exception.unauthorisedAccountProviderTitle).isEqualTo(AN_ACCOUNT_PROVIDER) assertThat(exception.authorisedAccountProviderTitles).containsExactly(AN_ACCOUNT_PROVIDER_2) runTest { assertThat( - isAllowedToConnectToAccountProvider( - accountProviderUrl = AN_ACCOUNT_PROVIDER_URL, - ) + isAllowedToConnectToAccountProvider(anAccountProviderUnderTest()) ).isFalse() } } private suspend fun DefaultAccountProviderAccessControl.expectAllowed() { // If no exception is thrown, the test passes - assertIsAllowedToConnectToAccountProvider( - title = AN_ACCOUNT_PROVIDER, - accountProviderUrl = AN_ACCOUNT_PROVIDER_URL, - ) + assertIsAllowedToConnectToAccountProvider(anAccountProviderUnderTest()) runTest { assertThat( - isAllowedToConnectToAccountProvider( - accountProviderUrl = AN_ACCOUNT_PROVIDER_URL, - ) + isAllowedToConnectToAccountProvider(anAccountProviderUnderTest()) ).isTrue() } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt index 30e25c8c3e..ff03d79989 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt @@ -10,9 +10,10 @@ package io.element.android.features.login.impl.accountprovider import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.appconfig.AuthenticationConfig -import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider +import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest @@ -24,61 +25,24 @@ class AccountProviderDataSourceTest { val warmUpRule = WarmUpRule() @Test - fun `present - initial state`() = runTest { + fun `present - initial state - matrix org is the default when nothing is configured`() = runTest { val sut = anAccountProviderDataSource() sut.flow.test { - val initialState = awaitItem() - assertThat(initialState).isEqualTo( - AccountProvider( - url = AuthenticationConfig.MATRIX_ORG_URL, - title = "matrix.org", - subtitle = null, - isPublic = true, - isMatrixOrg = true, - ) - ) + assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider) } } @Test - fun `present - initial state - matrix org`() = runTest { + fun `present - initial state - the first configured account provider is the default`() = runTest { val sut = anAccountProviderDataSource( enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(AuthenticationConfig.MATRIX_ORG_URL) } + accountProviderAllowListResult = { + listOf(anAccountProviderManaged(serverName = "first.org"), anAccountProviderManaged(serverName = "second.org")) + } ), ) sut.flow.test { - val initialState = awaitItem() - assertThat(initialState).isEqualTo( - AccountProvider( - url = AuthenticationConfig.MATRIX_ORG_URL, - title = "matrix.org", - subtitle = null, - isPublic = true, - isMatrixOrg = true, - ) - ) - } - } - - @Test - fun `present - ensure that default homeserver is not star char`() = runTest { - val sut = anAccountProviderDataSource( - enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(EnterpriseService.ANY_ACCOUNT_PROVIDER, AuthenticationConfig.MATRIX_ORG_URL) } - ), - ) - sut.flow.test { - val initialState = awaitItem() - assertThat(initialState).isEqualTo( - AccountProvider( - url = AuthenticationConfig.MATRIX_ORG_URL, - title = "matrix.org", - subtitle = null, - isPublic = true, - isMatrixOrg = true, - ) - ) + assertThat(awaitItem()).isEqualTo(anAccountProviderManaged(serverName = "first.org")) } } @@ -86,22 +50,11 @@ class AccountProviderDataSourceTest { fun `present - user change and reset`() = runTest { val sut = anAccountProviderDataSource() sut.flow.test { - val initialState = awaitItem() - assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) - sut.setAccountProvider(AccountProvider(url = "https://example.com")) - val changedState = awaitItem() - assertThat(changedState).isEqualTo( - AccountProvider( - url = "https://example.com", - title = "example.com", - subtitle = null, - isPublic = false, - isMatrixOrg = false, - ) - ) + assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider) + sut.setAccountProvider(AccountProvider.Generic("https://example.com")) + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com")) sut.reset() - val resetState = awaitItem() - assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) + assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider) } } @@ -109,22 +62,23 @@ class AccountProviderDataSourceTest { fun `present - set url and reset`() = runTest { val sut = anAccountProviderDataSource() sut.flow.test { - val initialState = awaitItem() - assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) + assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider) sut.setUrl(url = "https://example.com") - val changedState = awaitItem() - assertThat(changedState).isEqualTo( - AccountProvider( - url = "https://example.com", - title = "example.com", - subtitle = null, - isPublic = false, - isMatrixOrg = false, - ) + assertThat(awaitItem()).isEqualTo( + AccountProvider.Generic("https://example.com") ) sut.reset() - val resetState = awaitItem() - assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) + assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider) + } + } + + @Test + fun `present - a provider set by the user is stored as they input it`() = runTest { + val sut = anAccountProviderDataSource() + sut.flow.test { + skipItems(1) + sut.setUrl(url = "example.com") + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("example.com")) } } @@ -136,7 +90,7 @@ class AccountProviderDataSourceTest { ), ) sut.flow.test { - assertThat(awaitItem().url).isEqualTo("https://example.com") + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com")) } } @@ -144,14 +98,31 @@ class AccountProviderDataSourceTest { fun `present - history is ignored when the account provider is enforced`() = runTest { val sut = anAccountProviderDataSource( enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf("https://enforced.org") } + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "enforced.org")) }, + canConnectToAnyAccountProviderResult = { false }, ), appPreferencesStore = InMemoryAppPreferencesStore( homeserverHistory = listOf("https://example.com"), ), ) sut.flow.test { - assertThat(awaitItem().url).isEqualTo("https://enforced.org") + assertThat(awaitItem()).isEqualTo(anAccountProviderManaged(serverName = "enforced.org")) + } + } + + @Test + fun `present - history wins over the configured provider when other providers are allowed`() = runTest { + val sut = anAccountProviderDataSource( + enterpriseService = FakeEnterpriseService( + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "preferred.org")) }, + canConnectToAnyAccountProviderResult = { true }, + ), + appPreferencesStore = InMemoryAppPreferencesStore( + homeserverHistory = listOf("https://example.com"), + ), + ) + sut.flow.test { + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com")) } } @@ -163,11 +134,11 @@ class AccountProviderDataSourceTest { ), ) sut.flow.test { - assertThat(awaitItem().url).isEqualTo("https://example.com") + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com")) sut.setUrl("https://other.com") - assertThat(awaitItem().url).isEqualTo("https://other.com") + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://other.com")) sut.reset() - assertThat(awaitItem().url).isEqualTo("https://example.com") + assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com")) } } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistoryTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistoryTest.kt index 0af8188f3f..604e01acfe 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistoryTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/SaveAccountProviderToHistoryTest.kt @@ -20,7 +20,7 @@ class SaveAccountProviderToHistoryTest { val warmUpRule = WarmUpRule() @Test - fun `invoke saves the current account provider url to history`() = runTest { + fun `invoke saves the current account provider server name to history`() = runTest { val appPreferencesStore = InMemoryAppPreferencesStore() val accountProviderDataSource = anAccountProviderDataSource() accountProviderDataSource.setUrl("https://example.com") @@ -29,7 +29,7 @@ class SaveAccountProviderToHistoryTest { sut() assertThat(appPreferencesStore.getHomeserverHistoryFlow().first()) - .containsExactly("https://example.com") + .containsExactly("example.com") } @Test @@ -45,6 +45,6 @@ class SaveAccountProviderToHistoryTest { sut() assertThat(appPreferencesStore.getHomeserverHistoryFlow().first()) - .containsExactly("https://example.com") + .containsExactly("example.com") } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index 44a3bbd5cb..f43a575d37 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -12,14 +12,15 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog @@ -57,13 +58,13 @@ class ChangeServerPresenterTest { createPresenter( authenticationService = authenticationService, enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL))) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() @@ -80,14 +81,14 @@ class ChangeServerPresenterTest { ) createPresenter( enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), authenticationService = authenticationService, ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL))) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() @@ -108,14 +109,14 @@ class ChangeServerPresenterTest { ) createPresenter( enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), authenticationService = authenticationService, ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL))) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() @@ -128,30 +129,30 @@ class ChangeServerPresenterTest { @Test fun `present - change server not allowed error`() = runTest { - val isAllowedToConnectToHomeserverResult = lambdaRecorder { false } + val isAllowedToConnectToAccountProviderResult = lambdaRecorder { false } createPresenter( enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult, - defaultHomeserverListResult = { listOf("element.io") }, + isAllowedToConnectToAccountProviderResult = isAllowedToConnectToAccountProviderResult, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "element.io")) }, isElementProEnforcedResult = { false }, ), ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL) + val anAccountProvider = AccountProvider.Generic(A_HOMESERVER_URL) initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider)) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle - ).isEqualTo(anAccountProvider.title) + ).isEqualTo(anAccountProvider.serverNameOrBaseUrl()) assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles ).containsExactly("element.io") - isAllowedToConnectToHomeserverResult.assertions() + isAllowedToConnectToAccountProviderResult.assertions() .isCalledOnce() - .with(value(A_HOMESERVER_URL)) + .with(value(AccountProvider.Generic(A_HOMESERVER_URL))) } } @@ -166,14 +167,14 @@ class ChangeServerPresenterTest { ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL) + val anAccountProvider = AccountProvider.Generic(A_HOMESERVER_URL) initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider)) // Skip loading state skipItems(1) val failureState = awaitItem() assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).unauthorisedAccountProviderTitle - ).isEqualTo(anAccountProvider.title) + ).isEqualTo(anAccountProvider.serverNameOrBaseUrl()) assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId ).isEqualTo("io.element.enterprise") @@ -188,13 +189,13 @@ class ChangeServerPresenterTest { ) createPresenter( authenticationService = authenticationService, - enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }), + enterpriseService = FakeEnterpriseService(isAllowedToConnectToAccountProviderResult = { true }), localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true), permissionsPresenter = FakePermissionsPresenter(), ).test { val initialState = awaitItem() assertThat(initialState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None) - initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL))) val promptState = expectMostRecentItem() // Dialog is shown, permission has not been requested yet, setHomeserver has not been called. assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None) @@ -208,12 +209,12 @@ class ChangeServerPresenterTest { authenticationService = FakeMatrixAuthenticationService( setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) }, ), - enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }), + enterpriseService = FakeEnterpriseService(isAllowedToConnectToAccountProviderResult = { true }), localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true), permissionsPresenter = FakePermissionsPresenter(), ).test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL))) val promptState = expectMostRecentItem() assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None) promptState.eventSink.invoke(ChangeServerEvent.DismissLocalNetworkPermission) @@ -231,12 +232,12 @@ class ChangeServerPresenterTest { val permissionsPresenter = FakePermissionsPresenter() createPresenter( authenticationService = authenticationService, - enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }, isElementProEnforcedResult = { false }), + enterpriseService = FakeEnterpriseService(isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }), localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true), permissionsPresenter = permissionsPresenter, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL))) val promptState = expectMostRecentItem() assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None) permissionsPresenter.setPermissionGranted() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt index 78f4293489..17711cc469 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt @@ -11,14 +11,13 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.login.LoginModePresenter import io.element.android.features.login.impl.screens.onboarding.createLoginModePresenter import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3 import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test @@ -33,18 +32,8 @@ class ChooseAccountProviderPresenterTest { companion object { private const val ACCOUNT_PROVIDER_FROM_CONFIG_1 = AN_ACCOUNT_PROVIDER_2 private const val ACCOUNT_PROVIDER_FROM_CONFIG_2 = AN_ACCOUNT_PROVIDER_3 - val accountProvider1 = AccountProvider( - url = ACCOUNT_PROVIDER_FROM_CONFIG_1.ensureProtocol(), - subtitle = null, - isPublic = false, - isMatrixOrg = false, - ) - val accountProvider2 = AccountProvider( - url = ACCOUNT_PROVIDER_FROM_CONFIG_2.ensureProtocol(), - subtitle = null, - isPublic = false, - isMatrixOrg = false, - ) + val accountProvider1 = anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_1) + val accountProvider2 = anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_2) } @Test @@ -61,7 +50,7 @@ class ChooseAccountProviderPresenterTest { fun `present - initial state`() = runTest { val presenter = createPresenter( enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) }, + accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) }, ), ) presenter.test { @@ -79,7 +68,7 @@ class ChooseAccountProviderPresenterTest { val authenticationService = FakeMatrixAuthenticationService() val presenter = createPresenter( enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) }, + accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) }, ), loginModePresenter = createLoginModePresenter( authenticationService = authenticationService, @@ -103,7 +92,7 @@ class ChooseAccountProviderPresenterTest { ) val presenter = createPresenter( enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) }, + accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) }, ), loginModePresenter = createLoginModePresenter( authenticationService = authenticationService, @@ -136,7 +125,7 @@ class ChooseAccountProviderPresenterTest { val authenticationService = FakeMatrixAuthenticationService() val presenter = createPresenter( enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) }, + accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) }, ), loginModePresenter = createLoginModePresenter( authenticationService = authenticationService, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateTest.kt index a5cb54e7e1..c4af8f9544 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateTest.kt @@ -9,7 +9,7 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.accountprovider.anAccountProvider +import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged import io.element.android.features.login.impl.login.LoginMode import io.element.android.features.login.impl.login.aLoginModeState import io.element.android.libraries.architecture.AsyncData @@ -27,7 +27,7 @@ class ChooseAccountProviderStateTest { @Test fun `submitEnabled returns true when there is a selectedAccountProvider`() { val sut = aChooseAccountProviderState( - selectedAccountProvider = anAccountProvider(), + selectedAccountProvider = anAccountProviderManaged(), ) assertThat(sut.submitEnabled).isTrue() } @@ -35,7 +35,7 @@ class ChooseAccountProviderStateTest { @Test fun `submitEnabled returns false when there is a selectedAccountProvider but there is an error`() { val sut = aChooseAccountProviderState( - selectedAccountProvider = anAccountProvider(), + selectedAccountProvider = anAccountProviderManaged(), loginModeState = aLoginModeState(loginMode = AsyncData.Failure(Throwable("Error"))), ) assertThat(sut.submitEnabled).isFalse() @@ -44,7 +44,7 @@ class ChooseAccountProviderStateTest { @Test fun `submitEnabled returns false when there is a selectedAccountProvider but the result is successful`() { val sut = aChooseAccountProviderState( - selectedAccountProvider = anAccountProvider(), + selectedAccountProvider = anAccountProviderManaged(), loginModeState = aLoginModeState(loginMode = AsyncData.Success(LoginMode.PasswordLogin)), ) assertThat(sut.submitEnabled).isFalse() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt index 4ef7a71966..9bb8da717e 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.v2.runAndroidComposeUiTest -import io.element.android.features.login.impl.accountprovider.anAccountProvider +import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged import io.element.android.features.login.impl.login.aLoginModeState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.auth.OAuthDetails @@ -57,11 +57,11 @@ class ChooseAccountProviderViewTest : RobolectricTest() { ChooseAccountProviderPresenterTest.accountProvider1, ChooseAccountProviderPresenterTest.accountProvider2, ), - selectedAccountProvider = anAccountProvider(), + selectedAccountProvider = anAccountProviderManaged(), eventSink = eventSink, ), ) - onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick() + onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.friendlyServerName()).performClick() eventSink.assertSingle(ChooseAccountProviderEvent.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1)) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index 19afbe0f28..9bccb2c261 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -10,7 +10,6 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat -import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl @@ -24,6 +23,7 @@ import io.element.android.features.login.impl.screens.onboarding.createLoginMode import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails import io.element.android.libraries.oauth.api.OAuthAction @@ -44,6 +44,11 @@ class ConfirmAccountProviderPresenterTest { @get:Rule val warmUpRule = WarmUpRule() + companion object { + private const val MATRIX_ORG_SERVER_NAME = "matrix.org" + private const val MATRIX_ORG_URL = "https://matrix.org" + } + @Test fun `present - initial test`() = runTest { val presenter = createConfirmAccountProviderPresenter() @@ -68,7 +73,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin) cancelAndIgnoreRemainingEvents() @@ -87,7 +92,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) cancelAndIgnoreRemainingEvents() @@ -108,7 +113,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) authenticationService.givenOAuthCancelError(AN_EXCEPTION) @@ -133,7 +138,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) defaultOAuthActionFlow.post(OAuthAction.GoBack()) @@ -157,7 +162,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) awaitLoginMode { it is AsyncData.Loading } defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true)) val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized } @@ -180,7 +185,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) authenticationService.givenLoginError(AN_EXCEPTION) @@ -205,7 +210,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) defaultOidcActionFlow.post(OAuthAction.Success("aUrl")) @@ -227,7 +232,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) // The account provider validation fails, so the login is never attempted. val failureState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure } assertThat(failureState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized) @@ -249,7 +254,7 @@ class ConfirmAccountProviderPresenterTest { val initialState = awaitItem() // Submit will return an error while validating the account provider - initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) // Check an error was returned val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure } @@ -274,7 +279,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) // Check an error was returned val submittedState = awaitLoginMode { it is AsyncData.Failure } assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java) @@ -299,7 +304,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) val submittedState = awaitLoginMode { it is AsyncData.Success } assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) cancelAndIgnoreRemainingEvents() @@ -344,7 +349,7 @@ class ConfirmAccountProviderPresenterTest { val suggestionState = awaitState { it.accountProviderSuggestion != null } suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org")) awaitLoginMode { it is AsyncData.Success } - assertThat(submittedUrls.first()).isEqualTo("https://randomcommunity.org") + assertThat(submittedUrls.first()).isEqualTo("randomcommunity.org") cancelAndIgnoreRemainingEvents() } } @@ -375,8 +380,7 @@ class ConfirmAccountProviderPresenterTest { val presenter = createConfirmAccountProviderPresenter( appPreferencesStore = InMemoryAppPreferencesStore(homeserverHistory = emptyList()), enterpriseService = FakeEnterpriseService( - // Preferred servers plus the "*" (any) wildcard: the wildcard is filtered out, the rest complete. - defaultHomeserverListResult = { listOf("https://element.io", EnterpriseService.ANY_ACCOUNT_PROVIDER) }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "element.io")) }, ), ) presenter.test { @@ -387,6 +391,19 @@ class ConfirmAccountProviderPresenterTest { } } + @Test + fun `present - offers the previously used servers without the scheme as autocomplete suggestions`() = runTest { + val presenter = createConfirmAccountProviderPresenter( + appPreferencesStore = InMemoryAppPreferencesStore(homeserverHistory = listOf("https://previous.org")), + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("prev")) + val suggestionState = awaitState { it.accountProviderInput == "prev" } + assertThat(suggestionState.accountProviderSuggestion).isEqualTo("previous.org") + } + } + @Test fun `present - offers matrix_org without the scheme as an autocomplete suggestion`() = runTest { val presenter = createConfirmAccountProviderPresenter( @@ -432,7 +449,7 @@ class ConfirmAccountProviderPresenterTest { val initialState = awaitItem() initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org")) awaitLoginMode { it is AsyncData.Success } - assertThat(submittedUrls.first()).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) + assertThat(submittedUrls.first()).isEqualTo(MATRIX_ORG_SERVER_NAME) cancelAndIgnoreRemainingEvents() } } @@ -453,9 +470,9 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL)) awaitLoginMode { it is AsyncData.Success } - assertThat(submittedUrls).containsExactly(AuthenticationConfig.MATRIX_ORG_URL) + assertThat(submittedUrls).containsExactly(MATRIX_ORG_SERVER_NAME) cancelAndIgnoreRemainingEvents() } } @@ -474,7 +491,7 @@ class ConfirmAccountProviderPresenterTest { val initialState = awaitItem() initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org")) awaitLoginMode { it is AsyncData.Success } - assertThat(submittedUrls.first()).isEqualTo("https://example.org") + assertThat(submittedUrls.first()).isEqualTo("example.org") assertThat(authenticationService.getOAuthUrlLoginHint).isEqualTo("mxid:@alice:example.org") cancelAndIgnoreRemainingEvents() } @@ -494,7 +511,7 @@ class ConfirmAccountProviderPresenterTest { val initialState = awaitItem() initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org:8448")) awaitLoginMode { it is AsyncData.Success } - assertThat(submittedUrls.first()).isEqualTo("https://example.org:8448") + assertThat(submittedUrls.first()).isEqualTo("example.org:8448") cancelAndIgnoreRemainingEvents() } } @@ -570,7 +587,7 @@ class ConfirmAccountProviderPresenterTest { accountProviderDataSource = accountProviderDataSource, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), isEnterpriseBuild = { false }, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index b12c9ec667..015ece9e23 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -9,11 +9,11 @@ package io.element.android.features.login.impl.screens.loginpassword import com.google.common.truth.Truth.assertThat -import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.SaveAccountProviderToHistory import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_PASSWORD @@ -39,7 +39,7 @@ class LoginPasswordPresenterTest { fun `present - initial state`() = runTest { createLoginPasswordPresenter().test { val initialState = awaitItem() - assertThat(initialState.accountProvider.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) + assertThat(initialState.accountProvider).isEqualTo(matrixOrgAccountProvider) assertThat(initialState.formState).isEqualTo(LoginFormState.Default) assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.submitEnabled).isFalse() @@ -127,7 +127,7 @@ class LoginPasswordPresenterTest { val loggedInState = awaitItem() assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID)) assertThat(appPreferencesStore.getHomeserverHistoryFlow().first()) - .containsExactly(AuthenticationConfig.MATRIX_ORG_URL) + .containsExactly(matrixOrgAccountProvider.serverName) } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index 1088a87538..4fecc3ada8 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -9,7 +9,6 @@ package io.element.android.features.login.impl.screens.onboarding import com.google.common.truth.Truth.assertThat -import io.element.android.appconfig.AuthenticationConfig import io.element.android.appconfig.OnBoardingConfig import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.IsEnterpriseBuild @@ -22,6 +21,8 @@ import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissio import io.element.android.features.login.impl.login.LoginModePresenter import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider +import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 @@ -30,6 +31,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2 import io.element.android.libraries.matrix.test.A_LOGIN_HINT +import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.oauth.api.OAuthActionFlow @@ -82,7 +84,8 @@ class OnBoardingPresenterTest { val presenter = createPresenter( buildMeta = buildMeta, enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) }, + canConnectToAnyAccountProviderResult = { true }, ), ) presenter.test { @@ -184,15 +187,16 @@ class OnBoardingPresenterTest { showBackButton = false, ), enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) }, - isAllowedToConnectToHomeserverResult = { true }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) }, + canConnectToAnyAccountProviderResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), ) presenter.test { skipItems(3) awaitItem().also { - assertThat(it.defaultAccountProvider).isEqualTo(ACCOUNT_PROVIDER_FROM_LINK) + assertThat(it.defaultAccountProvider).isEqualTo(AccountProvider.Generic(ACCOUNT_PROVIDER_FROM_LINK)) assertThat(it.canLoginWithQrCode).isFalse() assertThat(it.canCreateAccount).isFalse() } @@ -208,8 +212,14 @@ class OnBoardingPresenterTest { showBackButton = false, ), enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, ACCOUNT_PROVIDER_FROM_CONFIG_2) }, - isAllowedToConnectToHomeserverResult = { false }, + accountProviderAllowListResult = { + listOf( + anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG), + anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_2) + ) + }, + canConnectToAnyAccountProviderResult = { false }, + isAllowedToConnectToAccountProviderResult = { false }, ), ) presenter.test { @@ -231,19 +241,60 @@ class OnBoardingPresenterTest { showBackButton = false, ), enterpriseService = FakeEnterpriseService( - defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG) }, + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) }, + canConnectToAnyAccountProviderResult = { false }, ) ) presenter.test { skipItems(1) awaitItem().also { - assertThat(it.defaultAccountProvider).isEqualTo(ACCOUNT_PROVIDER_FROM_CONFIG) + assertThat(it.defaultAccountProvider).isEqualTo(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) assertThat(it.canLoginWithQrCode).isTrue() assertThat(it.canCreateAccount).isFalse() } } } + @Test + fun `present - a single configured account provider is not forced when the user may use another one`() = runTest { + // A singleton allow list only forces the account provider when the user is not free to use another one. + val presenter = createPresenter( + enterpriseService = FakeEnterpriseService( + accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) }, + canConnectToAnyAccountProviderResult = { true }, + ), + ) + presenter.test { + awaitItem().also { + assertThat(it.defaultAccountProvider).isNull() + assertThat(it.mustChooseAccountProvider).isFalse() + } + skipItems(1) + } + } + + @Test + fun `present - the user must choose when several account providers are configured and enforced`() = runTest { + val presenter = createPresenter( + enterpriseService = FakeEnterpriseService( + accountProviderAllowListResult = { + listOf( + anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG), + anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_2) + ) + }, + canConnectToAnyAccountProviderResult = { false }, + ), + ) + presenter.test { + awaitItem().also { + assertThat(it.defaultAccountProvider).isNull() + assertThat(it.mustChooseAccountProvider).isTrue() + } + skipItems(1) + } + } + @Test fun `present - default account provider - login and clear error`() = runTest { val authenticationService = FakeMatrixAuthenticationService( @@ -259,7 +310,7 @@ class OnBoardingPresenterTest { showBackButton = false, ), enterpriseService = FakeEnterpriseService( - isAllowedToConnectToHomeserverResult = { true }, + isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }, ), loginModePresenter = createLoginModePresenter( @@ -270,12 +321,12 @@ class OnBoardingPresenterTest { presenter.test { skipItems(3) awaitItem().also { - assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL) - assertThat(accountProviderDataSource.flow.first().url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) - it.eventSink(OnBoardingEvent.OnSignIn(A_HOMESERVER_URL_2)) + assertThat(it.defaultAccountProvider).isEqualTo(AccountProvider.Generic(A_HOMESERVER_URL)) + assertThat(accountProviderDataSource.flow.first()).isEqualTo(matrixOrgAccountProvider) + it.eventSink(OnBoardingEvent.OnSignIn(AccountProvider.Generic(A_HOMESERVER_URL_2))) skipItems(1) // Loading // Account data source has been updated - assertThat(accountProviderDataSource.flow.first().url).isEqualTo(A_HOMESERVER_URL_2) + assertThat(accountProviderDataSource.flow.first()).isEqualTo(AccountProvider.Generic(A_HOMESERVER_URL_2)) // Check an error was returned val submittedState = awaitItem() assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt index 88c9acf55f..c7cc2b9f03 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt @@ -23,6 +23,7 @@ import io.element.android.features.login.impl.R import io.element.android.features.login.impl.login.LoginMode import io.element.android.features.login.impl.login.aLoginModeState import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.matrix.api.auth.OAuthDetails import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.ui.strings.CommonStrings @@ -134,13 +135,13 @@ class OnboardingViewTest : RobolectricTestParameter() { val eventSink = EventsRecorder() setOnboardingView( state = anOnBoardingState( - defaultAccountProvider = "element.io", + defaultAccountProvider = AccountProvider.Generic("element.io"), eventSink = eventSink, ), ) val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io") onNodeWithText(buttonText).performClick() - eventSink.assertSingle(OnBoardingEvent.OnSignIn("element.io")) + eventSink.assertSingle(OnBoardingEvent.OnSignIn(AccountProvider.Generic("element.io"))) } @Test @@ -148,7 +149,7 @@ class OnboardingViewTest : RobolectricTestParameter() { val eventSink = EventsRecorder() setOnboardingView( state = anOnBoardingState( - defaultAccountProvider = "element.io", + defaultAccountProvider = AccountProvider.Generic("element.io"), loginModeState = aLoginModeState(loginMode = AsyncData.Failure(AN_EXCEPTION)), eventSink = eventSink, ), diff --git a/features/login/test/build.gradle.kts b/features/login/test/build.gradle.kts index 4cbf0910db..41c231a594 100644 --- a/features/login/test/build.gradle.kts +++ b/features/login/test/build.gradle.kts @@ -16,6 +16,7 @@ android { dependencies { implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) implementation(projects.features.login.api) implementation(projects.tests.testutils) } diff --git a/features/login/test/src/main/kotlin/io/element/android/features/login/test/accesscontrol/FakeAccountProviderAccessControl.kt b/features/login/test/src/main/kotlin/io/element/android/features/login/test/accesscontrol/FakeAccountProviderAccessControl.kt index 9ebd3be7fc..2864669e7f 100644 --- a/features/login/test/src/main/kotlin/io/element/android/features/login/test/accesscontrol/FakeAccountProviderAccessControl.kt +++ b/features/login/test/src/main/kotlin/io/element/android/features/login/test/accesscontrol/FakeAccountProviderAccessControl.kt @@ -8,12 +8,13 @@ package io.element.android.features.login.test.accesscontrol import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.tests.testutils.lambda.lambdaError class FakeAccountProviderAccessControl( - private val isAllowedToConnectToAccountProviderResult: (String) -> Boolean = { lambdaError() }, + private val isAllowedToConnectToAccountProviderResult: (AccountProvider) -> Boolean = { lambdaError() }, ) : AccountProviderAccessControl { - override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String): Boolean { - return isAllowedToConnectToAccountProviderResult(accountProviderUrl) + override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean { + return isAllowedToConnectToAccountProviderResult(accountProvider) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt new file mode 100644 index 0000000000..828676285a --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.accountprovider + +sealed interface AccountProvider { + /** + * An account provider that was input by the user. + */ + data class Generic( + val serverName: String, + ) : AccountProvider + + /** + * An account provider that is provided by the app. + */ + data class Managed( + val serverName: String, + val baseUrl: String, + val canUseServerName: Boolean, + ) : AccountProvider + + fun serverNameOrBaseUrl(): String { + return when (this) { + is Generic -> serverName + is Managed -> if (canUseServerName) serverName else baseUrl + }.sanitized() + } + + fun friendlyServerName(): String { + return when (this) { + is Generic -> serverName + is Managed -> serverName + }.sanitized() + } + + /** + * The user may reach an allowed account provider by either of its identifiers: the server name it is known by + * (what the user types, e.g. `element.io`) or the base url its client API lives at (e.g. `https://element.ems.host`). + */ + fun matches(other: AccountProvider): Boolean { + return serverNameOrBaseUrl() == other.serverNameOrBaseUrl() + } + + /** + * Sanitizes the string with the following rules: + * - Trim any whitespace. + * - Lowercase the string. + * - Removes a https scheme (treating it as implicit). + * - Remove any trailing slashes. + */ + private fun String.sanitized(): String { + return trim() + .lowercase() + // Intentionally continue to show http:// as an indicator (and for history). + .removePrefix("https://") + .removeSuffix("/") + } +} + +val matrixOrgAccountProvider = AccountProvider.Generic( + serverName = "matrix.org", +) diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProviderTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProviderTest.kt new file mode 100644 index 0000000000..0268eaed2b --- /dev/null +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProviderTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.accountprovider + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class AccountProviderTest { + @Test + fun `serverNameOrBaseUrl of a generic account provider is the server name the user input`() { + assertThat(AccountProvider.Generic("matrix.org").serverNameOrBaseUrl()).isEqualTo("matrix.org") + } + + @Test + fun `serverNameOrBaseUrl of a managed account provider is its server name when it can be used`() { + val accountProvider = AccountProvider.Managed( + serverName = "element.io", + baseUrl = "https://element.ems.host", + canUseServerName = true, + ) + assertThat(accountProvider.serverNameOrBaseUrl()).isEqualTo("element.io") + } + + @Test + fun `serverNameOrBaseUrl of a managed account provider is its base url when the server name cannot be used`() { + val accountProvider = AccountProvider.Managed( + serverName = "element.io", + baseUrl = "https://element.ems.host", + canUseServerName = false, + ) + assertThat(accountProvider.serverNameOrBaseUrl()).isEqualTo("element.ems.host") + } + + @Test + fun `matrixOrgAccountProvider is known by its server name`() { + assertThat(matrixOrgAccountProvider.serverNameOrBaseUrl()).isEqualTo("matrix.org") + } + + @Test + fun `serverNameOrBaseUrl trims, lowercases and drops the implicit https scheme and a trailing slash`() { + assertThat(AccountProvider.Generic(" MATRIX.org ").serverNameOrBaseUrl()).isEqualTo("matrix.org") + assertThat(AccountProvider.Generic("https://matrix.org").serverNameOrBaseUrl()).isEqualTo("matrix.org") + assertThat(AccountProvider.Generic("https://matrix.org/").serverNameOrBaseUrl()).isEqualTo("matrix.org") + } + + @Test + fun `serverNameOrBaseUrl keeps an explicit http scheme as an indicator`() { + assertThat(AccountProvider.Generic("http://localhost:8080").serverNameOrBaseUrl()).isEqualTo("http://localhost:8080") + } + + @Test + fun `matches ignores the scheme, the case and a trailing slash`() { + val typed = AccountProvider.Generic("Element.io/") + assertThat(typed.matches(AccountProvider.Generic("https://element.io"))).isTrue() + assertThat( + typed.matches( + AccountProvider.Managed(serverName = "element.io", baseUrl = "https://element.ems.host", canUseServerName = true) + ) + ).isTrue() + } + + @Test + fun `matches a managed provider that cannot use its server name only through its base url`() { + val managed = AccountProvider.Managed( + serverName = "element.io", + baseUrl = "https://element.ems.host", + canUseServerName = false, + ) + assertThat(AccountProvider.Generic("element.ems.host").matches(managed)).isTrue() + assertThat(AccountProvider.Generic("element.io").matches(managed)).isFalse() + } + + @Test + fun `matches is false for a different account provider`() { + val typed = AccountProvider.Generic("element.io") + assertThat(typed.matches(AccountProvider.Generic("matrix.org"))).isFalse() + // A managed provider that can use its server name is not reached through its base url. + assertThat( + typed.matches( + AccountProvider.Managed(serverName = "other.io", baseUrl = "https://element.io", canUseServerName = true) + ) + ).isFalse() + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/accountprovider/AccountProviderFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/accountprovider/AccountProviderFixture.kt new file mode 100644 index 0000000000..3c9cb7033d --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/accountprovider/AccountProviderFixture.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.accountprovider + +import io.element.android.libraries.matrix.api.accountprovider.AccountProvider +import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER +import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL + +fun anAccountProviderGeneric( + serverName: String = AN_ACCOUNT_PROVIDER, +) = AccountProvider.Generic( + serverName = serverName, +) + +fun anAccountProviderManaged( + baseUrl: String = AN_ACCOUNT_PROVIDER_URL, + serverName: String = AN_ACCOUNT_PROVIDER, + canUseServerName: Boolean = true, +) = AccountProvider.Managed( + serverName = serverName, + baseUrl = baseUrl, + canUseServerName = canUseServerName, +) From bf70bb3e5077d6d81647b481d18d6f82949614a9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 14 Sep 2026 11:30:23 +0200 Subject: [PATCH 10/30] Update ref --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index 307b0fd01f..72abbbf4e1 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 307b0fd01f36191dddf6b51ed9ceb92887b360a2 +Subproject commit 72abbbf4e1b16c9079396245bca6a366fea40895 From aa69321898543de94ab61f506ae34b0e7c8e1eec Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 14 Sep 2026 12:36:16 +0000 Subject: [PATCH 11/30] Update screenshots --- ...ogin.impl.accountprovider_AccountProviderView_Day_0_en.png | 4 ++-- ...ogin.impl.accountprovider_AccountProviderView_Day_1_en.png | 3 --- ...ogin.impl.accountprovider_AccountProviderView_Day_2_en.png | 3 --- ...ogin.impl.accountprovider_AccountProviderView_Day_3_en.png | 3 --- ...in.impl.accountprovider_AccountProviderView_Night_0_en.png | 4 ++-- ...in.impl.accountprovider_AccountProviderView_Night_1_en.png | 3 --- ...in.impl.accountprovider_AccountProviderView_Night_2_en.png | 3 --- ...in.impl.accountprovider_AccountProviderView_Night_3_en.png | 3 --- ...ooseaccountprovider_ChooseAccountProviderView_Day_0_en.png | 4 ++-- ...ooseaccountprovider_ChooseAccountProviderView_Day_1_en.png | 4 ++-- ...ooseaccountprovider_ChooseAccountProviderView_Day_2_en.png | 4 ++-- ...seaccountprovider_ChooseAccountProviderView_Night_0_en.png | 4 ++-- ...seaccountprovider_ChooseAccountProviderView_Night_1_en.png | 4 ++-- ...seaccountprovider_ChooseAccountProviderView_Night_2_en.png | 4 ++-- ...nts.preview.imageeditor_AttachmentImageEditorView_2_en.png | 4 ++-- ...nts.preview.imageeditor_AttachmentImageEditorView_5_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_0_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_1_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_2_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_0_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_1_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_2_en.png | 4 ++-- ...re.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png | 4 ++-- ...re.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png | 4 ++-- ....resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png | 4 ++-- ....resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png | 4 ++-- ...meline.components.event_ATimelineItemEventRow_Day_0_en.png | 4 ++-- ...line.components.event_ATimelineItemEventRow_Night_0_en.png | 4 ++-- ....components_TimelineItemEventRowDisambiguated_Day_0_en.png | 4 ++-- ...omponents_TimelineItemEventRowDisambiguated_Night_0_en.png | 4 ++-- ...ents_TimelineItemEventRowWithReplyInformative_Day_0_en.png | 4 ++-- ...ents_TimelineItemEventRowWithReplyInformative_Day_1_en.png | 4 ++-- ...ts_TimelineItemEventRowWithReplyInformative_Night_0_en.png | 4 ++-- ...ts_TimelineItemEventRowWithReplyInformative_Night_1_en.png | 4 ++-- ...components_TimelineItemEventRowWithReplyOther_Day_0_en.png | 4 ++-- ...components_TimelineItemEventRowWithReplyOther_Day_1_en.png | 4 ++-- ...mponents_TimelineItemEventRowWithReplyOther_Night_0_en.png | 4 ++-- ...mponents_TimelineItemEventRowWithReplyOther_Night_1_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_0_en.png | 4 ++-- ...ine.components_TimelineItemEventRowWithReply_Day_10_en.png | 4 ++-- ...ine.components_TimelineItemEventRowWithReply_Day_11_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_1_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_2_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_3_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_4_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_5_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_6_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_7_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_8_en.png | 4 ++-- ...line.components_TimelineItemEventRowWithReply_Day_9_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_0_en.png | 4 ++-- ...e.components_TimelineItemEventRowWithReply_Night_10_en.png | 4 ++-- ...e.components_TimelineItemEventRowWithReply_Night_11_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_1_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_2_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_3_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_4_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_5_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_6_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_7_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_8_en.png | 4 ++-- ...ne.components_TimelineItemEventRowWithReply_Night_9_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_0_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_10_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_11_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_1_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_2_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_3_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_4_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_5_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_6_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_7_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_8_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_9_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_0_en.png | 4 ++-- .../features.messages.impl_MessagesView_Night_10_en.png | 4 ++-- .../features.messages.impl_MessagesView_Night_11_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_1_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_2_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_3_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_4_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_5_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_6_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_7_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_8_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_9_en.png | 4 ++-- ...references.impl.blockedusers_BlockedUsersView_Day_4_en.png | 4 ++-- ...references.impl.blockedusers_BlockedUsersView_Day_5_en.png | 4 ++-- ...ferences.impl.blockedusers_BlockedUsersView_Night_4_en.png | 4 ++-- ...ferences.impl.blockedusers_BlockedUsersView_Night_5_en.png | 4 ++-- ...es.preferences.impl.userstatus_UserStatusView_Day_5_en.png | 4 ++-- ...es.preferences.impl.userstatus_UserStatusView_Day_6_en.png | 4 ++-- ....preferences.impl.userstatus_UserStatusView_Night_5_en.png | 4 ++-- ....preferences.impl.userstatus_UserStatusView_Night_6_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_5_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_7_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_5_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_7_en.png | 4 ++-- ...eme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png | 4 ++-- ...e.components_HorizontalFloatingToolbarNoFab_Night_0_en.png | 4 ++-- ...em.theme.components_HorizontalFloatingToolbar_Day_0_en.png | 4 ++-- ....theme.components_HorizontalFloatingToolbar_Night_0_en.png | 4 ++-- 102 files changed, 192 insertions(+), 210 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_3_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_0_en.png index 0b20380cd5..b91b3b97b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49371b85e60a5e0465c0ba1537293f6c071962c498579bd71b42763671c8dc01 -size 18148 +oid sha256:6200774d91495f1ef0b49440b8836148f60736c510ffab7d28c7f0b9b38634bf +size 6596 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_1_en.png deleted file mode 100644 index cbe30336d4..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78ef17d2f96d5544ecc8213fe1b7f78ddf263557855901f00f152a0b543c1a6f -size 7746 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_2_en.png deleted file mode 100644 index 447de4de32..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce777b1c6895caacc21adf4f6c10c71f75a2c6d31a617afe1c7f49d10c5141b3 -size 6929 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_3_en.png deleted file mode 100644 index ae99061ada..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Day_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f49c89cde78d8d8971d5866f3cef2fef51bc36d1844b0364c950dc2afb188c5b -size 5707 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_0_en.png index 359e866105..82412586b2 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:759f68aae091a43f54d597e386a5503692f99ed9c3908450dfadf59999047d01 -size 17272 +oid sha256:8553d2b2b795ca403f340881b0c12a30bd7f0871cbb924a6987550a87cdad8fa +size 6437 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_1_en.png deleted file mode 100644 index 3f58435f8a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0591f839e4605cbc51746f9d479a0bfc37e078178e6b2d1395db07dc8a7e1261 -size 7382 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_2_en.png deleted file mode 100644 index abcf602e31..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a57866d169c69ba68365ef8cfd4739ec95f8a8aecf4805ec68b2f247f3f698dd -size 6627 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_3_en.png deleted file mode 100644 index fe5123f403..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.accountprovider_AccountProviderView_Night_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db8eda0daf8e2038c2c69ff945e686a9e5ff271d8dc4f30987f14f7acb22ae85 -size 5625 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en.png index 7869ed3dc5..95fd48ffda 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8098b9ff6c4d08d8cc61bc51e391a5a17e9791d7236d2d7cf652a6b855c87b97 -size 22501 +oid sha256:30f64c99d3bae9f34fdc855293070933e3c96ff6ea635892a4dc9f29a6e8e62c +size 22387 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en.png index c1f55034ec..d2d67db632 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94878580a4ffff7c4d437eba1c62184aa61435420d6c85488fefc53e33e1e150 -size 23497 +oid sha256:99d101dae150cf33fa9a2c5782ae18811c6238d74b35c27e54227bff1e32b28b +size 23390 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en.png index a960bff528..8b7f161b7e 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31cee1e647c74c67740320180af54509e466ad162ed56894695b038ccbe35103 -size 24017 +oid sha256:867ca4eacdbd56ea23d2b3ac619159a46fcb1670ab9a63f433cb7e44c8ceac63 +size 23910 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en.png index fc8aeb2409..bc0bf3ca43 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bdca1cc8a99976a8c4a297192650a496a4347dc27c73d1950f693be1cf2cabf -size 22005 +oid sha256:c0bb15955a7bb7ce2fecfc8227ece62c31021a3af3311bb9234d4d468a3384d8 +size 21850 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en.png index aa2b293c3b..de02c4d9bf 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:604243eb5409e170a760cda08fca1662b7694c6920f7b18da8d4c4f9d60ffe9a -size 22915 +oid sha256:b1b70aeb18d6e3e6d8d61081135dd015fb8d6a88fa4129605541097b4274cdb0 +size 22747 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en.png index 1f6dce1201..8b2c3b9bfa 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d3bb32677abc81ffcb892839c5609f27637a9ea8081460d3f95751a03f125aa -size 23426 +oid sha256:403b8e7c3488f0edbfd53afba758b3cda0638f80167c1a84ea46b75100b8a4be +size 23264 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png index 8edee63bc1..3192cf2a5e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de82970da241172b94f822f8792e6fc721dcfc7f0f67e3523962e253e9750884 -size 255933 +oid sha256:1885bbe8b0ea497ac20d7bcdd0c6a93ba0533e898378d35e1cafdc8c9924fd56 +size 279984 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png index ccfdd7d9ed..f899ad5d43 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview.imageeditor_AttachmentImageEditorView_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe6cb72de5079be2ac02ec39f74bac0d20492cd6f5509b71ad9cfc4cc52955d3 -size 326468 +oid sha256:1ca032575e4ebd75a2ebc5a4cb9f966e48cdfa56f4ff42d6d9309fa3577a1adb +size 310609 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png index 5f51b27f5e..693a36b7dd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3274314ab9e39c23a6b9d66adf80ac8bb7b364ce60a300f96582374a18bc821f -size 54590 +oid sha256:3dbb8ef39b6a7a0c2f576d8bd101aa97c89da696f23e6d5836846494471c01a1 +size 55799 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png index e1f6ee6430..b1850a50a5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e27c7c51fe07cca51b92a601aa9c58deab609bfa5e82f8f6f88eaab6ae59ec3 -size 64307 +oid sha256:f1dd0da147212e13a6c0ad5ad8f85ce55666c444076c238ea27eba5605cfab1f +size 56909 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png index 5289eaca63..4185f46b51 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65f9ee083a73669a0d3f9d88141a610ea53f3c39e89d8f706d414b04555b4764 -size 59321 +oid sha256:085963fcb425147d35a9ad947a7a3e4ba4712bd3d2f32e93099ff15cffe83b88 +size 64761 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png index d3f7da6320..2e70435fa5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a94a4cc176b5d52ef9b21339749324adeef9857b6cdaa2ae973903e2408662a -size 55904 +oid sha256:7a3667390d9d88894bbdd5d72b0b714b29c9ff160653e310e97b0da87b6ba8b0 +size 54724 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png index bd3b0196d6..0f6de1b844 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f7f7415abbe51858f938e16ad03c6aab983fb3d5e2a53a028bae3c031446646 -size 67848 +oid sha256:28512b0fda0b71b98eab03cee15c9d3163825b1a3545670cfa277ddd5434c87a +size 58417 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png index f4d51ccc61..6c774d4cec 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a8f5aaaa7ebb0e61b07867590d99a8791c5d04cad1ce0f334d23dce6f26c6e6 -size 67276 +oid sha256:6ed6a5f1a2055e19a6b8fb0af4738845bd2184f755c323022c52154cdfa07e05 +size 60909 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png index bd73e322ab..02d0fc8eeb 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 -size 3657 +oid sha256:f77d8243c23d3f37dff86dec53671cbde9fafe1314f6e6b445abdfe47d751ad6 +size 56187 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png index 93622cedfe..bd73e322ab 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87193a7c9eb9d40234970e5f5be02e80f2b3ffe91e2c9ca6afd58b8bccdd5911 -size 55075 +oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 +size 3657 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png index bd73e322ab..0cf6c10c4e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 -size 3657 +oid sha256:49f0bb16d3d57b59242e704e3dd4e8885c59721a42129837ff6a4f0d7d02de11 +size 54433 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png index bd73e322ab..adec213a8c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12020ba97720f2374f9ace172693f08be01522e4ca30d3970efa91d802581e81 -size 3657 +oid sha256:8ebee4aec3dbab6e1bb05fda7c5e423f0e931560743d0f0ccbac6b70fa9ad808 +size 52921 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png index 0ab00359b7..12bd190dd1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b5e6f4c5ec97e67bb20f03f25f18d9f32d5d1aed5ff30d161b331894ad03443 -size 647384 +oid sha256:ce15ca9f89274620728715e3448ce671e2253f4fed22a44035460e9a3709ca0b +size 647404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png index 0ccdcbb603..160d58f9ec 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0424685437a433b35b589668b409d55f26ab41bcae01af0121a54033c56a8c2 -size 646194 +oid sha256:c4e7b28b78cfbcbb6abf00317eea945859ac6465cecf81a3ad15801b0de721e4 +size 646216 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png index ab6392a424..0435f55de6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94f89a37549a3f198442db467bc7d8539f0b5649d97927bfe731f090d6dfb03d -size 379505 +oid sha256:ff839731834d7d0c94fa9a2c412014ddbaeaa053c5af1c8cbd00a3f3a583f364 +size 379511 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png index f0b5506b89..9cbd5c1d95 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04ea6c2947a21d3d4a3d43e9c4750c4f2a49056536a4da3f34c95df8353e9de3 -size 377990 +oid sha256:d0e170b4bea262de612bf3374d475fcd25470ff91b7554d20809c51857a78cec +size 377963 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png index 3ac59caeea..1f0fdabe6d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a94d5198e27338675c0e15df5874ba3272ee315b8d77291dc7ccc7c754ed1f4 -size 365082 +oid sha256:b8bb5a6de6601da8d7fe59f110a054577e2e25dae4bb449f60c04e7d7a1b9551 +size 365073 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png index 195aa04fe2..86018764ac 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76e5ae44362792d570cd82f3910c8bd0acde509b8418c5892e0385e7d636f042 -size 370223 +oid sha256:8344427a0fe87cbf0883ff53a9ee4dcf90976a7011f6618d85961df48b2c53a9 +size 370228 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png index 29771235f0..058a3992c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60b9f2b920cd2a2a414805d1f0f77925232d9ae60bd607fa18d4395cda2dd73d -size 363291 +oid sha256:00fe1e676086766a3eac3e4457eff52c989789fc5d8d0df2624c019ed25ce9af +size 363322 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png index db4ea8296a..c6d4541184 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f0d24b8011564ee7a8b2f8c2e6a24eda668ee42d451d862f884acb7926aa06c -size 368529 +oid sha256:c00e504593d8eb61b598f05f46ee4d591757975a80c0b4227b240847d2cd4868 +size 368510 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png index 94428abb56..84d9d4e5c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36d7cf2510d546d2b330b1d1b1674ce58df0558c547e443794a1e7b2222add87 -size 347897 +oid sha256:7c609a633ab23eae5729ecb57a17a385bac65e81cbf0180c01e0847f1028039e +size 347890 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png index 5400a98a50..40fa5d563f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40a23ddff1fe46a486a5d34d94e0e67ec14d0b2b9c6bee2ddbadc17ce0948b7b -size 367131 +oid sha256:3ff97eb99daf65f74b469d51b93bd77d304d0c780b9406e1c4d7893b71e3c68d +size 367163 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png index 22ff4965d1..bac23aab98 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:787151f8f242a8794a804bf14831f6ca680814d617e85feb3652103e8a0f20cd -size 346565 +oid sha256:e30686c7c037ef102717e0316543faf4d3ed2671c21eca96a667150daab380d6 +size 346614 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png index c3b6a086fe..2348755afd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e615c14cd23f3efd217ec20ce05ed32be76dc3317091e8221f1a72a5a594d92 -size 364175 +oid sha256:e907f67ef884edb61df187f0f229ed4ff4cb31f8c276f807698f861c0e7a4c27 +size 364124 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png index 67490ba700..01d4dca84b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4431e2d9f30123949ea5676ce60f642234adcf9b5a33f2acc3f80ebf6609a66 -size 369730 +oid sha256:ef2bf449671b652cb152b3baab92ab100eab77a54229ab49daeab156f2f484d0 +size 369724 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png index 1600afff71..f0dfbc3321 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:273351cc9c533b2424859a0de00a8a3786628abc3a88175705c4205dedf7e9aa -size 354737 +oid sha256:f7a6302320bd1e4258e03487539bf46b0b12781dfc8a22b3f9f267017a6146fa +size 354751 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png index c4943b892c..e38cf03ad6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c410e853089cc2c4a2c48a00dd9a8babc8095aed72bb4625057444710559c6c -size 368129 +oid sha256:41a0e404dea7f52359b24b3ec3926b1405429b165cfed44c63b3922ea6d3cb46 +size 368114 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png index b5dac8434c..68f8148879 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a92f8af94682facac66d4aacb6e49f345561c15be2f8b689041b78aeaaae831 -size 345062 +oid sha256:6ca8cf82aa8ab1bdad56a27584b27789402aeb7ad8fadd038280a376fb75a72c +size 345094 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png index 1b99468924..8aa2cca147 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a9509411777f342261cab58b92a022b3c9f8f0f7a9e4d0527120a91976575f0 -size 357733 +oid sha256:02ecb903124273e24151ea2088f6b99144f7dcadba00a582b6210dd23835b5c9 +size 357743 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png index cececc229a..b5032c29b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0c6dafaa41849af00d5097a85bd88e3a054a0a9e41c0466a08e6ff8dd6102b2 -size 356857 +oid sha256:dce23463c335d01eb67293cfcede5d85e96ab2ba035cc9a188d42e8e6866bc63 +size 356831 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png index 14a40a5676..a332744358 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a11044fe90227f1f316a1ade8587e545bb620e11cca76865fc2eb83cdb817fc5 -size 364655 +oid sha256:588967869cbd25ed8b82aff5618175660a8a9e45a5a861b1c7867cf1913f575b +size 364646 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png index d7b969fd5b..133fd3903f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2528df873713755f542f61cd794080929586c1bd5f316c0378ce9e837c21d50b -size 397145 +oid sha256:426d1c844a8c88281c5fa4c61916d93278fdfd38f8cdf2dc54bf1a821f2de3eb +size 397167 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png index 63bd9c3f6d..0041a870b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6cd94664f89bdad2a9761ea8b20acb83fed3ed7cb5be41af8fe4852643c0bfc -size 356175 +oid sha256:a2bd77c08be705c66225f16c29fd6826decd92b41922d0f8ffc0d1a63d8c59d2 +size 356193 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png index 9fcf389708..f67906a37c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9daeee1aaa04247da3c8bb5aaae9947aab1ed2d2057995e53d6ebdb63e5cd356 -size 355848 +oid sha256:ffa68c8398d789444151855496e5df4ffa03218a383db6ea46fd4f979829b823 +size 355831 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png index d235670def..6e83d6491d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a00bd766a00b7e7c6615fc53bf469b3ca5056a6c514c096431ae38d99d604306 -size 364845 +oid sha256:34152c742abc486a48234e22101833bc163cf11979890d06aac41e68e3c404d0 +size 364846 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png index 0021619c44..b10f9741b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b23d3a37774283067a8919b8c332b1bb5a79e90dceb7216b484b985491282e5 -size 355389 +oid sha256:3ec1b0244dab22414c6719b3309426e13128d0c70fb67dc66cd8a60d42521fd0 +size 355411 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png index 422c324695..77abe20982 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1df8d22766981fc76d6d767dc3f40d98cadc7c51be20417b067b91af604232b -size 367884 +oid sha256:5c3d87862c6d7fc88d0f23cd54c5b9f77028992fdd7c282a195b964dd17e85ac +size 367855 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png index 448bcec677..1d34761a9b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe0daf9161bc2da194992a2fc9505bf2009c5dbed78cdb35d2eb7ed1380a7df5 -size 353037 +oid sha256:f7d9b5fc0ff5d7828ad6f4dc0c9bd9520b4920553da97ed7b4e030ee844c0208 +size 353054 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png index c081fa16a5..5243218ae3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a92bcf5885322e11a706071969fb674b1f6e60e021af153aaa125baa4a602ae -size 366285 +oid sha256:9a85db2f5958db867578663533f204b2e803372d470da890fd8056ffed0cf46c +size 366254 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png index 47a9a6b630..b6d1f07279 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54372845e922d047408dd960d048b9b5eae2263d589117d8e7d197741aa34f32 -size 342827 +oid sha256:4bee12a6bdb18fea5ac6997671bfcb903296da7278cf69b2456330066ca31dfb +size 342870 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png index 5560c21967..ceca184bd7 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af4a48ee80535f2c0a226c4ffa4344054fb63b7b0a88ea2c030a4eb8dd5e9644 -size 356078 +oid sha256:8e82011a72f36889c53ab61ed2b9947c7fb5f868c9b7010904fc10f4ca587082 +size 356082 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png index 5cc5cfe4fc..5cdad1df92 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6524cc87257cc9d3f135904f4e14594b56df695e71fe576418d2319c472d710b -size 355285 +oid sha256:9e2d8caf5aaae7d3406c703288e491f328552f0acd18e56afd5265b4ceaf1cd1 +size 355272 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png index e9d85990e8..a431a9429a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66d4b6ea4a697735bbb0fac936b89cde99c5077973ba828894bf50b5dae233b3 -size 362848 +oid sha256:93c2df04d65f6078ce3f7067fbae8a7317ec42ed0fc013dee962a3953269f3b7 +size 362813 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png index b51a89586b..0eb106f4a9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e642229c58169775fe702769a306bbef22698516ae6f7ac75a761213c3f195fa -size 395446 +oid sha256:217ac95bf476c53d2dc6d3d0143b21312bf38b8ab86c8dfd4f9360582b584f9d +size 395476 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png index aa322fc2e4..ae8495618a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fae87094ea13b14e2dc7c16ca96e4f8096ff39e962799468fb4ead4c666620f -size 354381 +oid sha256:08057fdc1e1184720b438ddd2a8753f00791baf2456cc30b45de8e6bdeb57e8e +size 354404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png index 40105c1498..9d617f6688 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ed24d190f10c079f8cf176404372d40eb25cc78ce595a22febc540315057b86 -size 354075 +oid sha256:dabad9dcd5b8f0d4bb964a0187ad7c94b023b04552dacd29a077daddfd48d550 +size 354052 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png index 0cfd1dc985..1a8ea3e4e9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:545c9e8933e420cc3f8fbba0fbd5162c946c65a20721faeb77c568596ca720cd -size 363045 +oid sha256:66aba7b4271a990fac85b0bbc7cbdc371fa1e38a4da5bb4e2f62a80b21cb3a8f +size 363012 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png index c9227ebf94..c8306263a0 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e614f4a8657637c584a283d69124236d00fe1251c2b8d739c5688a57a6fd152e -size 353693 +oid sha256:756f0cff90ddd61adce2f467c0384d74d430f6d51c091e4980e45bcc9ee6f0fd +size 353705 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png index 7a5e5e1512..144411969f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e3467a7606a5a7beaafb510faf008ed567b1e3a9915ede6e2ec09c491af0bb9 -size 56552 +oid sha256:84415250503d5e586af7b7504aaa915c1e24dec0a3d28446487c474c354e05c9 +size 55534 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png index 843adcdff0..52e023ec49 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:446ae29cbb211665f6d91129a7dee0f718b407fcb2a64d3f2c0e962e1cb47af7 -size 50168 +oid sha256:c8420417cf8ba717747dead34b9646515f35eb94e1f71aa952b77c3ff433043e +size 49684 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png index ee23a7111d..adabb6dde5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38ae233a128e887efc48da73be3d6ce50a7095601315ed49a3c9cb209ffdda26 -size 58352 +oid sha256:9b071ba6e99b68dcc75f5aaed3a879f2e30614c954b88c6dc05e1dfec57f1079 +size 65771 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png index 2a5991cf59..6c2a7e8460 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:115c725873fb206d8f732a8297a6548ba4fa45d7328196b79a2d9e35545de7d5 -size 40347 +oid sha256:984536295516b542ec6aecf7d07baa3c6dfebbc66403b6d4258a6217616cec93 +size 39991 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_2_en.png index 7ce91f4684..a1dc68a3d5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b749e1260d6ba2b7fef5ae9efd58612f6d67d1788f6c1e5cebc22d58d0fd7b7 -size 58563 +oid sha256:871d92a7565dd233ea87d53658be4e5202775def8e17fea8d386b8975c3b065e +size 53868 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png index f6456f0ec1..479d07ae5f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22153168f6f3e84ab789faea29c94fffbc1a5e4c1b9a98deccb0460192fdde23 -size 57213 +oid sha256:0d4f03ac10bbb676d272dba906e758b25d633a1da1e249fe23ba825f8d2f4901 +size 55298 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png index 6a3ffb84b6..c90dc8d08f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48b91e52306daa750b4b5792535e1f1865ae3b74a36215376a51dafdd0dcd506 -size 54837 +oid sha256:e48a364a28a67d7d090faa23ef88b27dbd47f0a2f552857490728cb6952ce08b +size 53454 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png index abf8eb7086..6c0a7b4edc 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e79e1cc59ba62ca8eb99300eb520b05d7ac6d52c077e3010c03b474865b15f4 -size 58374 +oid sha256:f5aa22c3d79a392625ae231abc046f07f7cdabd91613d5e6f07aede5382aba9b +size 60410 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png index 5d4f5bdaeb..ac1d94a4dd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08fb14008b08d86755c3473e7194ef0a39bb73c3f0d48122ca80c6a019ec98eb -size 48676 +oid sha256:be5b086ab6934bae88b39987bc51e709c5adb81bdd04fa028e97832c725fd68a +size 50219 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png index b3af9d831a..2209fcb54f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce795c4e32a1be3d57f6b1c87588d8c7ed0442996ab9d8a96f1ec5a07627e598 -size 59663 +oid sha256:bd0de7412503e93c33cf92dde0cff38bb68555ef176080d913320af5e4942e7a +size 58642 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png index 2c88d322d4..82ef5e7d6a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:963fcec9647d0e5e57bd3fbe3edd389c17be7558c1da7e93fa44f34db34307ee -size 57726 +oid sha256:41c4bd5cd1bcbedd91de0a64967fb3f3e03b5f0937ae1ea1a93a7c4a1b24a4a0 +size 56262 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png index 281aad62a7..135d84dd90 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85f6f4c64f67faa99b9af65307a0cd3b46a68db52503c9c929375408597995d9 -size 62094 +oid sha256:4ff538a81d013fdc90dd7be466f710088eea93e081d23448b0731d301a4a3bb2 +size 60702 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png index cfa6e4dbc3..89fc6708d2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a3281f35461edb900ea434c68543dc684a5fad7dab7c9aabac510bc59d442b3 -size 56479 +oid sha256:89467c5b84d0a15210d4013013598d9994d3005a2299e44583d78355e6718ad8 +size 55538 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png index 5f3a619097..17df34a50a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19d14e3d779b163053c28e66d3381f0cabe3b540744606e955cd733f9e044089 -size 51713 +oid sha256:6548f5bf1906e1dddcbcc03bb8f76b42e604075751ea8efbc3ead826bf4cfad7 +size 51258 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png index 6e8468054f..b3109663f2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:262e8d4b8daa2d25593834fd0ab5d7019b0a3dd13707aa96e0701bf9240c6525 -size 59809 +oid sha256:10bce6df962a5198f019f509b1cdb55e28195587de4b74742ddc29833f1333a4 +size 69216 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png index b32ee34f99..1ff5ace3e3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bde66c53d81f882cde9ed527e8ff82c7e9d6cb1199b440d4b721659f7189bb42 -size 38906 +oid sha256:8d875f83a3a1977e2a2cf151dcf9d731108a8590eb3643eb057727421eb1e955 +size 38812 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png index f4ab74029b..f425dd2c4c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51a5b682243b969bc2d7d609decb823b0509d240f3ff14ee78319260af76a47f -size 58740 +oid sha256:bd6dc088e16f722b36c2198d6a8611af87dec2451f94ca3c2b6880b97f709b98 +size 54095 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png index 3974618f39..b0cd5bc6d4 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:718d5c9f24c114f3bf88e000b637ea19598d7bb9b97001ba137fc12a6a1285e4 -size 52608 +oid sha256:5e900ff75d02c9db4200e2032444dd7e2f8635973cca3389dd96d7d2bb734061 +size 50757 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png index 0ad1e67deb..486d5da5c6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82e6e38576a507b19e58fce9dda0734ec00bcedcb0baf8f4c7919dbaaf7d2b85 -size 55046 +oid sha256:e482603e6d03fffb57d4eb135edb6209c748c51b1f07e638a4ccf133b1b87a79 +size 52920 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png index a009d0f584..5a877a59e1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:478851e9e2e8c80095aa552b98a5d574749200ff49899c9472c913b07459c4b5 -size 53758 +oid sha256:5a582219d3143acb08563193bf7106e1ee8f6612e359d0a192e9a2f67c6799e3 +size 55375 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png index f82a7a8219..0383bb8f95 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22ea7fe121a63b35a2c2bb47609e692f8bf2b913871a6baf3d420c04334c6c4a -size 44344 +oid sha256:7c88b3ed8cc69dee59d4d0a726a9e383d00f2815b6525315f7dcd96f9c25ec2a +size 45618 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png index 7261c9b671..952751a962 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cf3d727da78b39ecf24d4df7db0884af9008db67cc92274849c1b0978f87726 -size 59252 +oid sha256:ed13a2778b8ced86ee41d5460d274b42b714a87f59749e3b26bf75114ef40dd9 +size 58265 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png index cfa62a6626..0f7fe2db38 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfe848e4c365cf4b4d663145f74e5f8b3f47a4c8994961c530603f0248400316 -size 57117 +oid sha256:a576d52b840730334a38046cf8b57993693fc760b4fbdc00376cb48b7268d76e +size 55890 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png index 89065e6bf0..4d6afdbbb1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a966c9a1fa4fdd933f4ef9239592e90458a2819ff4a45c857714d2e38c0df5b2 -size 64405 +oid sha256:35069d4fae829d9afe479308dd72ad025fa011290d23133a231f4e70f601ee75 +size 62987 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png index 7cca911c45..074820e157 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 -size 52770 +oid sha256:7caacb336793b8ae4b83e9a10a82325e25e8b3c036f881898ed28688250eb6e0 +size 55744 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png index 2af459c158..7cca911c45 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28a95d4b7ebca92c4e0467f3ecd9acdaf7f6257604d190f5d088a9a25e87d637 -size 55305 +oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 +size 52770 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png index 616c06bf46..a68f886f73 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 -size 53966 +oid sha256:e5a2e306087a967849f61872b56a83f194d1a76df5723f0bbae6e6e2f4a98c63 +size 55725 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png index 616c06bf46..36d19c6b72 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 -size 53966 +oid sha256:7dfa2767c55a81d3f121b8fa55443b7b05f9369e91639b80e0be349863799ad7 +size 55438 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png index 82f344f714..2c15fd6a37 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d96eefa01a5de803b0cc9d9109c20df86eaff01b73ff24a91945b43d53d5d01 -size 11960 +oid sha256:7260abe16fc2ab4f6ada97ea0f756dc5c9908ff3b61d6ec1723ffde302bb3dc5 +size 11955 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png index fdd8fcd5a5..7c422591ad 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22678a2a8505e1d9d5bfcb44459b6fac69c11a4bf72fae09c48ffa2e54f5a110 -size 12128 +oid sha256:2e16fc1daa0f284c442f66591e1d1f59161272a67998d26740b0b403bfe82495 +size 11866 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png index f6e27475da..acb8afa74a 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ee89826c8aa2ac0371ed31959243f62bf9968806a7b27117cd7b4d849423a33 -size 11667 +oid sha256:bed7565420e11c43acc06896ef298683d2f2dd1dc16c430eab7018e9ead9937a +size 11652 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png index b1bb69efb8..e0a4cb1c97 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e27cb9e501ccbac54926f0252b4098867643a2d2683c5b267f86ad99edf6f5d7 -size 11817 +oid sha256:87811df502c760816e74beb006341627857c39380ce715f1a747ac9fce77b226 +size 11516 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png index 73b8b8e50c..5c597300e4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8a3ad99763929d2783147814e44fa9ec7e7efb7b99949e3580f23c38fb5d971 -size 9398 +oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 +size 3853 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png index 5c597300e4..f8a1f24b34 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 -size 3853 +oid sha256:1342b6427f5b2e3075ae3ccee60bf1d3b11afaccf3e152643d6ef2d65c21b54e +size 9108 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png index d47bf90664..4ae7279fe7 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c -size 3664 +oid sha256:410a8596d4c70b9a3856d959d6d71ade8e831e8581ff410c9900957a93b3eb50 +size 8114 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png index a87d89c701..d47bf90664 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f3a0cb5d9bae6725a1c527f3d9575aabf85d3edf63a28d9faa23f4f0b65e2d0 -size 7624 +oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c +size 3664 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png index ee5c582240..58546b8522 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75de341f705bb22955f9399180519b65ad59c2e1917317d56e50ea4db78f35bc -size 12102 +oid sha256:30357bf18792db5ab20085010251170a724ffe8c00313facd04ee704e806af64 +size 10982 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png index 52fd9b9693..fedead0fa8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58b9491aaa5554b89d86c62a5c9c44975367538c6baa5562f8dc5768f76d8264 -size 8705 +oid sha256:4811667dec0f32accc5e2c8a5dd5c0b34017fcff8d1e0376ee7a4708426bca0f +size 9838 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png index 38ad0e4b56..83302d6ba4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:326d59cf768b2dbd96eb7964d54c8eda509a5cd57c6e62087625e3314da0bb4d -size 13658 +oid sha256:16dee1dfe746cd40dac9fe8f0af3ecb42cd14bab5cc490917b589beed065ed1b +size 14686 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png index 687e0d436a..4da2a98aca 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d57df7110371c9224e232b707d6a1b10ce14afcb56c803a78220792f2a0d4cf2 -size 11354 +oid sha256:ef0dc4d53daeb986f378b6e4d85928b4b6729336c89cf21e642c88c53688a3ab +size 10292 From 3d198d8065030820f0ad2ed06fff65d6928112f3 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 14 Sep 2026 15:19:14 +0200 Subject: [PATCH 12/30] Fix flaky test in `TimelinePresenterTest` (#7702) The problem seemed to be sometimes the background work to switch from live timeline to a focused event timeline wasn't finished in time and an assertion failed from time to time. By providing the coroutine to run this on we can pass a `TestScope` and force it to reliably run immediately instead in unit tests. --- .../features/messages/impl/MessagesNode.kt | 9 +++- .../impl/threads/ThreadedMessagesNode.kt | 9 +++- .../impl/timeline/TimelineController.kt | 8 +++- .../messages/impl/MessagesPresenterTest.kt | 7 ++- ...essageComposerPresenterSlashCommandTest.kt | 8 +++- .../MessageComposerPresenterTest.kt | 8 +++- .../impl/timeline/TimelineControllerTest.kt | 43 +++++++++++++++---- .../impl/timeline/TimelinePresenterTest.kt | 13 +++++- 8 files changed, 87 insertions(+), 18 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 97d3465ce7..e0213f2fb5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -51,6 +51,7 @@ import io.element.android.libraries.androidutils.system.toast import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.annotations.ApplicationContext @@ -103,6 +104,7 @@ class MessagesNode( private val roomMemberModerationRenderer: RoomMemberModerationRenderer, private val eventContentValidationCache: EventContentValidationCache, private val emojiPickerRenderer: EmojiPickerRenderer, + private val dispatchers: CoroutineDispatchers, ) : Node(buildContext, plugins = plugins), MessagesNavigator { data class Inputs( val focusedEventId: EventId?, @@ -111,7 +113,12 @@ class MessagesNode( private val inputs = inputs() private val callback: Callback = callback() - private val timelineController = TimelineController(room, room.liveTimeline) + private val timelineController = TimelineController( + room = room, + liveTimeline = room.liveTimeline, + roomCoroutineScope = room.roomCoroutineScope, + dispatchers = dispatchers, + ) private val presenter = presenterFactory.create( navigator = this, composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = null), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index cf3c701015..b5542ec4be 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -54,6 +54,7 @@ import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.di.RoomScope import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer @@ -95,6 +96,7 @@ class ThreadedMessagesNode( private val appNavigationStateService: AppNavigationStateService, private val roomMemberModerationRenderer: RoomMemberModerationRenderer, private val emojiPickerRenderer: EmojiPickerRenderer, + private val dispatchers: CoroutineDispatchers, ) : Node(buildContext, plugins = plugins), MessagesNavigator { data class Inputs( val threadRootEventId: ThreadId, @@ -113,7 +115,12 @@ class ThreadedMessagesNode( */ private suspend fun createPresenter(): Presenter { val threadedTimeline = room.createTimeline(CreateTimelineParams.Threaded(threadRootEventId = inputs.threadRootEventId)).getOrThrow() - val timelineController = TimelineController(room, threadedTimeline) + val timelineController = TimelineController( + room, + liveTimeline = threadedTimeline, + roomCoroutineScope = room.roomCoroutineScope, + dispatchers = dispatchers, + ) this.timelineController = timelineController return presenterFactory.create( navigator = this, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt index e41ac7bee7..05f7a06054 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt @@ -12,7 +12,10 @@ import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.binding import io.element.android.features.messages.impl.timeline.di.LiveTimeline +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.annotations.RoomCoroutineScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.room.CreateTimelineParams @@ -23,7 +26,6 @@ import io.element.android.libraries.matrix.api.timeline.TimelineProvider import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -47,8 +49,10 @@ import java.util.Optional class TimelineController( private val room: JoinedRoom, @LiveTimeline private val liveTimeline: Timeline, + @RoomCoroutineScope private val roomCoroutineScope: CoroutineScope, + dispatchers: CoroutineDispatchers, ) : Closeable, TimelineProvider { - private val coroutineScope = CoroutineScope(SupervisorJob()) + private val coroutineScope = roomCoroutineScope.childScope(dispatchers.computation, "TimelineController") private val liveTimelineFlow = flowOf(liveTimeline) private val detachedTimelineFlow = MutableStateFlow>(Optional.empty()) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 4c0abc0a34..949443f1f7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -1484,7 +1484,12 @@ class MessagesPresenterTest { clipboardHelper = clipboardHelper, htmlConverterProvider = FakeHtmlConverterProvider(), buildMeta = aBuildMeta(), - timelineController = TimelineController(joinedRoom, timeline), + timelineController = TimelineController( + room = joinedRoom, + liveTimeline = timeline, + roomCoroutineScope = backgroundScope, + dispatchers = coroutineDispatchers, + ), permalinkParser = permalinkParser, analyticsService = analyticsService, encryptionService = encryptionService, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt index faf0bbe6ed..a8f6c338f5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt @@ -65,6 +65,7 @@ import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -307,7 +308,12 @@ class MessageComposerPresenterSlashCommandTest { permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter), permalinkParser = permalinkParser, permalinkBuilder = permalinkBuilder, - timelineController = TimelineController(room, timeline), + timelineController = TimelineController( + room = room, + liveTimeline = timeline, + roomCoroutineScope = backgroundScope, + dispatchers = testCoroutineDispatchers(), + ), draftService = draftService, mentionSpanProvider = mentionSpanProvider, pillificationHelper = textPillificationHelper, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 4170ae41ab..6744cdebbb 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -113,6 +113,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.robolectric.RobolectricTest import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.waitForPredicate import io.mockk.mockk import kotlinx.collections.immutable.ImmutableList @@ -1707,7 +1708,12 @@ class MessageComposerPresenterTest : RobolectricTest() { permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter), permalinkParser = permalinkParser, permalinkBuilder = permalinkBuilder, - timelineController = TimelineController(room, timeline), + timelineController = TimelineController( + room = room, + liveTimeline = timeline, + roomCoroutineScope = backgroundScope, + dispatchers = testCoroutineDispatchers(), + ), draftService = draftService, mentionSpanProvider = mentionSpanProvider, pillificationHelper = textPillificationHelper, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt index bc36766bac..d00062ba6b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt @@ -21,8 +21,10 @@ import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -35,7 +37,10 @@ class TimelineControllerTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) } ) - val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) sut.activeTimelineFlow().test { awaitItem().also { state -> @@ -74,7 +79,10 @@ class TimelineControllerTest { } } ) - val sut = TimelineController(joinedRoom, liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) sut.activeTimelineFlow().test { awaitItem().also { state -> @@ -102,7 +110,10 @@ class TimelineControllerTest { val joinedRoom = FakeJoinedRoom( liveTimeline = liveTimeline ) - val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) sut.activeTimelineFlow().test { awaitItem().also { state -> assertThat(state).isEqualTo(liveTimeline) @@ -121,7 +132,10 @@ class TimelineControllerTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) } ) - val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) sut.activeTimelineFlow().test { awaitItem().also { state -> assertThat(state).isEqualTo(liveTimeline) @@ -149,7 +163,10 @@ class TimelineControllerTest { val joinedRoom = FakeJoinedRoom( liveTimeline = liveTimeline ) - val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) assertThat(sut.timelineItems().first()).hasSize(1) } @@ -171,7 +188,10 @@ class TimelineControllerTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) } ) - val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) sut.activeTimelineFlow().test { sut.focusOnEvent(AN_EVENT_ID, null) awaitItem().also { state -> @@ -196,7 +216,10 @@ class TimelineControllerTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) } ) - val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline) + val sut = createTimelineController( + room = joinedRoom, + liveTimeline = liveTimeline, + ) sut.activeTimelineFlow().test { awaitItem().also { state -> @@ -220,12 +243,14 @@ class TimelineControllerTest { } } -internal fun createTimelineController( +internal fun TestScope.createTimelineController( room: FakeJoinedRoom = FakeJoinedRoom(liveTimeline = FakeTimeline()), liveTimeline: Timeline = FakeTimeline(name = "live"), ): TimelineController { return TimelineController( room = room, - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + roomCoroutineScope = backgroundScope, + dispatchers = testCoroutineDispatchers(), ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 290246ffb9..60f13f4ea8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -1079,7 +1079,11 @@ class TimelinePresenterTest { val focusedState = consumeItemsUntilPredicate { it.focusRequestState == FocusRequestState.Success(AN_EVENT_ID) }.last() assertThat(focusedState.isLive).isFalse() initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.FORWARDS)) - advanceUntilIdle() + + // Give some time for any potential pagination to be called, but it should not be called because the focused event is not rendered yet. + // Also, given enough time, a new state should be emitted with isLive = false, because we switched to a focused event timeline. + runCurrent() + assert(paginateLambda).isNeverCalled() assertThat(expectMostRecentItem().isLive).isFalse() } @@ -1906,7 +1910,12 @@ class TimelinePresenterTest { sendPollResponseAction = sendPollResponseAction, sessionPreferencesStore = sessionPreferencesStore, timelineItemIndexer = timelineItemIndexer, - timelineController = TimelineController(room, timeline), + timelineController = TimelineController( + room = room, + liveTimeline = timeline, + roomCoroutineScope = backgroundScope, + dispatchers = testCoroutineDispatchers(), + ), resolveVerifiedUserSendFailurePresenter = resolveVerifiedUserSendFailurePresenter, typingNotificationPresenter = { aTypingNotificationState() }, roomCallStatePresenter = { aStandByCallState() }, From f9a493ef0c1d9b847201f4f75ce4874d8882f098 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 14 Sep 2026 16:55:25 +0200 Subject: [PATCH 13/30] Add Kdoc on data class AccountProvider --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index 72abbbf4e1..7f157d8fa2 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 72abbbf4e1b16c9079396245bca6a366fea40895 +Subproject commit 7f157d8fa2c77ad95f9c86f4cac20446c00cf2f3 From 84b6224e4dea5436be9fe5a43ad7443b58699ee8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 14 Sep 2026 17:18:18 +0200 Subject: [PATCH 14/30] Update ref --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index 7f157d8fa2..0b2a44594f 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 7f157d8fa2c77ad95f9c86f4cac20446c00cf2f3 +Subproject commit 0b2a44594f22b49b42d77818981bb0d4ad68e11c From 600a989854dde728bf9d18ed49bacd5c155e2829 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:09:23 +0000 Subject: [PATCH 15/30] Update kotlinpoet to v2.4.0 (#7709) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6602dbdf14..29b0c0c171 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ android_gradle_plugin = "9.3.2" # When updating this, please also update the version in the file ./idea/kotlinc.xml kotlin = "2.4.10" -kotlinpoet = "2.3.0" +kotlinpoet = "2.4.0" ksp = "2.3.11" firebaseAppDistribution = "5.3.0" From f97dac2d5b488c580d111dd05605e8863d5e6fef Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 14 Sep 2026 18:09:52 +0200 Subject: [PATCH 16/30] Update ref Point to the enterprise submodule rebased on main, which brings the X.509 feature, and resolves the MdmData conflict between the new UseX509 entry and the split of AccountProvider into AccountProviderServerName, AccountProviderManagedBaseUrl and AccountProviderManagedCanUseServerName. Co-Authored-By: Claude Opus 5 (1M context) --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index 0b2a44594f..2f6b36a314 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 0b2a44594f22b49b42d77818981bb0d4ad68e11c +Subproject commit 2f6b36a3144229bedda773737ede1c0723388171 From 7aa43411e052d8606f309624af0f8e7f5c6a7d95 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 14 Sep 2026 18:14:46 +0200 Subject: [PATCH 17/30] Reduce parallel JVMs spawned for tests (#7701) * Set `maxParallelForks = 1` for most tests, except for screenshot ones It turns out the Robolectric setup penalty compounds the more parallel JVMs you spawn. Using 1 JVM vs 8 was up to 2.5x faster in local tests. * Split the roborazzi tests tasks config and CI step --- .github/workflows/tests.yml | 3 +++ build.gradle.kts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bbf8fdf811..4bb11596fa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,6 +78,9 @@ jobs: - name: ⚙️ Run screenshot tests run: ./gradlew :tests:uitests:verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES + - name: ⚙️ Run Compound screenshot tests + run: ./gradlew :libraries:compound:verifyRoborazziDebug $CI_GRADLE_ARG_PROPERTIES + - name: ⚙️ Generate kover coverage report run: ./gradlew :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll -x testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES diff --git a/build.gradle.kts b/build.gradle.kts index 876383e325..725033a6ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -153,7 +153,10 @@ allprojects { maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) val isScreenshotTest = project.gradle.startParameter.taskNames.any { it.contains("paparazzi", ignoreCase = true) } + val isRoborazziTest = project.gradle.startParameter.taskNames.any { it.contains("roborazzi", ignoreCase = true) } if (isScreenshotTest) { + // Paparazzi tests benefit from parallelisation, so we can use half the available cores to run them in parallel. + maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) // Increase heap size for screenshot tests maxHeapSize = "2g" // Record all the languages? @@ -165,9 +168,22 @@ allprojects { exclude("translations/*.class") } } else { + // Robolectric and Compose pay a 5 to 30 seconds bootstrap cost per test JVM (instrumenting + // android-all, then warming up the Compose runtime). That cost is paid once per JVM and then + // amortised over every test class the JVM runs, so splitting a module across several forks + // re-pays it for each fork instead of saving time. Keep a single fork per module and let + // Gradle parallelise by running many modules' test tasks concurrently instead. + maxParallelForks = 1 + // Disable screenshot tests by default exclude("ui/*.class") exclude("translations/*.class") + if (isRoborazziTest.not()) { + // Roborazzi screenshot tests (:libraries:compound) live in a `screenshot` package, which + // the two patterns above do not match, so they used to run on every plain unit test run. + // They are verified by the dedicated `verifyRoborazziDebug` task instead. + exclude("**/screenshot/**") + } } } } From 5b0d7bf5a78106325704f6ee31396f975de3dad4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 14 Sep 2026 18:30:29 +0200 Subject: [PATCH 18/30] Log errors and fix quality issues --- enterprise | 2 +- .../login/impl/screens/onboarding/OnBoardingPresenter.kt | 4 +++- .../libraries/matrix/api/accountprovider/AccountProvider.kt | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/enterprise b/enterprise index 2f6b36a314..e4f10a779c 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 2f6b36a3144229bedda773737ede1c0723388171 +Subproject commit e4f10a779c1c79feebc96477ad65b576b5d96eda diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index c108aa1de0..84db4283b4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.accountprovider.AccountProvider import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.ui.utils.MultipleTapToUnlock import kotlinx.coroutines.launch +import timber.log.Timber @AssistedInject class OnBoardingPresenter( @@ -83,7 +84,8 @@ class OnBoardingPresenter( try { defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(it) true - } catch (_: Exception) { + } catch (e: Exception) { + Timber.e(e, "Failed to check account provider from link, assuming not allowed") false } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt index 828676285a..9eb6912d26 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt @@ -31,6 +31,9 @@ sealed interface AccountProvider { }.sanitized() } + /** + * Return the server name, to be used in the UI. + */ fun friendlyServerName(): String { return when (this) { is Generic -> serverName From dc466f1f5cc5eada7df2c6b841acc60973e6703f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 05:09:14 +0000 Subject: [PATCH 19/30] Update roborazzi to v1.74.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2eea188edd..2b9a7392c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ accompanist = "0.37.3" # Test test_core = "1.7.0" -roborazzi = "1.73.0" +roborazzi = "1.74.0" # Jetbrain datetime = "0.8.0" From c92bf4bc4360bcd59755d160ebae0542bf60424b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Sep 2026 09:11:40 +0200 Subject: [PATCH 20/30] Sealed interface must be immutable when uses in Composable function parameters. --- .../libraries/matrix/api/accountprovider/AccountProvider.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt index 9eb6912d26..c2d103b3f2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/accountprovider/AccountProvider.kt @@ -7,6 +7,9 @@ package io.element.android.libraries.matrix.api.accountprovider +import androidx.compose.runtime.Immutable + +@Immutable sealed interface AccountProvider { /** * An account provider that was input by the user. From fd315318a065d397d4edb63555899625b6901b55 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Sep 2026 09:15:38 +0200 Subject: [PATCH 21/30] Update ref to main branch. --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index e4f10a779c..6b422f4c8e 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit e4f10a779c1c79feebc96477ad65b576b5d96eda +Subproject commit 6b422f4c8e9e9e5557056e8a861fe3bfd9338f24 From 08eabd3ed52e25158799836fffc3edb3c8a42bfd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:36:56 +0000 Subject: [PATCH 22/30] Update metro to v1.4.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b9a7392c7..5be4b6d696 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ maplibre-compose = "0.15.0" dependencyAnalysis = "3.19.1" # DI -metro = "1.4.2" +metro = "1.4.3" # Auto service autoservice = "1.1.1" From 3e861cc64645a286a5455e0b47032b1a25b2f49d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Sep 2026 13:56:33 +0200 Subject: [PATCH 23/30] Do not report detekt LargeClass issues on test classes (#7713) detekt's default configuration excludes test source sets from `TooManyFunctions` and `StringLiteralDuplication`, but not from `LargeClass`, so large test classes were reported and had to carry a `@Suppress("LargeClass")` annotation. Add the same kind of exclusion to the `LargeClass` rule in `tools/detekt/detekt.yml` and remove the 24 now-redundant suppressions from test files. The two remaining suppressions are in production code, where the rule still applies. Co-authored-by: Claude Opus 5 (1M context) --- .../features/home/impl/roomlist/RoomListPresenterTest.kt | 1 - .../invitepeople/impl/DefaultInvitePeoplePresenterTest.kt | 1 - .../android/features/joinroom/impl/JoinRoomPresenterTest.kt | 1 - .../android/features/messages/impl/MessagesPresenterTest.kt | 1 - .../element/android/features/messages/impl/MessagesViewTest.kt | 1 - .../messages/impl/actionlist/ActionListPresenterTest.kt | 1 - .../impl/attachments/AttachmentsPreviewPresenterTest.kt | 1 - .../impl/messagecomposer/MessageComposerPresenterTest.kt | 1 - .../features/messages/impl/timeline/TimelinePresenterTest.kt | 1 - .../factories/event/TimelineItemContentMessageFactoryTest.kt | 1 - .../messages/impl/typing/TypingNotificationPresenterTest.kt | 1 - .../composer/DefaultVoiceMessageComposerPresenterTest.kt | 1 - .../impl/notifications/NotificationSettingsPresenterTest.kt | 1 - .../features/roomdetails/impl/RoomDetailsPresenterTest.kt | 1 - .../roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt | 1 - .../impl/root/SecurityAndPrivacyPresenterTest.kt | 1 - .../impl/DefaultPinnedMessagesBannerFormatterTest.kt | 1 - .../eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt | 1 - .../eventformatter/impl/RtcNotificationContentFormatterTest.kt | 1 - .../matrix/ui/messages/reply/InReplyToMetadataKtTest.kt | 1 - .../mediaviewer/impl/datasource/EventItemFactoryTest.kt | 1 - .../mediaviewer/impl/viewer/MediaViewerPresenterTest.kt | 1 - .../impl/notifications/DefaultNotifiableEventResolverTest.kt | 1 - .../android/libraries/push/impl/push/DefaultPushHandlerTest.kt | 1 - tools/detekt/detekt.yml | 2 ++ 25 files changed, 2 insertions(+), 24 deletions(-) diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt index 9bc8719a8b..5245a164d1 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt @@ -91,7 +91,6 @@ import org.junit.Rule import org.junit.Test import kotlin.time.Duration.Companion.seconds -@Suppress("LargeClass") class RoomListPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt index 39cfc3afba..30d6eaf844 100644 --- a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt +++ b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt @@ -59,7 +59,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -@Suppress("LargeClass") internal class DefaultInvitePeoplePresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 3c104e47ae..3edaf649e4 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -70,7 +70,6 @@ import org.junit.Rule import org.junit.Test import java.util.Optional -@Suppress("LargeClass") class JoinRoomPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 949443f1f7..78e9aba2d0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -125,7 +125,6 @@ import org.junit.Test import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds -@Suppress("LargeClass") class MessagesPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index f662da0eb0..677b2ac10b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -90,7 +90,6 @@ import org.junit.Test import org.robolectric.annotation.Config import kotlin.time.Duration.Companion.milliseconds -@Suppress("LargeClass") class MessagesViewTest : RobolectricTest() { @Test fun `clicking on back invoke expected callback`() = runAndroidComposeUiTest { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index e987a32480..66e57da1fa 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -54,7 +54,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -@Suppress("LargeClass") class ActionListPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 8370ec516f..c9f6aa9631 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -82,7 +82,6 @@ import org.junit.Test import java.io.File import kotlin.io.path.createTempFile -@Suppress("LargeClass") class AttachmentsPreviewPresenterTest : RobolectricTest() { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 6744cdebbb..3253e6088d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -127,7 +127,6 @@ import org.junit.Test import uniffi.wysiwyg_composer.MentionsState import java.io.File -@Suppress("LargeClass") class MessageComposerPresenterTest : RobolectricTest() { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 60f13f4ea8..aa4198d156 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -103,7 +103,6 @@ import java.util.Date import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -@Suppress("LargeClass") @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) class TimelinePresenterTest { @get:Rule diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 0c6c81a513..15b624aeff 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -83,7 +83,6 @@ import org.junit.Test import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes -@Suppress("LargeClass") class TimelineItemContentMessageFactoryTest : RobolectricTest() { @Test fun `test create OtherMessageType`() = runTest { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 62e596bc21..cdce1f2483 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -@Suppress("LargeClass") class TypingNotificationPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt index fb44bb4e80..baedfa33b6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt @@ -63,7 +63,6 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds -@Suppress("LargeClass") class DefaultVoiceMessageComposerPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt index 6a201fe3fa..8b32ef993c 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt @@ -45,7 +45,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.time.Duration.Companion.milliseconds -@Suppress("LargeClass") class NotificationSettingsPresenterTest { @Test fun `present - ensures initial state is correct`() = runTest { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt index 51a6f97ab0..8d6ef8d518 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt @@ -68,7 +68,6 @@ import org.junit.Rule import org.junit.Test import kotlin.time.Duration.Companion.milliseconds -@Suppress("LargeClass") @ExperimentalCoroutinesApi class RoomDetailsPresenterTest { @get:Rule diff --git a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt index 656cc92265..5d46f26230 100644 --- a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt @@ -50,7 +50,6 @@ import org.junit.Rule import org.junit.Test import java.io.File -@Suppress("LargeClass") @ExperimentalCoroutinesApi class RoomDetailsEditPresenterTest { @get:Rule diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt index 0b5133fe6a..8cdba871be 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt @@ -43,7 +43,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Test -@Suppress("LargeClass") class SecurityAndPrivacyPresenterTest { @Test fun `present - initial states`() = runTest { diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt index 38517dab33..981f53ebfe 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt @@ -57,7 +57,6 @@ import org.junit.Test import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config -@Suppress("LargeClass") class DefaultPinnedMessagesBannerFormatterTest : RobolectricTest() { private lateinit var context: Context private lateinit var fakeMatrixClient: FakeMatrixClient diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt index c293b67975..8b92d881a9 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt @@ -58,7 +58,6 @@ import org.junit.Test import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config -@Suppress("LargeClass") class DefaultRoomLatestEventFormatterTest : RobolectricTest() { private lateinit var context: Context private lateinit var fakeMatrixClient: FakeMatrixClient diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt index 9a79f78413..207de5bf6b 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt @@ -21,7 +21,6 @@ import org.junit.Test import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config -@Suppress("LargeClass") class RtcNotificationContentFormatterTest : RobolectricTest() { private lateinit var context: Context private lateinit var fakeMatrixClient: FakeMatrixClient diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt index 64e647ab19..46a012baff 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt @@ -54,7 +54,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.time.Duration.Companion.minutes -@Suppress("LargeClass") class InReplyToMetadataKtTest : RobolectricTest() { @Test fun `any message content`() = runTest { diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt index 4f02637a62..3003e5d46e 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt @@ -62,7 +62,6 @@ import kotlinx.collections.immutable.toImmutableList import org.junit.Test import kotlin.time.Duration.Companion.seconds -@Suppress("LargeClass") class EventItemFactoryTest { @Test fun `create check all null cases`() { diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt index be577362bd..ed710df50b 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt @@ -64,7 +64,6 @@ private val TESTED_MEDIA_INFO = anApkMediaInfo( senderId = A_USER_ID, ) -@Suppress("LargeClass") class MediaViewerPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt index 5918329c40..d25dd23d9d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt @@ -69,7 +69,6 @@ import org.junit.Test import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config -@Suppress("LargeClass") class DefaultNotifiableEventResolverTest : RobolectricTest() { @Test fun `resolve event no session`() = runTest { diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt index f0dee4446c..68fc55909b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt @@ -52,7 +52,6 @@ import kotlin.time.Duration.Companion.milliseconds private const val A_PUSHER_INFO = "info" -@Suppress("LargeClass") class DefaultPushHandlerTest { @Test fun `check handleInvalid behavior`() = runTest { diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index 147eca01d0..9c9bcd248b 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -175,6 +175,8 @@ complexity: active: true LargeClass: active: true + # Do not report large test classes. + excludes: [ '**/test/**', '**/androidTest/**' ] naming: VariableNaming: From 344b67f8e867c4d4034f765b3799b11df01a69e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:18:31 +0000 Subject: [PATCH 24/30] Update dependency net.zetetic:sqlcipher-android to v4.19.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b9a7392c7..686edb4c32 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -205,7 +205,7 @@ matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } -sqlcipher = "net.zetetic:sqlcipher-android:4.18.0" +sqlcipher = "net.zetetic:sqlcipher-android:4.19.0" sqlite = "androidx.sqlite:sqlite-ktx:2.7.0" unifiedpush = "org.unifiedpush.android:connector:3.3.5" vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" From 35af1fca485bacdd3896b5f236c1a6fe018513a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 08:38:38 +0200 Subject: [PATCH 25/30] Update dependency io.element.android:element-call-embedded to v0.26.0 (#7715) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b9a7392c7..62794efded 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -238,7 +238,7 @@ sigpwned_emoji4j = "com.sigpwned:emoji4j-core:16.0.0" metro_runtime = { module = "dev.zacsweers.metro:runtime", version.ref = "metro" } # Element Call -element_call_embedded = "io.element.android:element-call-embedded:0.25.0" +element_call_embedded = "io.element.android:element-call-embedded:0.26.0" # Auto services google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } From c3f128b6f04c94ed73ca9d33e7ec73505f7042df Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 16 Sep 2026 11:27:44 +0200 Subject: [PATCH 26/30] Use patched Konsist version (forked, 0.18.0) (#7719) * Use a patched fork of Konsist The patch contains code to ignore hidden folders in the root project: by default, Konsist scans *every single file* in the root directory except for a few exceptions, and hidden folders were not a part of that. That means `.git`, `.github`, etc. would be scanned to for no good reason and even worse, hidden folders used for worktrees would get scanned too, so Konsist would end up scanning the same project with small variations several times and OOMing / taking ages to finish. * Make `Immutable/Stable` annotation check more performant. Also, fix case that was previously not found --- .../android/features/joinroom/impl/JoinRoomState.kt | 1 + gradle/libs.versions.toml | 2 +- settings.gradle.kts | 2 ++ .../android/tests/konsist/KonsistArchitectureTest.kt | 9 ++------- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index 25f7765a7c..a1ab73486d 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -107,6 +107,7 @@ sealed interface LoadedDetails { ) : LoadedDetails } +@Immutable sealed interface JoinAuthorisationStatus { data object None : JoinAuthorisationStatus data class IsInvited(val inviteData: InviteData, val inviteSender: InviteSender?) : JoinAuthorisationStatus diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 62794efded..f609b5e26e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -168,7 +168,7 @@ test_arch_core = "androidx.arch.core:core-testing:2.2.0" test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.7.0" test_mockk = "io.mockk:mockk:1.14.11" -test_konsist = "com.lemonappdev:konsist:0.17.3" +test_konsist = "com.github.jmartinesp:konsist:0.18.0" test_turbine = "app.cash.turbine:turbine:1.2.1" test_truth = "com.google.truth:truth:1.4.5" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.22" diff --git a/settings.gradle.kts b/settings.gradle.kts index 715866b5c4..8bdd4632c0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,8 @@ dependencyResolutionManagement { includeModule("com.github.matrix-org", "matrix-analytics-events") // Required transitively by androidx.media3:media3-exoplayer-midi for MIDI playback. includeModule("com.github.philburk", "jsyn") + // This is a fork of Konsist that skips hidden folders and files, which otherwise can cause performance issues. + includeModule("com.github.jmartinesp", "konsist") } } // Check for official Android-related packages only in Google's maven repo diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt index 26f9ef50a3..23f36f014b 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt @@ -76,6 +76,7 @@ class KonsistArchitectureTest { .withSealedModifier() .withoutAnnotationOf(Immutable::class, Stable::class) .map { it.fullyQualifiedName } + .toSet() Konsist.scopeFromProject() .functions() .withAnnotationOf(Composable::class) @@ -85,13 +86,7 @@ class KonsistArchitectureTest { return@all if (type.startsWith("@") || type.contains("->") || type.startsWith("suspend")) { true } else { - val typePackage = param.type.sourceDeclaration?.let { declaration -> - declaration.asTypeParameterDeclaration()?.packagee - ?: declaration.asExternalDeclaration()?.packagee - ?: declaration.asClassOrInterfaceDeclaration()?.packagee - ?: declaration.asKotlinTypeDeclaration()?.packagee - ?: declaration.asObjectDeclaration()?.packagee - }?.name + val typePackage = param.type.packagee?.name if (typePackage == null) { false } else { From ca753ee6002e89a5563372143f905aada3c29fae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 12:54:12 +0000 Subject: [PATCH 27/30] Update actions/setup-java action to v6.0.1 --- .github/workflows/build.yml | 2 +- .github/workflows/build_enterprise.yml | 2 +- .github/workflows/generate_github_pages.yml | 2 +- .github/workflows/gradle-wrapper-update.yml | 2 +- .github/workflows/maestro-local.yml | 2 +- .github/workflows/nightly.yml | 2 +- .github/workflows/nightlyReports.yml | 4 ++-- .github/workflows/quality.yml | 12 ++++++------ .github/workflows/recordScreenshots.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/sonar.yml | 2 +- .github/workflows/sync-localazy.yml | 2 +- .github/workflows/tests.yml | 2 +- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bc1f10978..aa9be929ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,7 +51,7 @@ jobs: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index 00d5bb7fb5..b1d7204168 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -56,7 +56,7 @@ jobs: - name: Clone submodules run: git submodule update --init --recursive - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/generate_github_pages.yml b/.github/workflows/generate_github_pages.yml index 3c9b6f9dd8..763936e3d6 100644 --- a/.github/workflows/generate_github_pages.yml +++ b/.github/workflows/generate_github_pages.yml @@ -19,7 +19,7 @@ jobs: - name: ⏬ Checkout with LFS uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5 - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/gradle-wrapper-update.yml b/.github/workflows/gradle-wrapper-update.yml index a122135f37..777e9f1173 100644 --- a/.github/workflows/gradle-wrapper-update.yml +++ b/.github/workflows/gradle-wrapper-update.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 name: Use JDK 21 with: distribution: 'temurin' # See 'Supported distributions' for available options diff --git a/.github/workflows/maestro-local.yml b/.github/workflows/maestro-local.yml index c06dc55fa0..1195c7b53d 100644 --- a/.github/workflows/maestro-local.yml +++ b/.github/workflows/maestro-local.yml @@ -47,7 +47,7 @@ jobs: # https://github.com/actions/checkout/issues/881 ref: ${{ github.ref }} persist-credentials: false - - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 name: Use JDK 21 with: distribution: 'temurin' # See 'Supported distributions' for available options diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 250f736f65..25e7d174e0 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -36,7 +36,7 @@ jobs: with: persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index c4e70e84ec..fe38c0138f 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -37,7 +37,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5 - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -80,7 +80,7 @@ jobs: with: persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 5f0c2b1b3a..1f6289a8ea 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -69,7 +69,7 @@ jobs: with: persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -108,7 +108,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} run: git submodule update --init --recursive - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -149,7 +149,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} run: git submodule update --init --recursive - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -183,7 +183,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} run: git submodule update --init --recursive - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -228,7 +228,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} run: git submodule update --init --recursive - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -269,7 +269,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} run: git submodule update --init --recursive - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index b445301596..efac08a99f 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -54,7 +54,7 @@ jobs: with: persist-credentials: false - name: ☕️ Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f761e00438..8f264aa6de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: with: persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' @@ -88,7 +88,7 @@ jobs: with: persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 93a5e371b6..4febf272a9 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -45,7 +45,7 @@ jobs: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml index 7445bcc45b..9065e6d4e9 100644 --- a/.github/workflows/sync-localazy.yml +++ b/.github/workflows/sync-localazy.yml @@ -20,7 +20,7 @@ jobs: with: persist-credentials: false - name: Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4bb11596fa..e0ce28d5ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,7 +63,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} run: git submodule update --init --recursive - name: ☕️ Use JDK 21 - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' From 97d9707d6a5f77527b992ecb73038c5e1006bcf1 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Thu, 17 Sep 2026 16:13:06 +0200 Subject: [PATCH 28/30] Make NotificationResultProcessor.emit wait until the batch is processed (#7640) Signed-off-by: Johannes Marbach --- .../NotificationResultProcessor.kt | 55 +++++++++++++++---- .../DefaultNotificationResultProcessorTest.kt | 42 ++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt index 88ba6c5ff9..e1b6687e14 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt @@ -12,6 +12,7 @@ import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn import io.element.android.features.call.api.CallData import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -31,18 +32,25 @@ import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationSto import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived import io.element.android.libraries.push.impl.push.OnRedactedEventReceived import io.element.android.libraries.pushstore.api.UserPushStoreFactory +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.selects.select import timber.log.Timber private const val TAG = "NotifResultProcessor" interface NotificationResultProcessor { + /** + * Queue a batch of resolved push events for processing and suspend until it has been processed, so that the caller can keep the process + * alive (e.g. through a foreground service) until any resulting notification or ringing call has been set up. + */ suspend fun emit(results: Map>) fun start() fun stop() @@ -61,23 +69,50 @@ class DefaultNotificationResultProcessor( private val notificationChannels: NotificationChannels, @AppCoroutineScope private val coroutineScope: CoroutineScope, ) : NotificationResultProcessor { - private val resultFlow = MutableSharedFlow>>(extraBufferCapacity = Int.MAX_VALUE) + private data class PendingResults( + val results: Map>, + val processed: CompletableDeferred = CompletableDeferred(), + ) + + // A channel rather than a shared flow: it keeps the results until the collector takes them, so nothing is dropped if the collector isn't + // running yet (e.g. the worker runs in a process where no push has been received, after a WorkManager retry) or hasn't subscribed yet. + private val pendingResults = Channel(Channel.UNLIMITED) private var processJob: Job? = null override suspend fun emit(results: Map>) { - resultFlow.emit(results) + val collector = ensureStarted() + val pending = PendingResults(results) + pendingResults.send(pending) + // Suspend until the batch has been processed, or give up if the collector was stopped in the meantime. + select { + pending.processed.onAwait {} + collector.onJoin { Timber.tag(TAG).w("Processing stopped before the results were processed") } + } } override fun start() { - if (processJob?.isActive == true) { - Timber.tag(TAG).w("Is already processing, not starting again") - return - } - processJob = resultFlow - .onEach(::processResults) - .launchIn(coroutineScope) + ensureStarted() } + @Synchronized + private fun ensureStarted(): Job { + processJob?.takeIf { it.isActive }?.let { return it } + return pendingResults + .receiveAsFlow() + .onEach { pending -> + try { + // Don't let a failing batch cancel the collector, or subsequent batches would never be processed. + runCatchingExceptions { processResults(pending.results) } + .onFailure { Timber.tag(TAG).e(it, "Failed to process notification results") } + } finally { + pending.processed.complete(Unit) + } + } + .launchIn(coroutineScope) + .also { processJob = it } + } + + @Synchronized override fun stop() { if (processJob?.isActive != true) { Timber.tag(TAG).w("Is not processing, not stopping") diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt index 21a2c6cb91..a27e1cacc4 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt @@ -303,6 +303,48 @@ class DefaultNotificationResultProcessorTest { ) } + @Test + fun `emit starts the processing if needed and only returns once the results have been processed`() = runTest { + val onNotifiableEventsReceived = lambdaRecorder, Unit> {} + val processor = createDefaultNotificationResultProcessor( + pushHistoryService = FakePushHistoryService(onPushReceivedResult = { _, _, _, _, _, _, _ -> }), + onNotifiableEventsReceived = onNotifiableEventsReceived, + ) + + // No call to start(): the worker may run in a process where no push has been received yet + processor.emit(mapOf(aPushRequest() to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent())))) + + // No runCurrent(): the results must already have been processed when emit returns + onNotifiableEventsReceived.assertions().isCalledOnce() + + processor.stop() + } + + @Test + fun `a failing batch does not prevent later batches from being processed`() = runTest { + var pushHistoryCalls = 0 + val pushHistoryService = FakePushHistoryService( + onPushReceivedResult = { _, _, _, _, _, _, _ -> + pushHistoryCalls++ + if (pushHistoryCalls == 1) error("boom") + }, + ) + val onNotifiableEventsReceived = lambdaRecorder, Unit> {} + val processor = createDefaultNotificationResultProcessor( + pushHistoryService = pushHistoryService, + onNotifiableEventsReceived = onNotifiableEventsReceived, + ) + + runningProcessor(processor) { + // Processing the first batch throws, but emit must still return and the collector must survive + emit(mapOf(aPushRequest() to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent())))) + emit(mapOf(aPushRequest() to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent())))) + } + + // Only the second batch made it to the notification drawer + onNotifiableEventsReceived.assertions().isCalledOnce() + } + private suspend fun TestScope.runningProcessor(processor: NotificationResultProcessor, block: suspend NotificationResultProcessor.() -> Unit) { processor.start() From c0bb68c872476a0a81027920418dc9cf93d58a43 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 17 Sep 2026 19:08:32 +0200 Subject: [PATCH 29/30] New timeline event renderer: add new modules (#7721) * Added HTML parser to abstract tree nodes * Add HTML renderer using `HtmlMessageContent` * Add custom rendering for inline code nodes besides the monospace font * Support `` and `` too * Add `ContentAvoidingLayoutData` support * Improve the last block node behaviour and its integration with `ContentAvoidingLayout` * Move `ContentAvoidingLayout` to `:libraries:ui-common` so it can be shared * Add missing `:libraries:htmlrenderer:impl` entry in `DependencyHandlerScope.allLibrariesImpl` * Update screenshots --------- Co-authored-by: ElementBot --- features/messages/impl/build.gradle.kts | 1 + .../pinned/list/PinnedMessagesListView.kt | 2 +- .../components/TimelineItemEventRow.kt | 4 +- .../TimelineItemGroupedEventsRow.kt | 2 +- .../timeline/components/TimelineItemRow.kt | 2 +- .../event/TimelineItemAttachmentView.kt | 4 +- .../components/event/TimelineItemAudioView.kt | 2 +- .../event/TimelineItemEncryptedView.kt | 2 +- .../event/TimelineItemEventContentView.kt | 4 +- .../components/event/TimelineItemFileView.kt | 2 +- .../event/TimelineItemInformativeView.kt | 2 +- .../event/TimelineItemRedactedView.kt | 2 +- .../components/event/TimelineItemTextView.kt | 4 +- .../event/TimelineItemUnknownView.kt | 2 +- .../components/event/TimelineItemVoiceView.kt | 2 +- libraries/htmlrenderer/api/build.gradle.kts | 19 + .../htmlrenderer/api/HtmlMessageParser.kt | 40 ++ .../htmlrenderer/api/MentionNodeContent.kt | 42 ++ .../libraries/htmlrenderer/api/MessageNode.kt | 95 +++++ libraries/htmlrenderer/impl/build.gradle.kts | 36 ++ .../impl/DefaultHtmlMessageParser.kt | 237 ++++++++++++ ...ontentAvoidingHtmlMessageContentPreview.kt | 70 ++++ .../impl/renderer/HtmlMessageContent.kt | 362 ++++++++++++++++++ .../renderer/HtmlMessageContentPreview.kt | 212 ++++++++++ .../impl/DefaultHtmlMessageParserTest.kt | 204 ++++++++++ libraries/htmlrenderer/test/build.gradle.kts | 18 + .../test/FakeHtmlMessageParser.kt | 19 + libraries/ui-common/build.gradle.kts | 1 + .../common}/layout/ContentAvoidingLayout.kt | 19 +- .../kotlin/extension/DependencyHandleScope.kt | 1 + .../tests/konsist/KonsistClassNameTest.kt | 1 + ...eeditor_AttachmentImageEditorView_2_en.png | 4 +- ...eeditor_AttachmentImageEditorView_5_en.png | 4 +- ...essagesViewWithIdentityChange_Day_0_en.png | 4 +- ...essagesViewWithIdentityChange_Day_1_en.png | 4 +- ...essagesViewWithIdentityChange_Day_2_en.png | 4 +- ...sagesViewWithIdentityChange_Night_0_en.png | 4 +- ...sagesViewWithIdentityChange_Night_1_en.png | 4 +- ...sagesViewWithIdentityChange_Night_2_en.png | 4 +- ...veVerifiedUserSendFailureView_Day_1_en.png | 4 +- ...veVerifiedUserSendFailureView_Day_2_en.png | 4 +- ...VerifiedUserSendFailureView_Night_1_en.png | 4 +- ...VerifiedUserSendFailureView_Night_2_en.png | 4 +- ...s.event_ATimelineItemEventRow_Day_0_en.png | 4 +- ...event_ATimelineItemEventRow_Night_0_en.png | 4 +- ...lineItemEventRowDisambiguated_Day_0_en.png | 4 +- ...neItemEventRowDisambiguated_Night_0_en.png | 4 +- ...mEventRowWithReplyInformative_Day_0_en.png | 4 +- ...mEventRowWithReplyInformative_Day_1_en.png | 4 +- ...ventRowWithReplyInformative_Night_0_en.png | 4 +- ...ventRowWithReplyInformative_Night_1_en.png | 4 +- ...ineItemEventRowWithReplyOther_Day_0_en.png | 4 +- ...ineItemEventRowWithReplyOther_Day_1_en.png | 4 +- ...eItemEventRowWithReplyOther_Night_0_en.png | 4 +- ...eItemEventRowWithReplyOther_Night_1_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_0_en.png | 4 +- ...imelineItemEventRowWithReply_Day_10_en.png | 4 +- ...imelineItemEventRowWithReply_Day_11_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_1_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_2_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_3_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_4_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_5_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_6_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_7_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_8_en.png | 4 +- ...TimelineItemEventRowWithReply_Day_9_en.png | 4 +- ...melineItemEventRowWithReply_Night_0_en.png | 4 +- ...elineItemEventRowWithReply_Night_10_en.png | 4 +- ...elineItemEventRowWithReply_Night_11_en.png | 4 +- ...melineItemEventRowWithReply_Night_1_en.png | 4 +- ...melineItemEventRowWithReply_Night_2_en.png | 4 +- ...melineItemEventRowWithReply_Night_3_en.png | 4 +- ...melineItemEventRowWithReply_Night_4_en.png | 4 +- ...melineItemEventRowWithReply_Night_5_en.png | 4 +- ...melineItemEventRowWithReply_Night_6_en.png | 4 +- ...melineItemEventRowWithReply_Night_7_en.png | 4 +- ...melineItemEventRowWithReply_Night_8_en.png | 4 +- ...melineItemEventRowWithReply_Night_9_en.png | 4 +- ...s.messages.impl_MessagesView_Day_12_en.png | 4 +- ....messages.impl_MessagesView_Night_0_en.png | 4 +- ...messages.impl_MessagesView_Night_10_en.png | 4 +- ...messages.impl_MessagesView_Night_12_en.png | 4 +- ....messages.impl_MessagesView_Night_2_en.png | 4 +- ....messages.impl_MessagesView_Night_4_en.png | 4 +- ....messages.impl_MessagesView_Night_6_en.png | 4 +- ....messages.impl_MessagesView_Night_8_en.png | 4 +- ...blockedusers_BlockedUsersView_Day_4_en.png | 4 +- ...blockedusers_BlockedUsersView_Day_5_en.png | 4 +- ...ockedusers_BlockedUsersView_Night_4_en.png | 4 +- ...ockedusers_BlockedUsersView_Night_5_en.png | 4 +- ...mpl.userstatus_UserStatusView_Day_5_en.png | 4 +- ...mpl.userstatus_UserStatusView_Day_6_en.png | 4 +- ...l.userstatus_UserStatusView_Night_5_en.png | 4 +- ...l.userstatus_UserStatusView_Night_6_en.png | 4 +- ...impl_RoomMemberModerationView_Day_5_en.png | 4 +- ...impl_RoomMemberModerationView_Day_7_en.png | 4 +- ...pl_RoomMemberModerationView_Night_5_en.png | 4 +- ...pl_RoomMemberModerationView_Night_7_en.png | 4 +- ...orizontalFloatingToolbarNoFab_Day_0_en.png | 4 +- ...izontalFloatingToolbarNoFab_Night_0_en.png | 4 +- ...nts_HorizontalFloatingToolbar_Day_0_en.png | 4 +- ...s_HorizontalFloatingToolbar_Night_0_en.png | 4 +- ...l.renderer_HtmlMessageContent_Day_0_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_1_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_2_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_3_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_4_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_5_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_6_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_7_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_8_en.png | 3 + ...l.renderer_HtmlMessageContent_Day_9_en.png | 3 + ...renderer_HtmlMessageContent_Night_0_en.png | 3 + ...renderer_HtmlMessageContent_Night_1_en.png | 3 + ...renderer_HtmlMessageContent_Night_2_en.png | 3 + ...renderer_HtmlMessageContent_Night_3_en.png | 3 + ...renderer_HtmlMessageContent_Night_4_en.png | 3 + ...renderer_HtmlMessageContent_Night_5_en.png | 3 + ...renderer_HtmlMessageContent_Night_6_en.png | 3 + ...renderer_HtmlMessageContent_Night_7_en.png | 3 + ...renderer_HtmlMessageContent_Night_8_en.png | 3 + ...renderer_HtmlMessageContent_Night_9_en.png | 3 + 123 files changed, 1592 insertions(+), 169 deletions(-) create mode 100644 libraries/htmlrenderer/api/build.gradle.kts create mode 100644 libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/HtmlMessageParser.kt create mode 100644 libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MentionNodeContent.kt create mode 100644 libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MessageNode.kt create mode 100644 libraries/htmlrenderer/impl/build.gradle.kts create mode 100644 libraries/htmlrenderer/impl/src/main/kotlin/io/element/android/libraries/htmlrenderer/impl/DefaultHtmlMessageParser.kt create mode 100644 libraries/htmlrenderer/impl/src/main/kotlin/io/element/android/libraries/htmlrenderer/impl/renderer/ContentAvoidingHtmlMessageContentPreview.kt create mode 100644 libraries/htmlrenderer/impl/src/main/kotlin/io/element/android/libraries/htmlrenderer/impl/renderer/HtmlMessageContent.kt create mode 100644 libraries/htmlrenderer/impl/src/main/kotlin/io/element/android/libraries/htmlrenderer/impl/renderer/HtmlMessageContentPreview.kt create mode 100644 libraries/htmlrenderer/impl/src/test/kotlin/io/element/android/libraries/htmlrenderer/impl/DefaultHtmlMessageParserTest.kt create mode 100644 libraries/htmlrenderer/test/build.gradle.kts create mode 100644 libraries/htmlrenderer/test/src/main/kotlin/io/element/android/libraries/htmlrenderer/test/FakeHtmlMessageParser.kt rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components => libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common}/layout/ContentAvoidingLayout.kt (94%) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Day_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.htmlrenderer.impl.renderer_HtmlMessageContent_Night_9_en.png diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 0b51a2b80d..58c71a96f6 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { implementation(projects.libraries.voicerecorder.api) implementation(projects.libraries.mediaplayer.api) implementation(projects.libraries.push.api) + implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiUtils) implementation(projects.libraries.testtags) implementation(projects.libraries.wellknown.api) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index 5daec3e275..298a783e97 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -36,7 +36,6 @@ import io.element.android.features.messages.impl.timeline.components.TimelineIte import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -58,6 +57,7 @@ import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 99e72f4973..97ec71173a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -72,8 +72,6 @@ import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -139,6 +137,8 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderName import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.a11y.isTalkbackActive diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 5b620a7dc3..0128e80b79 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -23,7 +23,6 @@ import io.element.android.features.messages.impl.timeline.aRedactedMessagesGroup import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.groups.isRedactedMessagesGroup @@ -35,6 +34,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.wysiwyg.link.Link @Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index fb9659ff70..aa66c262b5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -29,7 +29,6 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent @@ -46,6 +45,7 @@ import io.element.android.libraries.designsystem.text.toPx import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.a11y.isTalkbackActive import io.element.android.wysiwyg.link.Link diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt index 38d575718f..586ff6cfe5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt @@ -22,12 +22,12 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData /** * package-private, you should only use TimelineItemFileView and TimelineItemAudioView. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt index d47ca552b8..a6c6afcbc9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt @@ -12,11 +12,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentPreviewParam import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData @Composable fun TimelineItemAudioView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt index 6b9022d3cf..38920af287 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContentPreviewParam import io.element.android.libraries.designsystem.icons.CompoundDrawables @@ -20,6 +19,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt index 4b7ab9372d..839df1d320 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt @@ -24,8 +24,6 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.timeline.TimelineEvent -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.rememberPresenter import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAttachmentsContent @@ -60,6 +58,8 @@ import io.element.android.libraries.matrix.ui.media.contentvalidation.NotFoundCo import io.element.android.libraries.matrix.ui.media.contentvalidation.collectOverallState import io.element.android.libraries.matrix.ui.media.contentvalidation.rememberEventContentValidationState import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.voiceplayer.api.VoiceMessageState import io.element.android.wysiwyg.compose.EditorStyledText import io.element.android.wysiwyg.link.Link diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt index 77df9ba923..bc3fe91eca 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt @@ -13,11 +13,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentPreviewParam import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings /** diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt index 41a950ee5e..34f22d9d48 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt @@ -22,12 +22,12 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData @Composable fun TimelineItemInformativeView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt index e32de7f4ae..70ac84eb31 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt @@ -11,11 +11,11 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index 86b50f3015..bb8e30aca3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -23,8 +23,6 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.compound.theme.ElementTheme -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam @@ -36,6 +34,8 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.LocalUiTestMode import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.wysiwyg.compose.EditorStyledText import io.element.android.wysiwyg.link.Link diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt index fbbefca207..8f375ddb5b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt @@ -11,11 +11,11 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt index a05ac78f84..55bc5866d6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentPreviewParam import io.element.android.libraries.designsystem.atomic.atoms.PlaybackSpeedButton @@ -52,6 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue +import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.a11y.isTalkbackActive import io.element.android.libraries.voiceplayer.api.VoiceMessageEvent diff --git a/libraries/htmlrenderer/api/build.gradle.kts b/libraries/htmlrenderer/api/build.gradle.kts new file mode 100644 index 0000000000..a475be7eb0 --- /dev/null +++ b/libraries/htmlrenderer/api/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.libraries.htmlrenderer.api" +} + +dependencies { + api(projects.libraries.matrix.api) + api(libs.jsoup) +} diff --git a/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/HtmlMessageParser.kt b/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/HtmlMessageParser.kt new file mode 100644 index 0000000000..f0c3f67249 --- /dev/null +++ b/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/HtmlMessageParser.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.htmlrenderer.api + +import org.jsoup.nodes.Document + +/** + * Parses the HTML [Document] of a formatted message body into a tree of [MessageNode]s + * that a Compose renderer can turn into composables. + * + * The [Document] is expected to have already been sanitized (e.g. via + * `FormattedBody.toHtmlDocument`) so that only supported tags remain. + */ +interface HtmlMessageParser { + /** + * Converts [document] into a [DocumentNode]. Never throws: unknown or empty content + * yields a [DocumentNode] with no children. + */ + fun parse(document: Document): DocumentNode + + companion object { + /** + * Tag used for the string annotation attached to link (``) text ranges in a + * [ParagraphNode.text]. The annotation value is the link target URL. + */ + const val LINK_ANNOTATION_TAG = "url" + + /** + * Tag used for the string annotation attached to inline code (``) text ranges in a + * [ParagraphNode.text]. The annotation value is empty; only the range matters. A `` + * nested inside a `
` is part of the [CodeBlockNode] and is never annotated this way.
+         */
+        const val INLINE_CODE_ANNOTATION_TAG = "inline_code"
+    }
+}
diff --git a/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MentionNodeContent.kt b/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MentionNodeContent.kt
new file mode 100644
index 0000000000..4886dcef23
--- /dev/null
+++ b/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MentionNodeContent.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.libraries.htmlrenderer.api
+
+import androidx.compose.runtime.Immutable
+import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
+import io.element.android.libraries.matrix.api.core.UserId
+
+/**
+ * Describes a mention "pill" referenced from a [ParagraphNode].
+ *
+ * Each mention occupies an inline-content placeholder inside the paragraph's
+ * [ParagraphNode.text]; the renderer looks the id up in [ParagraphNode.inlineContent]
+ * and draws the corresponding pill.
+ */
+@Immutable
+sealed interface MentionNodeContent {
+    /** The text shown inside the pill (e.g. a display name, prefixed with `@` for users). */
+    val displayText: String
+
+    /** A mention of a specific user (``). */
+    data class User(
+        override val displayText: String,
+        val userId: UserId,
+    ) : MentionNodeContent
+
+    /** A mention of a room (``). */
+    data class Room(
+        override val displayText: String,
+        val roomIdOrAlias: RoomIdOrAlias,
+    ) : MentionNodeContent
+
+    /** An `@room` mention notifying everyone in the room. */
+    data class Everyone(
+        override val displayText: String,
+    ) : MentionNodeContent
+}
diff --git a/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MessageNode.kt b/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MessageNode.kt
new file mode 100644
index 0000000000..05be7fd8ea
--- /dev/null
+++ b/libraries/htmlrenderer/api/src/main/kotlin/io/element/android/libraries/htmlrenderer/api/MessageNode.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.libraries.htmlrenderer.api
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.AnnotatedString
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.ImmutableMap
+import kotlinx.collections.immutable.persistentMapOf
+
+/**
+ * A node in the parsed representation of a formatted (HTML) message body.
+ *
+ * The tree is produced by [HtmlMessageParser] and consumed by a Compose renderer.
+ * Block-level nodes ([BlockNode]) either contain other block nodes (nested lists, a
+ * quote containing paragraphs, ...) or terminate in a [ParagraphNode] / [CodeBlockNode]
+ * that carries the actual text.
+ */
+@Immutable
+sealed interface MessageNode
+
+/** A block-level node, laid out vertically by the renderer. */
+@Immutable
+sealed interface BlockNode : MessageNode
+
+/**
+ * The root of a parsed message: an ordered list of block-level children.
+ */
+@Immutable
+data class DocumentNode(
+    val children: ImmutableList,
+) : BlockNode
+
+/**
+ * A run of inline content (text with inline formatting) already flattened to an
+ * [AnnotatedString]. This is the only leaf that carries styled text.
+ *
+ * Inline formatting (``, ``, ``, ``, inline ``) is encoded as
+ * [androidx.compose.ui.text.SpanStyle] ranges, and links (``) as string annotations
+ * tagged [HtmlMessageParser.LINK_ANNOTATION_TAG].
+ *
+ * Mentions (pills) are encoded as inline-content placeholders inside [text] (see
+ * [androidx.compose.foundation.text.appendInlineContent]); [inlineContent] maps each
+ * placeholder id to the [MentionNodeContent] the renderer needs to draw the pill.
+ */
+@Immutable
+data class ParagraphNode(
+    val text: AnnotatedString,
+    val inlineContent: ImmutableMap = persistentMapOf(),
+) : BlockNode
+
+/**
+ * A preformatted code block (`
`). Rendered in a monospaced, horizontally
+ * scrollable container. The text is kept raw: no inline styling is applied inside it.
+ */
+@Immutable
+data class CodeBlockNode(
+    val code: String,
+) : BlockNode
+
+/**
+ * A block quote (`
`). Quotes can nest and can contain any block content. + */ +@Immutable +data class QuoteNode( + val children: ImmutableList, +) : BlockNode + +/** + * An ordered (`
    `) or unordered (`