From 0ab2ad1b8b97e7e332fff9b55736f5abd14ebd0d Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Nov 2024 16:33:34 +0100 Subject: [PATCH 001/203] room details : display the item to show request to join list --- .../roomdetails/impl/RoomDetailsFlowNode.kt | 4 ++++ .../roomdetails/impl/RoomDetailsNode.kt | 8 ++++++- .../roomdetails/impl/RoomDetailsPresenter.kt | 18 ++++++++++++++- .../roomdetails/impl/RoomDetailsState.kt | 2 ++ .../impl/RoomDetailsStateProvider.kt | 4 ++++ .../roomdetails/impl/RoomDetailsView.kt | 22 +++++++++++++++++++ .../room/powerlevels/MatrixRoomPowerLevels.kt | 7 ++++++ .../matrix/ui/room/MatrixRoomState.kt | 8 +++++++ 8 files changed, 71 insertions(+), 2 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 4030e30272..4d4b644bc1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -141,6 +141,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( backstack.push(NavTarget.PinnedMessagesList) } + override fun openKnockRequestsList() { + // TODO open the knock requests list screen + } + override fun onJoinCall() { val inputs = CallType.RoomCall( sessionId = room.sessionId, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index 19c0b4ffe4..e9ad095c76 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -47,6 +47,7 @@ class RoomDetailsNode @AssistedInject constructor( fun openPollHistory() fun openAdminSettings() fun openPinnedMessagesList() + fun openKnockRequestsList() fun onJoinCall() } @@ -111,6 +112,10 @@ class RoomDetailsNode @AssistedInject constructor( callbacks.forEach { it.openPinnedMessagesList() } } + private fun openKnockRequestsLists() { + callbacks.forEach { it.openKnockRequestsList() } + } + @Composable override fun View(modifier: Modifier) { val context = LocalContext.current @@ -140,7 +145,8 @@ class RoomDetailsNode @AssistedInject constructor( openPollHistory = ::openPollHistory, openAdminSettings = this::openAdminSettings, onJoinCallClick = ::onJoinCall, - onPinnedMessagesClick = ::openPinnedMessages + onPinnedMessagesClick = ::openPinnedMessages, + onKnockRequestsClick = ::openKnockRequestsLists ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 586790b618..dc6568a60f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.api.room.roomNotificationSettings +import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin @@ -69,7 +70,7 @@ class RoomDetailsPresenter @Inject constructor( val canShowNotificationSettings = remember { mutableStateOf(false) } val roomInfo by room.roomInfoFlow.collectAsState(initial = null) val isUserAdmin = room.isOwnUserAdmin() - + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomAvatar by remember { derivedStateOf { roomInfo?.avatarUrl ?: room.avatarUrl } } val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.displayName).trim() } } @@ -90,6 +91,7 @@ class RoomDetailsPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val canInvite by getCanInvite(membersState) + val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC) @@ -99,6 +101,8 @@ class RoomDetailsPresenter @Inject constructor( val roomType by getRoomType(dmMember, currentMember) val roomCallState = roomCallStatePresenter.present() + val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) + val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic @@ -109,6 +113,12 @@ class RoomDetailsPresenter @Inject constructor( } } + val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) + val knockRequestsCount by remember { mutableStateOf(null) } + val canShowKnockRequests by remember { + derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests } + } + val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState() fun handleEvents(event: RoomDetailsEvent) { @@ -153,10 +163,16 @@ class RoomDetailsPresenter @Inject constructor( heroes = roomInfo?.heroes.orEmpty().toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, pinnedMessagesCount = pinnedMessagesCount, + canShowKnockRequests = canShowKnockRequests, + knockRequestsCount = knockRequestsCount, eventSink = ::handleEvents, ) } + private fun getCanBan(membersState: MatrixRoomMembersState): Any { + TODO("Not yet implemented") + } + @Composable private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { dmMemberState?.let { roomMember -> diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index d43b0a813a..7f15c846f9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -41,6 +41,8 @@ data class RoomDetailsState( val heroes: ImmutableList, val canShowPinnedMessages: Boolean, val pinnedMessagesCount: Int?, + val canShowKnockRequests: Boolean, + val knockRequestsCount: Int?, val eventSink: (RoomDetailsEvent) -> Unit ) { val roomBadges = buildList { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index 49b9f73cb5..dcf5bc3054 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -102,6 +102,8 @@ fun aRoomDetailsState( heroes: List = emptyList(), canShowPinnedMessages: Boolean = true, pinnedMessagesCount: Int? = null, + canShowKnockRequests: Boolean = false, + knockRequestsCount: Int? = null, eventSink: (RoomDetailsEvent) -> Unit = {}, ) = RoomDetailsState( roomId = roomId, @@ -125,6 +127,8 @@ fun aRoomDetailsState( heroes = heroes.toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, pinnedMessagesCount = pinnedMessagesCount, + canShowKnockRequests = canShowKnockRequests, + knockRequestsCount = knockRequestsCount, eventSink = eventSink ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 7e89c3ef07..80f7beda56 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -104,6 +104,7 @@ fun RoomDetailsView( openAdminSettings: () -> Unit, onJoinCallClick: () -> Unit, onPinnedMessagesClick: () -> Unit, + onKnockRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -206,6 +207,12 @@ fun RoomDetailsView( memberCount = state.memberCount, openRoomMemberList = openRoomMemberList, ) + if (state.canShowKnockRequests) { + KnockRequestsItem( + knockRequestsCount = state.knockRequestsCount, + onKnockRequestsClick = onKnockRequestsClick + ) + } } } @@ -231,6 +238,20 @@ fun RoomDetailsView( } } +@Composable +private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: () -> Unit) { + ListItem( + headlineContent = { Text(stringResource(CommonStrings.screen_room_details_requests_to_join_title)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), + trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) { + null + } else { + ListItemContent.Text(knockRequestsCount.toString()) + }, + onClick = onKnockRequestsClick, + ) +} + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomDetailsTopBar( @@ -613,5 +634,6 @@ private fun ContentToPreview(state: RoomDetailsState) { openAdminSettings = {}, onJoinCallClick = {}, onPinnedMessagesClick = {}, + onKnockRequestsClick = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt index 682596a59d..ba81a5780c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt @@ -57,6 +57,13 @@ suspend fun MatrixRoom.canRedactOwn(): Result = canUserRedactOwn(sessio */ suspend fun MatrixRoom.canRedactOther(): Result = canUserRedactOther(sessionId) +/** + * Shortcut for checking if current user can handle knock requests. + */ +suspend fun MatrixRoom.canHandleKnockRequests(): Result = runCatching { + canInvite().getOrThrow() || canBan().getOrThrow() || canKick().getOrThrow() +} + /** * Shortcut for calling [MatrixRoom.canUserPinUnpin] with our own user. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt index 81ae3e6b89..59bd1fa773 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canBan +import io.element.android.libraries.matrix.api.room.powerlevels.canHandleKnockRequests import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canKick import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther @@ -86,6 +87,13 @@ fun MatrixRoom.canBanAsState(updateKey: Long): State { } } +@Composable +fun MatrixRoom.canHandleKnockRequestsAsState(updateKey: Long): State { + return produceState(initialValue = false, key1 = updateKey) { + value = canHandleKnockRequests().getOrElse { false } + } +} + @Composable fun MatrixRoom.userPowerLevelAsState(updateKey: Long): State { return produceState(initialValue = 0, key1 = updateKey) { From 795937b74c3a0d3d7bb084b049223c984c6a54f4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Nov 2024 12:25:23 +0100 Subject: [PATCH 002/203] knock requests : create feature module --- features/knockrequests/api/build.gradle.kts | 22 ++++++++++ .../api/list/KnockRequestsListEntryPoint.kt | 12 ++++++ features/knockrequests/impl/build.gradle.kts | 35 +++++++++++++++ .../DefaultKnockRequestsListEntryPoint.kt | 23 ++++++++++ .../impl/list/KnockRequestsListEvents.kt | 12 ++++++ .../impl/list/KnockRequestsListNode.kt | 36 ++++++++++++++++ .../impl/list/KnockRequestsListPresenter.kt | 29 +++++++++++++ .../impl/list/KnockRequestsListState.kt | 14 ++++++ .../list/KnockRequestsListStateProvider.kt | 22 ++++++++++ .../impl/list/KnockRequestsListView.kt | 43 +++++++++++++++++++ features/roomdetails/impl/build.gradle.kts | 1 + .../roomdetails/impl/RoomDetailsFlowNode.kt | 10 ++++- 12 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 features/knockrequests/api/build.gradle.kts create mode 100644 features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt create mode 100644 features/knockrequests/impl/build.gradle.kts create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts new file mode 100644 index 0000000000..e3d563f2e0 --- /dev/null +++ b/features/knockrequests/api/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +import extension.setupAnvil + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.knockrequests.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} + diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt new file mode 100644 index 0000000000..0215b5cde9 --- /dev/null +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.api.list + +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint + +interface KnockRequestsListEntryPoint : SimpleFeatureEntryPoint diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts new file mode 100644 index 0000000000..35d18b0c3a --- /dev/null +++ b/features/knockrequests/impl/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +import extension.setupAnvil + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.knockrequests.impl" +} + +setupAnvil() + +dependencies { + api(projects.features.knockrequests.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt new file mode 100644 index 0000000000..c685f1cf37 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultKnockRequestsListEntryPoint @Inject constructor() : KnockRequestsListEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt new file mode 100644 index 0000000000..f7f1755f32 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +sealed interface KnockRequestsListEvents { + data object AcceptAll : KnockRequestsListEvents +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt new file mode 100644 index 0000000000..953b5991db --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.RoomScope + +@ContributesNode(RoomScope::class) +class KnockRequestsListNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: KnockRequestsListPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + KnockRequestsListView( + state = state, + modifier = modifier + ) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt new file mode 100644 index 0000000000..b290a5b99f --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class KnockRequestsListPresenter @Inject constructor() : Presenter { + + @Composable + override fun present(): KnockRequestsListState { + + fun handleEvents(event: KnockRequestsListEvents) { + when (event) { + KnockRequestsListEvents.AcceptAll -> Unit + } + } + + return KnockRequestsListState( + eventSink = ::handleEvents + ) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt new file mode 100644 index 0000000000..8433c29a5a --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +// TODO add your ui models. Remove the eventSink if you don't have events. +// Do not use default value, so no member get forgotten in the presenters. +data class KnockRequestsListState( + val eventSink: (KnockRequestsListEvents) -> Unit +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt new file mode 100644 index 0000000000..4301ccdc32 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class KnockRequestsListStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aKnockRequestsListState(), + // Add other states here + ) +} + +fun aKnockRequestsListState() = KnockRequestsListState( + eventSink = {} +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt new file mode 100644 index 0000000000..790a970883 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun KnockRequestsListView( + state: KnockRequestsListState, + modifier: Modifier = Modifier, +) { + Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + "KnockRequestsList feature view", + color = MaterialTheme.colorScheme.primary, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun KnockRequestsListViewPreview( + @PreviewParameter(KnockRequestsListStateProvider::class) state: KnockRequestsListState +) = ElementPreview { + KnockRequestsListView( + state = state, + ) +} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 231161e583..bfb2cfde7a 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(projects.features.poll.api) implementation(projects.features.messages.api) implementation(projects.features.roomcall.api) + implementation(projects.features.knockrequests.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 4d4b644bc1..5aab0be9f8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -22,6 +22,7 @@ import im.vector.app.features.analytics.plan.Interaction import io.element.android.anvilannotations.ContributesNode import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.poll.api.history.PollHistoryEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint @@ -59,6 +60,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( private val room: MatrixRoom, private val analyticsService: AnalyticsService, private val messagesEntryPoint: MessagesEntryPoint, + private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), @@ -103,6 +105,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( @Parcelize data object PinnedMessagesList : NavTarget + + @Parcelize + data object KnockRequestsList : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -142,7 +147,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( } override fun openKnockRequestsList() { - // TODO open the knock requests list screen + backstack.push(NavTarget.KnockRequestsList) } override fun onJoinCall() { @@ -253,6 +258,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( .callback(callback) .build() } + NavTarget.KnockRequestsList -> { + knockRequestsListEntryPoint.createNode(this, buildContext) + } } } From bc09872baeb0cdbdedf3ddd617259697bb193d33 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Nov 2024 21:12:11 +0100 Subject: [PATCH 003/203] knock requests : start knock requests list view --- features/knockrequests/impl/build.gradle.kts | 1 + .../knockrequests/impl/KnockRequest.kt | 34 +++ .../impl/list/KnockRequestsListEvents.kt | 5 + .../impl/list/KnockRequestsListNode.kt | 1 + .../impl/list/KnockRequestsListPresenter.kt | 14 + .../impl/list/KnockRequestsListState.kt | 18 +- .../list/KnockRequestsListStateProvider.kt | 66 ++++- .../impl/list/KnockRequestsListView.kt | 260 +++++++++++++++++- .../components/avatar/AvatarSize.kt | 2 + 9 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts index 35d18b0c3a..83f7132320 100644 --- a/features/knockrequests/impl/build.gradle.kts +++ b/features/knockrequests/impl/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) + implementation(projects.libraries.uiStrings) implementation(projects.libraries.designsystem) testImplementation(libs.test.junit) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt new file mode 100644 index 0000000000..3a293dc565 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser + +data class KnockRequest( + val userId: UserId, + val displayName: String?, + val avatarUrl: String?, + val reason: String?, +) + +fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( + id = userId.value, + name = displayName, + url = avatarUrl, + size = size, +) + +fun KnockRequest.getBestName(): String { + return displayName?.takeIf { it.isNotEmpty() } ?: userId.value +} + + + diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt index f7f1755f32..d84e5209b9 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -7,6 +7,11 @@ package io.element.android.features.knockrequests.impl.list +import io.element.android.features.knockrequests.impl.KnockRequest + sealed interface KnockRequestsListEvents { + data class Accept(val knockRequest: KnockRequest) : KnockRequestsListEvents + data class Decline(val knockRequest: KnockRequest) : KnockRequestsListEvents data object AcceptAll : KnockRequestsListEvents + data object DismissCurrentAction : KnockRequestsListEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt index 953b5991db..0310f67d2e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -30,6 +30,7 @@ class KnockRequestsListNode @AssistedInject constructor( val state = presenter.present() KnockRequestsListView( state = state, + onBackClick = ::navigateUp, modifier = modifier ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index b290a5b99f..e7168e967b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -8,21 +8,35 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor() : Presenter { @Composable override fun present(): KnockRequestsListState { + val actions = remember { + mutableStateOf(KnockRequestsCurrentAction.None) + } fun handleEvents(event: KnockRequestsListEvents) { when (event) { KnockRequestsListEvents.AcceptAll -> Unit + is KnockRequestsListEvents.Accept -> Unit + is KnockRequestsListEvents.Decline -> Unit + KnockRequestsListEvents.DismissCurrentAction -> { + actions.value = KnockRequestsCurrentAction.None + } } } return KnockRequestsListState( + knockRequests = AsyncData.Success(persistentListOf()), + currentAction = actions.value, eventSink = ::handleEvents ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 8433c29a5a..0aa96b4f28 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -7,8 +7,20 @@ package io.element.android.features.knockrequests.impl.list -// TODO add your ui models. Remove the eventSink if you don't have events. -// Do not use default value, so no member get forgotten in the presenters. +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.ImmutableList + data class KnockRequestsListState( - val eventSink: (KnockRequestsListEvents) -> Unit + val knockRequests: AsyncData>, + val currentAction: KnockRequestsCurrentAction, + val eventSink: (KnockRequestsListEvents) -> Unit, ) + +sealed interface KnockRequestsCurrentAction { + data object None : KnockRequestsCurrentAction + data class Accept(val async: AsyncAction) : KnockRequestsCurrentAction + data class Decline(val async: AsyncAction) : KnockRequestsCurrentAction + data class AcceptAll(val async: AsyncAction) : KnockRequestsCurrentAction +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 4301ccdc32..761c850180 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -8,15 +8,73 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.core.UserId +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf open class KnockRequestsListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aKnockRequestsListState(), - // Add other states here + aKnockRequestsListState( + knockRequests = AsyncData.Loading(), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf() + ), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf( + aKnockRequest() + ) + ), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf( + aKnockRequest(), + aKnockRequest( + userId = UserId("@user:example.com"), + displayName = null, + avatarUrl = null, + reason = null, + ) + ) + ), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf( + aKnockRequest() + ) + ), + actions = KnockRequestsCurrentAction.AcceptAll(AsyncAction.Loading), + ), ) } -fun aKnockRequestsListState() = KnockRequestsListState( - eventSink = {} +fun aKnockRequest( + userId: UserId = UserId("@jacob_ross:example.com"), + displayName: String? = "Jacob Ross", + avatarUrl: String? = null, + reason: String? = "Hi, I would like to get access to this room please.", +) = KnockRequest( + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, + reason = reason, +) + +fun aKnockRequestsListState( + knockRequests: AsyncData> = AsyncData.Success(persistentListOf()), + actions: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + eventSink: (KnockRequestsListEvents) -> Unit = {}, +) = KnockRequestsListState( + knockRequests = knockRequests, + currentAction = actions, + eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 790a970883..b8ab403c1f 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -7,31 +7,278 @@ package io.element.android.features.knockrequests.impl.list +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow 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.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.getAvatarData +import io.element.android.features.knockrequests.impl.getBestName +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider +import io.element.android.libraries.designsystem.theme.components.OutlinedButton +import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList @Composable fun KnockRequestsListView( state: KnockRequestsListState, + onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { - Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text( - "KnockRequestsList feature view", - color = MaterialTheme.colorScheme.primary, + Scaffold( + modifier = modifier, + topBar = { + KnockRequestsListTopBar(onBackClick = onBackClick) + }, + content = { padding -> + KnockRequestsListContent( + state = state, + modifier = Modifier + .padding(padding) + .consumeWindowInsets(padding), + ) + } + ) +} + +@Composable +private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Modifier) { + + fun onAcceptClick(knockRequest: KnockRequest) { + state.eventSink(KnockRequestsListEvents.Accept(knockRequest)) + } + + fun onDeclineClick(knockRequest: KnockRequest) { + state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) + } + + Box(modifier, contentAlignment = Alignment.Center) { + when (state.knockRequests) { + is AsyncData.Success -> { + val knockRequests = state.knockRequests.data + if (knockRequests.isEmpty()) { + KnockRequestsListEmpty() + } else { + KnockRequestsList( + knockRequests = knockRequests, + onAcceptClick = ::onAcceptClick, + onDeclineClick = ::onDeclineClick, + ) + } + } + else -> Unit + } + KnockRequestsActionsView( + actions = state.currentAction, + onDismiss = { + state.eventSink(KnockRequestsListEvents.AcceptAll) + }, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } +} + +@Composable +private fun KnockRequestsActionsView( + actions: KnockRequestsCurrentAction, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier) { + when (actions) { + is KnockRequestsCurrentAction.AcceptAll -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } + is KnockRequestsCurrentAction.Accept -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } + is KnockRequestsCurrentAction.Decline -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } + KnockRequestsCurrentAction.None -> Unit + } + } +} + +@Composable +private fun KnockRequestsList( + knockRequests: ImmutableList, + onAcceptClick: (KnockRequest) -> Unit, + onDeclineClick: (KnockRequest) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn(modifier = modifier) { + itemsIndexed(knockRequests) { index, knockRequest -> + KnockRequestItem( + knockRequest = knockRequest, + onAcceptClick = onAcceptClick, + onDeclineClick = onDeclineClick, + ) + if (index != knockRequests.size - 1) { + HorizontalDivider() + } + } + } +} + +@Composable +private fun KnockRequestItem( + knockRequest: KnockRequest, + onAcceptClick: (KnockRequest) -> Unit, + onDeclineClick: (KnockRequest) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + Avatar(knockRequest.getAvatarData(AvatarSize.KnockRequestItem)) + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier) { + // Name + Text( + modifier = Modifier.clipToBounds(), + text = knockRequest.getBestName(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary, + style = ElementTheme.typography.fontBodyLgMedium, + ) + // UserId + if (!knockRequest.displayName.isNullOrEmpty()) { + Text( + text = knockRequest.userId.value, + color = MaterialTheme.colorScheme.secondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + // Reason + if (!knockRequest.reason.isNullOrBlank()) { + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = knockRequest.reason, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + Spacer(modifier = Modifier.height(12.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { + OutlinedButton( + text = stringResource(CommonStrings.action_decline), + onClick = { + onDeclineClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + Button( + text = stringResource(CommonStrings.action_accept), + onClick = { + onAcceptClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + TextButton( + text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), + onClick = { + onAcceptClick(knockRequest) + }, + destructive = true, + size = ButtonSize.Small, + modifier = Modifier.fillMaxWidth(), + ) + + } + } +} + +@Composable +private fun KnockRequestsListEmpty( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier.padding( + horizontal = 32.dp, + vertical = 48.dp, + ), + contentAlignment = Alignment.Center, + ) { + IconTitleSubtitleMolecule( + title = "No pending request to join", + subTitle = "When somebody will ask to join the room, you’ll be able to see their request here.", + iconStyle = BigIcon.Style.Default(CompoundIcons.Pin()), ) } } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun KnockRequestsListTopBar(onBackClick: () -> Unit) { + TopAppBar( + title = { + Text( + text = "Requests to join", + style = ElementTheme.typography.aliasScreenTitle, + ) + }, + navigationIcon = { BackButton(onClick = onBackClick) }, + ) +} + @PreviewsDayNight @Composable internal fun KnockRequestsListViewPreview( @@ -39,5 +286,6 @@ internal fun KnockRequestsListViewPreview( ) = ElementPreview { KnockRequestsListView( state = state, + onBackClick = {}, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 49a3e93e87..c5bd468abe 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -54,4 +54,6 @@ enum class AvatarSize(val dp: Dp) { EditProfileDetails(96.dp), Suggestion(32.dp), + + KnockRequestItem(52.dp), } From 1978c9dbdbe2238cb35046c78df262a47427a0ac Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Nov 2024 21:31:23 +0100 Subject: [PATCH 004/203] knock requests : accept all ui --- .../impl/list/KnockRequestsListState.kt | 4 +- .../impl/list/KnockRequestsListView.kt | 39 ++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 0aa96b4f28..072ff5bdee 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -16,7 +16,9 @@ data class KnockRequestsListState( val knockRequests: AsyncData>, val currentAction: KnockRequestsCurrentAction, val eventSink: (KnockRequestsListEvents) -> Unit, -) +) { + val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 +} sealed interface KnockRequestsCurrentAction { data object None : KnockRequestsCurrentAction diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index b8ab403c1f..416a00194c 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -7,12 +7,14 @@ package io.element.android.features.knockrequests.impl.list +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -25,6 +27,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -88,12 +92,12 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) } - Box(modifier, contentAlignment = Alignment.Center) { + Box(modifier.fillMaxSize()) { when (state.knockRequests) { is AsyncData.Success -> { val knockRequests = state.knockRequests.data if (knockRequests.isEmpty()) { - KnockRequestsListEmpty() + KnockRequestsEmptyList() } else { KnockRequestsList( knockRequests = knockRequests, @@ -107,10 +111,17 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo KnockRequestsActionsView( actions = state.currentAction, onDismiss = { - state.eventSink(KnockRequestsListEvents.AcceptAll) + state.eventSink(KnockRequestsListEvents.DismissCurrentAction) }, - modifier = Modifier.align(Alignment.BottomCenter), ) + if (state.canAcceptAll) { + KnockRequestsAcceptAll( + onClick = { + state.eventSink(KnockRequestsListEvents.AcceptAll) + }, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } } } @@ -247,7 +258,25 @@ private fun KnockRequestItem( } @Composable -private fun KnockRequestsListEmpty( +private fun KnockRequestsAcceptAll(onClick: () -> Unit, modifier: Modifier) { + Box( + modifier = modifier + .shadow(elevation = 24.dp, spotColor = Color.Transparent) + .background(color = ElementTheme.colors.bgCanvasDefault) + .padding(vertical = 12.dp, horizontal = 16.dp) + ) + { + OutlinedButton( + text = ("Accept all"), + onClick = onClick, + size = ButtonSize.Medium, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun KnockRequestsEmptyList( modifier: Modifier = Modifier, ) { Box( From 20ed6bb0d2141200a2562fda1a142c3fffa2e7df Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Nov 2024 11:40:47 +0100 Subject: [PATCH 005/203] knock requests : add permissions check --- .../impl/list/KnockRequestsListEvents.kt | 1 + .../impl/list/KnockRequestsListPresenter.kt | 60 ++++++++++++--- .../impl/list/KnockRequestsListState.kt | 8 +- .../list/KnockRequestsListStateProvider.kt | 45 +++++++++++- .../impl/list/KnockRequestsListView.kt | 73 +++++++++++++------ 5 files changed, 149 insertions(+), 38 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt index d84e5209b9..132c137ce2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -12,6 +12,7 @@ import io.element.android.features.knockrequests.impl.KnockRequest sealed interface KnockRequestsListEvents { data class Accept(val knockRequest: KnockRequest) : KnockRequestsListEvents data class Decline(val knockRequest: KnockRequest) : KnockRequestsListEvents + data class DeclineAndBan(val knockRequest: KnockRequest) : KnockRequestsListEvents data object AcceptAll : KnockRequestsListEvents data object DismissCurrentAction : KnockRequestsListEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index e7168e967b..8ae993c9ef 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -8,35 +8,77 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +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.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.ui.room.canBanAsState +import io.element.android.libraries.matrix.ui.room.canInviteAsState +import io.element.android.libraries.matrix.ui.room.canKickAsState import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject -class KnockRequestsListPresenter @Inject constructor() : Presenter { +class KnockRequestsListPresenter @Inject constructor( + private val room: MatrixRoom, +) : Presenter { @Composable override fun present(): KnockRequestsListState { - val actions = remember { - mutableStateOf(KnockRequestsCurrentAction.None) - } + val currentAction = remember { mutableStateOf(KnockRequestsCurrentAction.None) } + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() + val canBan by room.canBanAsState(syncUpdateFlow.value) + val canDecline by room.canKickAsState(syncUpdateFlow.value) + val canAccept by room.canInviteAsState(syncUpdateFlow.value) fun handleEvents(event: KnockRequestsListEvents) { when (event) { - KnockRequestsListEvents.AcceptAll -> Unit - is KnockRequestsListEvents.Accept -> Unit - is KnockRequestsListEvents.Decline -> Unit + KnockRequestsListEvents.AcceptAll -> { + currentAction.value = KnockRequestsCurrentAction.AcceptAll(AsyncAction.Uninitialized) + } + is KnockRequestsListEvents.Accept -> { + currentAction.value = KnockRequestsCurrentAction.Accept(event.knockRequest, AsyncAction.Uninitialized) + } + is KnockRequestsListEvents.Decline -> { + currentAction.value = KnockRequestsCurrentAction.Decline(event.knockRequest, AsyncAction.Uninitialized) + } + is KnockRequestsListEvents.DeclineAndBan -> { + currentAction.value = KnockRequestsCurrentAction.DeclineAndBan(event.knockRequest, AsyncAction.Uninitialized) + } KnockRequestsListEvents.DismissCurrentAction -> { - actions.value = KnockRequestsCurrentAction.None + currentAction.value = KnockRequestsCurrentAction.None + } + } + } + + LaunchedEffect(currentAction) { + when (val action = currentAction.value) { + is KnockRequestsCurrentAction.Accept -> { + // Accept the knock request + } + is KnockRequestsCurrentAction.Decline -> { + // Decline the knock request + } + is KnockRequestsCurrentAction.DeclineAndBan -> { + // Decline and ban the user + } + is KnockRequestsCurrentAction.AcceptAll -> { + // Accept all knock requests } + KnockRequestsCurrentAction.None -> Unit } } return KnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf()), - currentAction = actions.value, + currentAction = currentAction.value, + canAccept = canAccept, + canDecline = canDecline, + canBan = canBan, eventSink = ::handleEvents ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 072ff5bdee..8c4cd3e0ed 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -15,6 +15,9 @@ import kotlinx.collections.immutable.ImmutableList data class KnockRequestsListState( val knockRequests: AsyncData>, val currentAction: KnockRequestsCurrentAction, + val canAccept: Boolean, + val canDecline: Boolean, + val canBan: Boolean, val eventSink: (KnockRequestsListEvents) -> Unit, ) { val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 @@ -22,7 +25,8 @@ data class KnockRequestsListState( sealed interface KnockRequestsCurrentAction { data object None : KnockRequestsCurrentAction - data class Accept(val async: AsyncAction) : KnockRequestsCurrentAction - data class Decline(val async: AsyncAction) : KnockRequestsCurrentAction + data class Accept(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction + data class Decline(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction + data class DeclineAndBan(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction data class AcceptAll(val async: AsyncAction) : KnockRequestsCurrentAction } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 761c850180..5a94a000ae 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -52,7 +52,40 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), - actions: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + currentAction: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + canAccept: Boolean = true, + canDecline: Boolean = true, + canBan: Boolean = true, eventSink: (KnockRequestsListEvents) -> Unit = {}, ) = KnockRequestsListState( knockRequests = knockRequests, - currentAction = actions, + currentAction = currentAction, + canAccept = canAccept, + canDecline = canDecline, + canBan = canBan, eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 416a00194c..bbf7facfd8 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -101,6 +101,9 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo } else { KnockRequestsList( knockRequests = knockRequests, + canAccept = state.canAccept, + canDecline = state.canDecline, + canBan = state.canBan, onAcceptClick = ::onAcceptClick, onDeclineClick = ::onDeclineClick, ) @@ -154,6 +157,13 @@ private fun KnockRequestsActionsView( onErrorDismiss = onDismiss, ) } + is KnockRequestsCurrentAction.DeclineAndBan -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } KnockRequestsCurrentAction.None -> Unit } } @@ -162,6 +172,9 @@ private fun KnockRequestsActionsView( @Composable private fun KnockRequestsList( knockRequests: ImmutableList, + canAccept: Boolean, + canDecline: Boolean, + canBan: Boolean, onAcceptClick: (KnockRequest) -> Unit, onDeclineClick: (KnockRequest) -> Unit, modifier: Modifier = Modifier, @@ -171,6 +184,9 @@ private fun KnockRequestsList( KnockRequestItem( knockRequest = knockRequest, onAcceptClick = onAcceptClick, + canBan = canBan, + canDecline = canDecline, + canAccept = canAccept, onDeclineClick = onDeclineClick, ) if (index != knockRequests.size - 1) { @@ -183,6 +199,9 @@ private fun KnockRequestsList( @Composable private fun KnockRequestItem( knockRequest: KnockRequest, + canAccept: Boolean, + canDecline: Boolean, + canBan: Boolean, onAcceptClick: (KnockRequest) -> Unit, onDeclineClick: (KnockRequest) -> Unit, modifier: Modifier = Modifier, @@ -194,7 +213,7 @@ private fun KnockRequestItem( ) { Avatar(knockRequest.getAvatarData(AvatarSize.KnockRequestItem)) Spacer(modifier = Modifier.width(16.dp)) - Column(modifier = Modifier) { + Column { // Name Text( modifier = Modifier.clipToBounds(), @@ -222,37 +241,43 @@ private fun KnockRequestItem( style = ElementTheme.typography.fontBodyMdRegular, ) } + // Actions Spacer(modifier = Modifier.height(12.dp)) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { - OutlinedButton( - text = stringResource(CommonStrings.action_decline), - onClick = { - onDeclineClick(knockRequest) - }, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), - ) - Button( - text = stringResource(CommonStrings.action_accept), + if (canDecline) { + OutlinedButton( + text = stringResource(CommonStrings.action_decline), + onClick = { + onDeclineClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + if (canAccept) { + Button( + text = stringResource(CommonStrings.action_accept), + onClick = { + onAcceptClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + } + if (canBan) { + Spacer(modifier = Modifier.height(12.dp)) + TextButton( + text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), onClick = { onAcceptClick(knockRequest) }, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), + destructive = true, + size = ButtonSize.Small, + modifier = Modifier.fillMaxWidth(), ) } - Spacer(modifier = Modifier.height(12.dp)) - TextButton( - text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), - onClick = { - onAcceptClick(knockRequest) - }, - destructive = true, - size = ButtonSize.Small, - modifier = Modifier.fillMaxWidth(), - ) - } } } From 95cf18dbb5df4bbd45d8f89e2ff526dbad9ffdb1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Nov 2024 16:37:46 +0100 Subject: [PATCH 006/203] knock requests : adjust spacing for actions --- .../features/knockrequests/impl/list/KnockRequestsListView.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index bbf7facfd8..de08550d9c 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -242,7 +242,9 @@ private fun KnockRequestItem( ) } // Actions - Spacer(modifier = Modifier.height(12.dp)) + if (canDecline || canAccept) { + Spacer(modifier = Modifier.height(12.dp)) + } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { if (canDecline) { OutlinedButton( From abb4279b136f88f2040d748ceef6fcf93dcf0175 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 05:01:15 +0000 Subject: [PATCH 007/203] fix(deps): update dependency org.jsoup:jsoup to v1.18.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 1bcab24c19..6b4cc1df85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -169,7 +169,7 @@ serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-jso kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8" showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" } showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" } -jsoup = "org.jsoup:jsoup:1.18.1" +jsoup = "org.jsoup:jsoup:1.18.3" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" From 6acf5eeb2c5bf97b29b862b4187b79ec24cf7b35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 08:19:38 +0000 Subject: [PATCH 008/203] chore(deps): update kotlin --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1bcab24c19..3a244d43a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,9 +4,9 @@ [versions] # Project android_gradle_plugin = "8.7.2" -kotlin = "2.0.21" +kotlin = "2.1.0" kotlinpoet = "2.0.0" -ksp = "2.0.21-1.0.28" +ksp = "2.1.0-1.0.29" firebaseAppDistribution = "5.0.0" # AndroidX From 0f12b151fda5eff6583804409fcfebc30b078147 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 2 Dec 2024 10:55:38 +0100 Subject: [PATCH 009/203] knock requests : allow reason to be expanded --- .../list/KnockRequestsListStateProvider.kt | 11 +++++ .../impl/list/KnockRequestsListView.kt | 42 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 5a94a000ae..7b7b1e4f5e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -33,6 +33,17 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider + if (!isExpanded && result.hasVisualOverflow) { + isExpandable = true + } + }, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + Box(modifier = Modifier.size(24.dp)) { + if (isExpandable) { + Icon( + imageVector = if (isExpanded) CompoundIcons.ChevronUp() else CompoundIcons.ChevronDown(), + contentDescription = null, + tint = ElementTheme.colors.iconTertiary, + ) + } + } + } } // Actions if (canDecline || canAccept) { From 822dbca424d8ceac8e6ee6be962c2d5d568e3b56 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 4 Dec 2024 13:48:15 +0100 Subject: [PATCH 010/203] knock requests : add formatted date --- .../knockrequests/impl/KnockRequest.kt | 1 + .../list/KnockRequestsListStateProvider.kt | 2 ++ .../impl/list/KnockRequestsListView.kt | 30 +++++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt index 3a293dc565..81ba322abe 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -17,6 +17,7 @@ data class KnockRequest( val displayName: String?, val avatarUrl: String?, val reason: String?, + val formattedDate: String?, ) fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 7b7b1e4f5e..551f6b89b0 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -106,11 +106,13 @@ fun aKnockRequest( displayName: String? = "Jacob Ross", avatarUrl: String? = null, reason: String? = "Hi, I would like to get access to this room please.", + formattedDate: String = "20 Nov 2024", ) = KnockRequest( userId = userId, displayName = displayName, avatarUrl = avatarUrl, reason = reason, + formattedDate = formattedDate, ) fun aKnockRequestsListState( diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 2d9480a02e..abc0a09970 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -222,15 +222,27 @@ private fun KnockRequestItem( Avatar(knockRequest.getAvatarData(AvatarSize.KnockRequestItem)) Spacer(modifier = Modifier.width(16.dp)) Column { - // Name - Text( - modifier = Modifier.clipToBounds(), - text = knockRequest.getBestName(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary, - style = ElementTheme.typography.fontBodyLgMedium, - ) + // Name and date + Row { + Text( + modifier = Modifier + .clipToBounds() + .weight(1f), + text = knockRequest.getBestName(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary, + style = ElementTheme.typography.fontBodyLgMedium, + ) + if (!knockRequest.formattedDate.isNullOrEmpty()) { + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = knockRequest.formattedDate, + color = MaterialTheme.colorScheme.secondary, + style = ElementTheme.typography.fontBodySmRegular, + ) + } + } // UserId if (!knockRequest.displayName.isNullOrEmpty()) { Text( From 80f25e637a4ab4c39be4789e0cf9272b483e66af Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 4 Dec 2024 14:15:17 +0100 Subject: [PATCH 011/203] knock requests : cleaning up --- features/knockrequests/api/build.gradle.kts | 3 -- .../knockrequests/impl/KnockRequest.kt | 4 -- .../impl/list/KnockRequestsListNode.kt | 2 - .../impl/list/KnockRequestsListPresenter.kt | 3 +- .../impl/list/KnockRequestsListView.kt | 45 ++++++++++++++----- .../impl/src/main/res/values/localazy.xml | 17 +++++++ .../roomdetails/impl/RoomDetailsPresenter.kt | 4 -- .../src/main/res/values/localazy.xml | 5 --- tools/localazy/config.json | 6 +++ 9 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 features/knockrequests/impl/src/main/res/values/localazy.xml diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts index e3d563f2e0..90bcb6e568 100644 --- a/features/knockrequests/api/build.gradle.kts +++ b/features/knockrequests/api/build.gradle.kts @@ -5,8 +5,6 @@ * Please see LICENSE in the repository root for full details. */ -import extension.setupAnvil - plugins { id("io.element.android-library") } @@ -19,4 +17,3 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) } - diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt index 81ba322abe..d9df9a5bc2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -10,7 +10,6 @@ package io.element.android.features.knockrequests.impl import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.user.MatrixUser data class KnockRequest( val userId: UserId, @@ -30,6 +29,3 @@ fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( fun KnockRequest.getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } - - - diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt index 0310f67d2e..ce8d602861 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -15,7 +15,6 @@ import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.RoomScope @ContributesNode(RoomScope::class) @@ -24,7 +23,6 @@ class KnockRequestsListNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: KnockRequestsListPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index 8ae993c9ef..fcafc5428b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor( private val room: MatrixRoom, ) : Presenter { - @Composable override fun present(): KnockRequestsListState { val currentAction = remember { mutableStateOf(KnockRequestsCurrentAction.None) } @@ -56,7 +55,7 @@ class KnockRequestsListPresenter @Inject constructor( } LaunchedEffect(currentAction) { - when (val action = currentAction.value) { + when (currentAction.value) { is KnockRequestsCurrentAction.Accept -> { // Accept the knock request } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index abc0a09970..ca289eeef4 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets @@ -28,7 +29,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -36,6 +39,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -43,6 +47,7 @@ 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.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getAvatarData import io.element.android.features.knockrequests.impl.getBestName import io.element.android.libraries.architecture.AsyncData @@ -54,6 +59,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize @@ -90,8 +96,10 @@ fun KnockRequestsListView( } @Composable -private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Modifier) { - +private fun KnockRequestsListContent( + state: KnockRequestsListState, + modifier: Modifier = Modifier, +) { fun onAcceptClick(knockRequest: KnockRequest) { state.eventSink(KnockRequestsListEvents.Accept(knockRequest)) } @@ -100,6 +108,8 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) } + var bottomPaddingInPixels by remember { mutableIntStateOf(0) } + Box(modifier.fillMaxSize()) { when (state.knockRequests) { is AsyncData.Success -> { @@ -114,6 +124,7 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo canBan = state.canBan, onAcceptClick = ::onAcceptClick, onDeclineClick = ::onDeclineClick, + contentPadding = PaddingValues(bottom = bottomPaddingInPixels.toDp()), ) } } @@ -130,6 +141,9 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo onClick = { state.eventSink(KnockRequestsListEvents.AcceptAll) }, + onHeightChange = { height -> + bottomPaddingInPixels = height + }, modifier = Modifier.align(Alignment.BottomCenter), ) } @@ -186,8 +200,12 @@ private fun KnockRequestsList( onAcceptClick: (KnockRequest) -> Unit, onDeclineClick: (KnockRequest) -> Unit, modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { - LazyColumn(modifier = modifier) { + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = contentPadding, + ) { itemsIndexed(knockRequests) { index, knockRequest -> KnockRequestItem( knockRequest = knockRequest, @@ -316,7 +334,7 @@ private fun KnockRequestItem( if (canBan) { Spacer(modifier = Modifier.height(12.dp)) TextButton( - text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), + text = stringResource(R.string.screen_knock_requests_list_decline_and_ban_action_title), onClick = { onAcceptClick(knockRequest) }, @@ -325,22 +343,25 @@ private fun KnockRequestItem( modifier = Modifier.fillMaxWidth(), ) } - } } } @Composable -private fun KnockRequestsAcceptAll(onClick: () -> Unit, modifier: Modifier) { +private fun KnockRequestsAcceptAll( + onClick: () -> Unit, + onHeightChange: (Int) -> Unit, + modifier: Modifier = Modifier +) { Box( modifier = modifier .shadow(elevation = 24.dp, spotColor = Color.Transparent) .background(color = ElementTheme.colors.bgCanvasDefault) .padding(vertical = 12.dp, horizontal = 16.dp) - ) - { + .onSizeChanged { onHeightChange(it.height) } + ) { OutlinedButton( - text = ("Accept all"), + text = stringResource(R.string.screen_knock_requests_list_accept_all_button_title), onClick = onClick, size = ButtonSize.Medium, modifier = Modifier.fillMaxWidth(), @@ -360,8 +381,8 @@ private fun KnockRequestsEmptyList( contentAlignment = Alignment.Center, ) { IconTitleSubtitleMolecule( - title = "No pending request to join", - subTitle = "When somebody will ask to join the room, you’ll be able to see their request here.", + title = stringResource(R.string.screen_knock_requests_list_empty_state_title), + subTitle = stringResource(R.string.screen_knock_requests_list_empty_state_description), iconStyle = BigIcon.Style.Default(CompoundIcons.Pin()), ) } @@ -373,7 +394,7 @@ private fun KnockRequestsListTopBar(onBackClick: () -> Unit) { TopAppBar( title = { Text( - text = "Requests to join", + text = stringResource(R.string.screen_knock_requests_list_title), style = ElementTheme.typography.aliasScreenTitle, ) }, diff --git a/features/knockrequests/impl/src/main/res/values/localazy.xml b/features/knockrequests/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..df14d665b8 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values/localazy.xml @@ -0,0 +1,17 @@ + + + "Yes, accept all" + "Are you sure you want to accept all requests to join?" + "Accept all requests" + "Accept all" + "Yes, decline and ban" + "Are you sure you want to decline and ban %1$s? This user won’t be able to request access to join this room again." + "Decline and ban from accessing" + "Yes, decline" + "Are you sure you want to decline %1$s request to join this room?" + "Decline access" + "Decline and ban" + "When somebody will ask to join the room, you’ll be able to see their request here." + "No pending request to join" + "Requests to join" + diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index dc6568a60f..46588ae5fe 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -169,10 +169,6 @@ class RoomDetailsPresenter @Inject constructor( ) } - private fun getCanBan(membersState: MatrixRoomMembersState): Any { - TODO("Not yet implemented") - } - @Composable private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { dmMemberState?.let { roomMember -> diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 18d580d9f4..f52b53564b 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -296,11 +296,6 @@ Reason: %1$s." "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "Accept all" - "Decline and ban" - "When somebody will ask to join the room, you’ll be able to see their request here." - "No pending request to join" - "Requests to join" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." diff --git a/tools/localazy/config.json b/tools/localazy/config.json index f18744bae9..9bf178ac85 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -286,6 +286,12 @@ "screen_join_room_.*", "screen\\.join_room\\..*" ] + }, + { + "name" : ":features:knockrequests:impl", + "includeRegex" : [ + "screen\\.knock_requests_list\\..*" + ] } ] } From 6bf9c41d6b9918710940ff1e0e44880168908d1f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 4 Dec 2024 13:48:20 +0000 Subject: [PATCH 012/203] Update screenshots --- ....knockrequests.impl.list_KnockRequestsListView_Day_0_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_1_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_2_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_3_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_4_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_5_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_6_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_7_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_8_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_9_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_0_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_1_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_2_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_3_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_4_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_5_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_6_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_7_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_8_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_9_en.png | 3 +++ ...ies.designsystem.components.avatar_Avatar_Avatars_78_en.png | 3 +++ ...ies.designsystem.components.avatar_Avatar_Avatars_79_en.png | 3 +++ ...ies.designsystem.components.avatar_Avatar_Avatars_80_en.png | 3 +++ 23 files changed, 69 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png new file mode 100644 index 0000000000..d2ddc2c7c6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbbd687e5e0a1fd3a1be442937e32990dc72a6e4817d6ffc29f6f596eeddef1b +size 8086 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png new file mode 100644 index 0000000000..66c6102947 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9595edf8183796a0a0efa838a3c5265e07f70452933e6255d79f536cfca64ac +size 26138 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png new file mode 100644 index 0000000000..b75df7ecea --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a71bcfe210fdc6b2a0c54fe187d9528811bd6b5d442a66a1151b7a00ae514273 +size 33141 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png new file mode 100644 index 0000000000..92f98168bb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:036a4f8f2f478df0e1705ff4bfd04599f6c0251936d2a80d750c9055d9d63ae0 +size 41550 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png new file mode 100644 index 0000000000..f125ca034e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99f070f9b5bdea3aa057776b303be171daff36f684770174f46b37ecc0eea781 +size 52135 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png new file mode 100644 index 0000000000..c14ef5dd52 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7e66d3f7d10e759f629795580545cda6644292020ce36763d07e9d476dcc231 +size 30325 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png new file mode 100644 index 0000000000..466e591450 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:586021e67718aa949b1de04799a43d9da8189fadacdb6dba23405c762fc7ee06 +size 30618 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png new file mode 100644 index 0000000000..2952c5bd81 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b71dc6dc0d29801e81d1d4e52df811624583f69c0510f2f650676747a326455 +size 30296 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png new file mode 100644 index 0000000000..9c47af83a4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6be2cfe7991bce6c622f4f2e45f4ac96810cdc9462d9a4d7aea11baf69ab5ac +size 27446 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png new file mode 100644 index 0000000000..8f0414d4b3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9afb58cfcd13c064f0b17d449318d5111aaa914cb8875fe0596c66e5a8a17051 +size 30647 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png new file mode 100644 index 0000000000..5188e40e69 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ff4bc9e0588ea6a8396572b9ac9fb2401c3193fc3f1fbeb75efd69be6dda24d +size 7867 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png new file mode 100644 index 0000000000..055bfccd93 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37e24295a404463010f6d36ab94b1ac3c2e193d9122637abbeb0a43abcb427c1 +size 25669 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png new file mode 100644 index 0000000000..b9982135b9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7548a1029b4e63722aca769800728c0bb519d3d736cb54138bc5e053a1aca46e +size 32150 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png new file mode 100644 index 0000000000..2c6855f6ab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b094584e67852da5ecb74511143943d3526965a548a24d5bb4e23540fc77b90 +size 40527 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png new file mode 100644 index 0000000000..0bca790524 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79e34ab35d9bd44d6e7694df882995f401f49a2ba042c6a3b2c010c12657750f +size 50637 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png new file mode 100644 index 0000000000..45b2a829ef --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c30b6e25ec148d4f98a6c305b7d71af6c7e413cc3425f5a6f5b42a201039f491 +size 29007 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png new file mode 100644 index 0000000000..b39ccb0150 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5c6abc8bd5ca40eb91d926adc798cee95a8aacb0e87d937983c6240173fe2b9 +size 30071 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png new file mode 100644 index 0000000000..215cef276c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b67c8d2cedb724ece75a2171463c4e035d562c4480d39b11b1072f4fdae3053 +size 29840 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png new file mode 100644 index 0000000000..171b5f5aa5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15903c9494100becc4dd2bc6e9fb7f38f8eebbb595cbc251cfc0f839d9a99a27 +size 27322 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png new file mode 100644 index 0000000000..54a5fc7d9c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68294db4cbd8bb42f4a0b6534b48585341beb2bc405e93cf74a2655f049522ff +size 29768 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png new file mode 100644 index 0000000000..cf85f766ab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66c29b560708bb2d8870d8cf5caf4f0da49815b5527fed7294a88c0b3aa05c73 +size 18079 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png new file mode 100644 index 0000000000..b27ebae0ce --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dd106bc9b54dfc0c4e3e9a5f04d3df52f58f71e4c3cd3d80f53d3f1308f22a1 +size 16838 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png new file mode 100644 index 0000000000..1698660c8e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4979794c700bc8bab1bc767cc2387ce3be28b9d2c0b6da0696b237445ce7df95 +size 21225 From cb0e3bf1dd9072fceea0988f94dcdf967f1744b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 23:01:45 +0000 Subject: [PATCH 013/203] Update dependency com.autonomousapps.dependency-analysis to v2.6.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 513bdc9328..afe4bafec5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,7 @@ wysiwyg = "2.37.14" telephoto = "0.14.0" # Dependency analysis -dependencyAnalysis = "2.5.0" +dependencyAnalysis = "2.6.0" # DI dagger = "2.53" From 5ac401be57e1a36d4fc93cd0b2df41e49c492dc7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 12:15:35 +0100 Subject: [PATCH 014/203] knock requests : fix stability of KnockRequestsCurrentAction --- .../features/knockrequests/impl/list/KnockRequestsListState.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 8c4cd3e0ed..3ba10e9302 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -7,6 +7,7 @@ package io.element.android.features.knockrequests.impl.list +import androidx.compose.runtime.Immutable import io.element.android.features.knockrequests.impl.KnockRequest import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -23,6 +24,7 @@ data class KnockRequestsListState( val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 } +@Immutable sealed interface KnockRequestsCurrentAction { data object None : KnockRequestsCurrentAction data class Accept(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction From be1a8a3d16f41082704f2f14ff7caff7f6d6a0b7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 12:57:15 +0100 Subject: [PATCH 015/203] knock requests : fix test on room details view --- .../roomdetails/impl/RoomDetailsView.kt | 4 ++-- .../impl/src/main/res/values/localazy.xml | 3 +++ .../roomdetails/impl/RoomDetailsViewTest.kt | 19 ++++++++++++++++++- .../src/main/res/values/localazy.xml | 2 -- tools/localazy/config.json | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 80f7beda56..d77cd9e6ea 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -241,7 +241,7 @@ fun RoomDetailsView( @Composable private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: () -> Unit) { ListItem( - headlineContent = { Text(stringResource(CommonStrings.screen_room_details_requests_to_join_title)) }, + headlineContent = { Text(stringResource(R.string.screen_room_details_requests_to_join_title)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) { null @@ -546,7 +546,7 @@ private fun PinnedMessagesItem( ) { val analyticsService = LocalAnalyticsService.current ListItem( - headlineContent = { Text(stringResource(CommonStrings.screen_room_details_pinned_events_row_title)) }, + headlineContent = { Text(stringResource(R.string.screen_room_details_pinned_events_row_title)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Pin())), trailingContent = if (pinnedMessagesCount == null) { diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 19400c85b3..50c2d639be 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -49,9 +49,12 @@ "Invite people" "Leave conversation" "Leave room" + "Media and files" "Custom" "Default" "Notifications" + "Pinned messages" + "Requests to join" "Roles and permissions" "Room name" "Security" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index e4eb8d5f69..abbca71b53 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -129,7 +129,7 @@ class RoomDetailsViewTest { ), onPinnedMessagesClick = callback, ) - rule.clickOn(CommonStrings.screen_room_details_pinned_events_row_title) + rule.clickOn(R.string.screen_room_details_pinned_events_row_title) } } @@ -253,6 +253,21 @@ class RoomDetailsViewTest { rule.clickOn(R.string.screen_room_details_leave_room_title) eventsRecorder.assertSingle(RoomDetailsEvent.LeaveRoom) } + + @Config(qualifiers = "h1024dp") + @Test + fun `click on knock requests invokes expected callback`() { + ensureCalledOnce { callback -> + rule.setRoomDetailView( + state = aRoomDetailsState( + eventSink = EventsRecorder(expectEvents = false), + canShowKnockRequests = true, + ), + onKnockRequestsClick = callback, + ) + rule.clickOn(R.string.screen_room_details_requests_to_join_title) + } + } } private fun AndroidComposeTestRule.setRoomDetailView( @@ -270,6 +285,7 @@ private fun AndroidComposeTestRule.setRoomD openAdminSettings: () -> Unit = EnsureNeverCalled(), onJoinCallClick: () -> Unit = EnsureNeverCalled(), onPinnedMessagesClick: () -> Unit = EnsureNeverCalled(), + onKnockRequestsClick: () -> Unit = EnsureNeverCalled(), ) { setContent { RoomDetailsView( @@ -285,6 +301,7 @@ private fun AndroidComposeTestRule.setRoomD openAdminSettings = openAdminSettings, onJoinCallClick = onJoinCallClick, onPinnedMessagesClick = onPinnedMessagesClick, + onKnockRequestsClick = onKnockRequestsClick, ) } } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 8ab9695844..75b0f6853a 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -320,8 +320,6 @@ Reason: %1$s." "Your message was not sent because %1$s has not verified all devices" "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" - "Pinned messages" - "Requests to join" "Failed processing media to upload, please try again." "Could not retrieve user details" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 9bf178ac85..fe95d1930e 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -165,6 +165,7 @@ "name" : ":features:roomdetails:impl", "includeRegex" : [ "screen_room_details_.*", + "screen\\.room_details\\..*", "screen_room_member_list_.*", "screen_room_notification_settings_.*", "screen_notification_settings_edit_failed_updating_default_mode", From e845840d8998cfa114a2d67ff7f7a07805a19de4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:25:07 +0000 Subject: [PATCH 016/203] Update dependency com.google.firebase:firebase-bom to v33.7.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 748a62bd88..b2ad997d77 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -77,7 +77,7 @@ kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", ve ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } gms_google_services = "com.google.gms:google-services:4.4.2" # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:33.6.0" +google_firebase_bom = "com.google.firebase:firebase-bom:33.7.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } From 584f20a08888fc8c5fb9497645f98ff47181e143 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 18:28:31 +0100 Subject: [PATCH 017/203] knock requests : small nit on RoomDetailsNode Co-authored-by: Benoit Marty --- .../android/features/roomdetails/impl/RoomDetailsNode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index e9ad095c76..a56a028808 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -146,7 +146,7 @@ class RoomDetailsNode @AssistedInject constructor( openAdminSettings = this::openAdminSettings, onJoinCallClick = ::onJoinCall, onPinnedMessagesClick = ::openPinnedMessages, - onKnockRequestsClick = ::openKnockRequestsLists + onKnockRequestsClick = ::openKnockRequestsLists, ) } } From 3058739461707e3135a6aa2b77b7cd0cbcacc8cb Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 11:56:04 +0100 Subject: [PATCH 018/203] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 2730a94337..fe63c2c68e 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -47,7 +47,7 @@ private const val versionMinor = 7 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 5 +private const val versionPatch = 6 object Versions { const val VERSION_CODE = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 21f7bcd9f8b0bfc502493c4270a8b4a2003237df Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2024 11:58:52 +0100 Subject: [PATCH 019/203] Add destructive param to BigIcon.Style.Default to be able to render icons with red tint. --- .../designsystem/components/BigIcon.kt | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt index 5b22b534f4..47230b35ae 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt @@ -49,7 +49,11 @@ object BigIcon { * @param vectorIcon the [ImageVector] to display * @param contentDescription the content description of the icon, if any. It defaults to `null` */ - data class Default(val vectorIcon: ImageVector, val contentDescription: String? = null) : Style + data class Default( + val vectorIcon: ImageVector, + val contentDescription: String? = null, + val destructive: Boolean = false, + ) : Style /** * An alert style with a transparent background. @@ -84,25 +88,40 @@ object BigIcon { modifier: Modifier = Modifier, ) { val backgroundColor = when (style) { - is Style.Default -> ElementTheme.colors.bgSubtleSecondary - Style.Alert, Style.Success -> Color.Transparent + is Style.Default -> if (style.destructive) { + ElementTheme.colors.bgCriticalSubtle + } else { + ElementTheme.colors.bgSubtleSecondary + } + Style.Alert, + Style.Success -> Color.Transparent Style.AlertSolid -> ElementTheme.colors.bgCriticalSubtle Style.SuccessSolid -> ElementTheme.colors.bgSuccessSubtle } val icon = when (style) { is Style.Default -> style.vectorIcon - Style.Alert, Style.AlertSolid -> CompoundIcons.Error() - Style.Success, Style.SuccessSolid -> CompoundIcons.CheckCircleSolid() + Style.Alert, + Style.AlertSolid -> CompoundIcons.Error() + Style.Success, + Style.SuccessSolid -> CompoundIcons.CheckCircleSolid() } val contentDescription = when (style) { is Style.Default -> style.contentDescription - Style.Alert, Style.AlertSolid -> stringResource(CommonStrings.common_error) - Style.Success, Style.SuccessSolid -> stringResource(CommonStrings.common_success) + Style.Alert, + Style.AlertSolid -> stringResource(CommonStrings.common_error) + Style.Success, + Style.SuccessSolid -> stringResource(CommonStrings.common_success) } val iconTint = when (style) { - is Style.Default -> ElementTheme.colors.iconSecondary - Style.Alert, Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary - Style.Success, Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary + is Style.Default -> if (style.destructive) { + ElementTheme.colors.iconCriticalPrimary + } else { + ElementTheme.colors.iconSecondary + } + Style.Alert, + Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary + Style.Success, + Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary } Box( modifier = modifier @@ -140,6 +159,7 @@ internal class BigIconStyleProvider : PreviewParameterProvider { BigIcon.Style.Default(Icons.Filled.CatchingPokemon), BigIcon.Style.Alert, BigIcon.Style.AlertSolid, + BigIcon.Style.Default(Icons.Filled.CatchingPokemon, destructive = true), BigIcon.Style.Success, BigIcon.Style.SuccessSolid ) From 1a8d8a7b6a51af4981dcc2d99729a39d4b34f78f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 6 Dec 2024 11:14:02 +0000 Subject: [PATCH 020/203] Update screenshots --- .../libraries.designsystem.components_BigIcon_Day_0_en.png | 4 ++-- .../libraries.designsystem.components_BigIcon_Night_0_en.png | 4 ++-- ...designsystem.components_PageTitleWithIconFull_Day_3_en.png | 4 ++-- ...designsystem.components_PageTitleWithIconFull_Day_4_en.png | 4 ++-- ...designsystem.components_PageTitleWithIconFull_Day_5_en.png | 3 +++ ...signsystem.components_PageTitleWithIconFull_Night_3_en.png | 4 ++-- ...signsystem.components_PageTitleWithIconFull_Night_4_en.png | 4 ++-- ...signsystem.components_PageTitleWithIconFull_Night_5_en.png | 3 +++ 8 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png index 261e330a8f..6fbfd7ed82 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee6ae9f6af47e39480ec9e78d37da7d2f7174cba71c5274bb6314a2dc346b1ab -size 10691 +oid sha256:560f267082af4e27cc5d36cb4265f67adbc0d6010827c7ad07ac50fae9cfdf68 +size 10640 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png index c057b5283e..9c13143905 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0ee879f0cb6b6d42aa0706c1d2ce763211d95768e0111f03e79eaa923515534 -size 10615 +oid sha256:f18608dd1a83235c28f17c4ed66ebbb35f9692c400e5f0b7a284d33d57199e2f +size 10892 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_3_en.png index d51d509823..951f776d62 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0ae9f53b675f2f754f7cb3ebf3fbe45ae7eae0c63bc8628425e0bf21ff95bcb -size 12485 +oid sha256:315e1d831a1e3082a9d4ea749b76b374b0f8018c6d5c11b1f48ed34db3b3a1c5 +size 13279 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_4_en.png index b7c3ab2d68..d51d509823 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a9d956826399b4a700a4f5d05eed66412f76f59a21a0995a85c69b3c528803a -size 13124 +oid sha256:b0ae9f53b675f2f754f7cb3ebf3fbe45ae7eae0c63bc8628425e0bf21ff95bcb +size 12485 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_5_en.png new file mode 100644 index 0000000000..b7c3ab2d68 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a9d956826399b4a700a4f5d05eed66412f76f59a21a0995a85c69b3c528803a +size 13124 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_3_en.png index 316ef83354..5a1526f50b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8debba5d7b2f5866dafbb553732d5f99e3ecade1e3da873abdf2a82856f6b835 -size 12249 +oid sha256:da1945462f70133c47e33eaaa9dfc3d64033c6a83d4d50b03776adcc7a7f77e0 +size 13334 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_4_en.png index 0f1ab6cc74..316ef83354 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe20c0a4b18c1844df6d962e147a5e939cf0cba70657a67f3286c013942dd010 -size 13068 +oid sha256:8debba5d7b2f5866dafbb553732d5f99e3ecade1e3da873abdf2a82856f6b835 +size 12249 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_5_en.png new file mode 100644 index 0000000000..0f1ab6cc74 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe20c0a4b18c1844df6d962e147a5e939cf0cba70657a67f3286c013942dd010 +size 13068 From 175431fa86f4e37dc06976c3988bfc4328aba84b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2024 13:22:56 +0100 Subject: [PATCH 021/203] Fix BigIcon preview. --- .../designsystem/components/BigIcon.kt | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt index 47230b35ae..468c1c903d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt @@ -10,9 +10,12 @@ package io.element.android.libraries.designsystem.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CatchingPokemon @@ -142,11 +145,19 @@ object BigIcon { @PreviewsDayNight @Composable -internal fun BigIconPreview() { - ElementPreview { - Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) { - val provider = BigIconStyleProvider() - for (style in provider.values) { +internal fun BigIconPreview() = ElementPreview { + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize() + .padding(10.dp), + columns = GridCells.Adaptive(minSize = 64.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + items(BigIconStyleProvider().values.toList()) { style -> + Box( + contentAlignment = Alignment.Center + ) { BigIcon(style = style) } } From d87062d11cf4d047a99e5b47df58cf52fd9a4cf9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 13:23:46 +0100 Subject: [PATCH 022/203] Changelog for version 0.7.5 --- CHANGES.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3dc07f4c66..53d6eb2e12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,72 @@ +Changes in Element X v0.7.5 (2024-12-06) +======================================== + +## What's Changed +### ✨ Features +* Allow to set caption when uploading file and audio files, and allow adding / edit / remove caption on Event with attachment (also works on local echo) by @bmarty in https://github.com/element-hq/element-x-android/pull/3902 +* Enable all notification actions: quick reply, accept/decline invite, mark as read from notification. by @bmarty in https://github.com/element-hq/element-x-android/pull/3916 +* Video player controller by @bmarty in https://github.com/element-hq/element-x-android/pull/3959 +### 🙌 Improvements +* change : confirm biometric before allowing biometric unlock. by @ganfra in https://github.com/element-hq/element-x-android/pull/3930 +* Hide media preprocessing by @bmarty in https://github.com/element-hq/element-x-android/pull/3943 +* changes: iterate on room create screen by @ganfra in https://github.com/element-hq/element-x-android/pull/3966 +* change : knock message supporting text display number of characters by @ganfra in https://github.com/element-hq/element-x-android/pull/3970 +* feat(design) : update send button background by @ganfra in https://github.com/element-hq/element-x-android/pull/4000 +### 🐛 Bugfixes +* Min size for hidden media by @bmarty in https://github.com/element-hq/element-x-android/pull/3906 +* fix : use RoomMembershipObserver to close room screen when leaving by @ganfra in https://github.com/element-hq/element-x-android/pull/3887 +* fix : protect some usages of client to avoid crashes by @bmarty in https://github.com/element-hq/element-x-android/pull/3886 +* Fix long click not working on pinned events timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3940 +* Element Call: display error dialog only when loading the main URL by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3962 +* Fix navigation issue when entering recovery key after navigating from the banner by @bmarty in https://github.com/element-hq/element-x-android/pull/3961 +* navigation : clear backstack when opening room from outer node by @ganfra in https://github.com/element-hq/element-x-android/pull/3984 +* fix : hide keyboard when TextComposer is removed from composition by @ganfra in https://github.com/element-hq/element-x-android/pull/3985 +* fix(room_preview) : catch all exception instead by @ganfra in https://github.com/element-hq/element-x-android/pull/3989 +* fix(room_detail) : hide room avatar preview by @ganfra in https://github.com/element-hq/element-x-android/pull/3992 +* fix(composer) : use HideKeyboardWhenDisposed only in MessagesView by @ganfra in https://github.com/element-hq/element-x-android/pull/3993 +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3936 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3975 +### Dependency upgrades +* Update dependency io.sentry:sentry-android to v7.18.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3891 +* Update plugin sonarqube to v6 - autoclosed by @renovate in https://github.com/element-hq/element-x-android/pull/3895 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.64 by @renovate in https://github.com/element-hq/element-x-android/pull/3907 +* Update dependency com.autonomousapps.dependency-analysis to v2.5.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3909 +* Update dependency org.robolectric:robolectric to v4.14.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3924 +* Update dependency io.element.android:compound-android to v0.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3915 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.65 by @renovate in https://github.com/element-hq/element-x-android/pull/3932 +* Update media3 to v1.5.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3942 +* Update plugin ktlint to v12.1.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3944 +* Update wysiwyg to v2.37.14 by @renovate in https://github.com/element-hq/element-x-android/pull/3948 +* Update mobile-dev-inc/action-maestro-cloud action to v1.9.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3914 +* Update dependency com.lemonappdev:konsist to v0.17.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3947 +* deps : update rust sdk to 0.2.67 and fix breaking changes by @ganfra in https://github.com/element-hq/element-x-android/pull/3957 +* Update dependency com.lemonappdev:konsist to v0.17.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3983 +* Update plugin sonarqube to v6.0.1.5171 by @renovate in https://github.com/element-hq/element-x-android/pull/3958 +* Update dagger to v2.53 by @renovate in https://github.com/element-hq/element-x-android/pull/3986 +* Update dependency com.sigpwned:emoji4j-core to v16 by @renovate in https://github.com/element-hq/element-x-android/pull/3899 +* dependencies : update rust sdk to 0.2.68 by @ganfra in https://github.com/element-hq/element-x-android/pull/3988 +* Update plugin dependencycheck to v11.1.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3994 +* chore(dependencies) : update rust sdk to 0.2.69 by @ganfra in https://github.com/element-hq/element-x-android/pull/3999 +### Others +* Send button iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/3901 +* Fix photo / video name by @bmarty in https://github.com/element-hq/element-x-android/pull/3903 +* Render edited caption. by @bmarty in https://github.com/element-hq/element-x-android/pull/3904 +* Rely on the SDK to decide if a caption is editable or not by @bmarty in https://github.com/element-hq/element-x-android/pull/3917 +* Remove AttachmentsState and use the MessagesNavigator by @bmarty in https://github.com/element-hq/element-x-android/pull/3918 +* Fix element call crash when resuming from notification by @bmarty in https://github.com/element-hq/element-x-android/pull/3926 +* Ensure that the SDK is syncing during an incoming call so that the app can cancel the notification by @bmarty in https://github.com/element-hq/element-x-android/pull/3931 +* Add feature flag to temporary disable sending caption by default in production by @bmarty in https://github.com/element-hq/element-x-android/pull/3953 +* Add timeline action item to copy caption by @bmarty in https://github.com/element-hq/element-x-android/pull/3963 +* Fix wrong name of classes and method by @bmarty in https://github.com/element-hq/element-x-android/pull/3971 +* Rework on media module by @bmarty in https://github.com/element-hq/element-x-android/pull/3967 +* Add warning when adding a caption. by @bmarty in https://github.com/element-hq/element-x-android/pull/3977 +* Do not auto-play videos. by @bmarty in https://github.com/element-hq/element-x-android/pull/3978 +* MediaViewer: iterate on design by @bmarty in https://github.com/element-hq/element-x-android/pull/3979 +* feat(crypto): Support new expected UTD causes UX + Analytics by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/3980 +* increase ringing timeout from 15 seconds to 90 seconds by @fkwp in https://github.com/element-hq/element-x-android/pull/3991 +* MediaViewer: Align title to left and move action bottom to top bar. by @bmarty in https://github.com/element-hq/element-x-android/pull/4003 + Changes in Element X v0.7.4 (2024-11-20) ======================================== From 84299fc37a9b3ef3bc01f2f6c20f8950e6641f8c Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 12:11:24 +0100 Subject: [PATCH 023/203] knock requests : start implementing banner ui --- .../knockrequests/impl/KnockRequest.kt | 14 ++ .../impl/banner/KnockRequestsBannerState.kt | 60 ++++++ .../KnockRequestsBannerStateProvider.kt | 55 +++++ .../impl/banner/KnockRequestsBannerView.kt | 204 ++++++++++++++++++ .../list/KnockRequestsListStateProvider.kt | 15 +- .../components/avatar/AvatarSize.kt | 1 + 6 files changed, 335 insertions(+), 14 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt index d9df9a5bc2..1014d13e58 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -29,3 +29,17 @@ fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( fun KnockRequest.getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } + +fun aKnockRequest( + userId: UserId = UserId("@jacob_ross:example.com"), + displayName: String? = "Jacob Ross", + avatarUrl: String? = null, + reason: String? = "Hi, I would like to get access to this room please.", + formattedDate: String = "20 Nov 2024", +) = KnockRequest( + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, + reason = reason, + formattedDate = formattedDate, +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt new file mode 100644 index 0000000000..3f993a1d40 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.getBestName +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.ui.strings.CommonPlurals +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList + +@Immutable +sealed interface KnockRequestsBannerState { + data object Hidden : KnockRequestsBannerState + data class Visible( + val knockRequests: ImmutableList, + val acceptAction: AsyncAction, + val canAccept: Boolean, + ) : KnockRequestsBannerState { + + val subtitle = if (knockRequests.size == 1) { + knockRequests.first().userId.value + } else { + null + } + + val reason = if (knockRequests.size == 1) { + knockRequests.first().reason + } else { + null + } + + @Composable + fun formattedTitle(): String { + return when (knockRequests.size) { + 0 -> "" + 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) + else -> { + val firstRequest = knockRequests.first() + val otherRequestsCount = knockRequests.size - 1 + pluralStringResource( + id = CommonPlurals.screen_room_multiple_knock_requests_title, + count = otherRequestsCount, + firstRequest.getBestName(), + otherRequestsCount + ) + } + } + } + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt new file mode 100644 index 0000000000..330c919c6a --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.aKnockRequest +import io.element.android.libraries.architecture.AsyncAction +import kotlinx.collections.immutable.toImmutableList + +class KnockRequestsBannerStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + KnockRequestsBannerState.Hidden, + aVisibleKnockRequestsBannerState(), + aVisibleKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest(), + aKnockRequest(displayName = "Alice") + ) + ), + aVisibleKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest(), + aKnockRequest(displayName = "Alice"), + aKnockRequest(displayName = "Bob"), + aKnockRequest(displayName = "Charlie") + ) + ), + aVisibleKnockRequestsBannerState( + canAccept = false + ), + aVisibleKnockRequestsBannerState( + acceptAction = AsyncAction.Loading + ), + aVisibleKnockRequestsBannerState( + acceptAction = AsyncAction.Failure(Throwable()) + ), + ) +} + +fun aVisibleKnockRequestsBannerState( + knockRequests: List = listOf(aKnockRequest()), + acceptAction: AsyncAction = AsyncAction.Uninitialized, + canAccept: Boolean = true, +) = KnockRequestsBannerState.Visible( + knockRequests = knockRequests.toImmutableList(), + acceptAction = acceptAction, + canAccept = canAccept +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt new file mode 100644 index 0000000000..a8fc985b7d --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.getAvatarData +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.OutlinedButton +import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList + +private const val MAX_AVATAR_COUNT = 3 + +@Composable +fun KnockRequestsBannerView( + state: KnockRequestsBannerState, + onDismissClick: () -> Unit, + onViewRequestsClick: () -> Unit, + modifier: Modifier = Modifier, +) { + when (state) { + is KnockRequestsBannerState.Hidden -> Unit + is KnockRequestsBannerState.Visible -> VisibleKnockRequestsBannerView( + state = state, + onDismissClick = onDismissClick, + onViewRequestsClick = onViewRequestsClick, + modifier = modifier + ) + } +} + +@Composable +private fun VisibleKnockRequestsBannerView( + state: KnockRequestsBannerState.Visible, + onDismissClick: () -> Unit, + onViewRequestsClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Surface( + modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.small, + color = ElementTheme.colors.bgCanvasDefault, + shadowElevation = 24.dp + ) { + Column( + Modifier + .fillMaxWidth() + .padding(all = 16.dp) + ) { + Row { + KnockRequestAvatarView(state.knockRequests) + Spacer(modifier = Modifier.width(10.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = state.formattedTitle(), + style = ElementTheme.typography.fontBodyMdMedium, + color = MaterialTheme.colorScheme.primary, + textAlign = TextAlign.Start, + ) + if (state.subtitle != null) { + Text( + text = state.subtitle, + style = ElementTheme.typography.fontBodySmRegular, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Start, + ) + } + } + Icon( + modifier = Modifier.clickable(onClick = onDismissClick), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_close) + ) + } + if (state.reason != null) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = state.reason, + color = ElementTheme.colors.textPrimary, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { + if (state.knockRequests.size > 1) { + Button( + text = "View all", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } else { + OutlinedButton( + text = "View", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + if (state.canAccept) { + Button( + text = "Accept", + onClick = {}, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + } + } + } + } +} + +@Composable +private fun KnockRequestAvatarView( + knockRequests: ImmutableList, + modifier: Modifier = Modifier, +) { + Box(modifier) { + when (knockRequests.size) { + 0 -> Unit + 1 -> Avatar(knockRequests.first().getAvatarData(AvatarSize.KnockRequestBanner)) + else -> KnockRequestAvatarListView(knockRequests) + } + } +} + +@Composable +private fun KnockRequestAvatarListView( + knockRequests: ImmutableList, + modifier: Modifier = Modifier, +) { + val avatarSize = AvatarSize.KnockRequestBanner.dp + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(-avatarSize / 2), + ) { + knockRequests + .take(MAX_AVATAR_COUNT) + .forEachIndexed { index, knockRequest -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(size = avatarSize) + .clip(CircleShape) + .background(color = ElementTheme.colors.bgCanvasDefault) + .zIndex(-index.toFloat()), + ) { + Avatar( + modifier = Modifier.padding(2.dp), + avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), + ) + } + } + } +} + +@Composable +@PreviewsDayNight +internal fun KnockRequestsBannerViewPreview(@PreviewParameter(KnockRequestsBannerStateProvider::class) state: KnockRequestsBannerState) = ElementPreview { + KnockRequestsBannerView( + state = state, + onDismissClick = {}, + onViewRequestsClick = {}, + modifier = Modifier.padding(16.dp) + ) +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 551f6b89b0..476bf556e1 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.aKnockRequest import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId @@ -101,20 +102,6 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), currentAction: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index c5bd468abe..3f7d087f41 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -56,4 +56,5 @@ enum class AvatarSize(val dp: Dp) { Suggestion(32.dp), KnockRequestItem(52.dp), + KnockRequestBanner(32.dp), } From 4f018f4906558d6964a25cd39d7015cafbe04d28 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 6 Dec 2024 12:34:45 +0000 Subject: [PATCH 024/203] Update screenshots --- .../libraries.designsystem.components_BigIcon_Day_0_en.png | 4 ++-- .../libraries.designsystem.components_BigIcon_Night_0_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png index 6fbfd7ed82..0d92b4ca6d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:560f267082af4e27cc5d36cb4265f67adbc0d6010827c7ad07ac50fae9cfdf68 -size 10640 +oid sha256:6bed157ced6cb695c6c92f15bc8b31b124fb943f86db580c9ab2db33d423b731 +size 12408 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png index 9c13143905..36f2db136e 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f18608dd1a83235c28f17c4ed66ebbb35f9692c400e5f0b7a284d33d57199e2f -size 10892 +oid sha256:211c37c03a828d65c2d28622ebb7eee49d39941f80aca809698561c55ae12135 +size 12521 From e744ff00ba5bcb74a04d3dc3f2f3d8a38cd370b3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 13:49:14 +0100 Subject: [PATCH 025/203] knock requests : update icon --- .../features/knockrequests/impl/list/KnockRequestsListView.kt | 2 +- .../android/features/roomdetails/impl/RoomDetailsView.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index ca289eeef4..41b5553438 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -383,7 +383,7 @@ private fun KnockRequestsEmptyList( IconTitleSubtitleMolecule( title = stringResource(R.string.screen_knock_requests_list_empty_state_title), subTitle = stringResource(R.string.screen_knock_requests_list_empty_state_description), - iconStyle = BigIcon.Style.Default(CompoundIcons.Pin()), + iconStyle = BigIcon.Style.Default(CompoundIcons.AskToJoin()), ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index d77cd9e6ea..d73b8e2626 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -242,7 +242,7 @@ fun RoomDetailsView( private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: () -> Unit) { ListItem( headlineContent = { Text(stringResource(R.string.screen_room_details_requests_to_join_title)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.AskToJoin())), trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) { null } else { From 4cf0fdb00ee15745b1feae389d724ce257a868fa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2024 13:59:31 +0100 Subject: [PATCH 026/203] Update doc and rename param. --- .../android/libraries/designsystem/components/BigIcon.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt index 468c1c903d..b0497620aa 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt @@ -51,11 +51,12 @@ object BigIcon { * * @param vectorIcon the [ImageVector] to display * @param contentDescription the content description of the icon, if any. It defaults to `null` + * @param useCriticalTint whether the icon and background should be rendered using critical tint */ data class Default( val vectorIcon: ImageVector, val contentDescription: String? = null, - val destructive: Boolean = false, + val useCriticalTint: Boolean = false, ) : Style /** @@ -91,7 +92,7 @@ object BigIcon { modifier: Modifier = Modifier, ) { val backgroundColor = when (style) { - is Style.Default -> if (style.destructive) { + is Style.Default -> if (style.useCriticalTint) { ElementTheme.colors.bgCriticalSubtle } else { ElementTheme.colors.bgSubtleSecondary @@ -116,7 +117,7 @@ object BigIcon { Style.SuccessSolid -> stringResource(CommonStrings.common_success) } val iconTint = when (style) { - is Style.Default -> if (style.destructive) { + is Style.Default -> if (style.useCriticalTint) { ElementTheme.colors.iconCriticalPrimary } else { ElementTheme.colors.iconSecondary @@ -170,7 +171,7 @@ internal class BigIconStyleProvider : PreviewParameterProvider { BigIcon.Style.Default(Icons.Filled.CatchingPokemon), BigIcon.Style.Alert, BigIcon.Style.AlertSolid, - BigIcon.Style.Default(Icons.Filled.CatchingPokemon, destructive = true), + BigIcon.Style.Default(Icons.Filled.CatchingPokemon, useCriticalTint = true), BigIcon.Style.Success, BigIcon.Style.SuccessSolid ) From 4b16ec63783b1b9b6c1809cb3ea429daa23f9ab3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 16:31:24 +0100 Subject: [PATCH 027/203] knock requests : branch banner in room --- features/knockrequests/api/build.gradle.kts | 2 +- .../api/banner/KnockRequestsBannerRenderer.kt | 16 ++++++++++ .../DefaultKnockRequestsBannerRenderer.kt | 32 +++++++++++++++++++ .../banner/KnockRequestsBannerPresenter.kt | 19 +++++++++++ features/messages/impl/build.gradle.kts | 1 + .../messages/impl/MessagesFlowNode.kt | 12 +++++++ .../features/messages/impl/MessagesNode.kt | 13 ++++++++ .../features/messages/impl/MessagesView.kt | 7 ++++ .../MessagesViewWithIdentityChangePreview.kt | 2 ++ .../messages/impl/MessagesViewTest.kt | 1 + 10 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts index 90bcb6e568..c3eca7567c 100644 --- a/features/knockrequests/api/build.gradle.kts +++ b/features/knockrequests/api/build.gradle.kts @@ -6,7 +6,7 @@ */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt new file mode 100644 index 0000000000..86483aee70 --- /dev/null +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.api.banner + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +interface KnockRequestsBannerRenderer { + @Composable + fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt new file mode 100644 index 0000000000..f77dd1fa91 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer +import io.element.android.libraries.di.RoomScope +import javax.inject.Inject + +@ContributesBinding(RoomScope::class) +class DefaultKnockRequestsBannerRenderer @Inject constructor( + private val presenter: KnockRequestsBannerPresenter, +): KnockRequestsBannerRenderer { + + @Composable + override fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) { + val state = presenter.present() + KnockRequestsBannerView( + state = state, + onDismissClick = {}, + onViewRequestsClick = onViewRequestsClick, + ) + } + +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt new file mode 100644 index 0000000000..3c6d0a4066 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class KnockRequestsBannerPresenter @Inject constructor(): Presenter { + @Composable + override fun present(): KnockRequestsBannerState { + return KnockRequestsBannerState.Hidden + } +} diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 824e8c1692..e87d90cbdf 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.zoomableimage) implementation(libs.matrix.emojibase.bindings) + implementation(projects.features.knockrequests.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 8cbb7b6d74..27b219fc1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -26,6 +26,7 @@ import im.vector.app.features.analytics.plan.Interaction import io.element.android.anvilannotations.ContributesNode import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.location.api.Location import io.element.android.features.location.api.SendLocationEntryPoint import io.element.android.features.location.api.ShowLocationEntryPoint @@ -95,6 +96,7 @@ class MessagesFlowNode @AssistedInject constructor( private val mentionSpanTheme: MentionSpanTheme, private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider, private val timelineController: TimelineController, + private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialTarget.toNavTarget(), @@ -146,6 +148,9 @@ class MessagesFlowNode @AssistedInject constructor( @Parcelize data object PinnedMessagesList : NavTarget + + @Parcelize + data object KnockRequestsList : NavTarget } private val callbacks = plugins() @@ -226,6 +231,10 @@ class MessagesFlowNode @AssistedInject constructor( override fun onViewAllPinnedEvents() { backstack.push(NavTarget.PinnedMessagesList) } + + override fun onViewKnockRequests() { + backstack.push(NavTarget.KnockRequestsList) + } } val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId) createNode(buildContext, listOf(callback, inputs)) @@ -326,6 +335,9 @@ class MessagesFlowNode @AssistedInject constructor( NavTarget.Empty -> { node(buildContext) {} } + NavTarget.KnockRequestsList -> { + knockRequestsListEntryPoint.createNode(this, buildContext) + } } } 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 7d5bad4d63..0658309e52 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 @@ -28,6 +28,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.compound.theme.ElementTheme +import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.attachments.Attachment @@ -71,6 +72,7 @@ class MessagesNode @AssistedInject constructor( private val timelineItemPresenterFactories: TimelineItemPresenterFactories, private val mediaPlayer: MediaPlayer, private val permalinkParser: PermalinkParser, + private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer ) : Node(buildContext, plugins = plugins), MessagesNavigator { private val presenter = presenterFactory.create( navigator = this, @@ -98,6 +100,7 @@ class MessagesNode @AssistedInject constructor( fun onEditPollClick(eventId: EventId) fun onJoinCallClick(roomId: RoomId) fun onViewAllPinnedEvents() + fun onViewKnockRequests() } override fun onBuilt() { @@ -206,6 +209,10 @@ class MessagesNode @AssistedInject constructor( callbacks.forEach { it.onJoinCallClick(room.roomId) } } + private fun onViewKnockRequestsClick() { + callbacks.forEach { it.onViewKnockRequests() } + } + @Composable override fun View(modifier: Modifier) { val activity = LocalContext.current as Activity @@ -231,6 +238,12 @@ class MessagesNode @AssistedInject constructor( onCreatePollClick = this::onCreatePollClick, onJoinCallClick = this::onJoinCallClick, onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick, + knockRequestsBanner = { modifier -> + knockRequestsBannerRenderer.View( + modifier = modifier, + onViewRequestsClick = this::onViewKnockRequestsClick + ) + }, modifier = modifier, ) 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 47e4721f7f..28ebc2a7b4 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 @@ -118,6 +118,7 @@ fun MessagesView( onViewAllPinnedMessagesClick: () -> Unit, modifier: Modifier = Modifier, forceJumpToBottomVisibility: Boolean = false, + knockRequestsBanner: @Composable (Modifier) -> Unit, ) { OnLifecycleEvent { _, event -> state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.LifecycleEvent(event)) @@ -215,6 +216,7 @@ fun MessagesView( forceJumpToBottomVisibility = forceJumpToBottomVisibility, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, + knockRequestsBanner = knockRequestsBanner, ) }, snackbarHost = { @@ -284,6 +286,7 @@ private fun MessagesViewContent( forceJumpToBottomVisibility: Boolean, onSwipeToReply: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, + knockRequestsBanner: @Composable (Modifier) -> Unit, ) { Box( modifier = modifier @@ -372,6 +375,9 @@ private fun MessagesViewContent( onViewAllClick = onViewAllPinnedMessagesClick, ) } + Box(modifier = Modifier.padding(all = 16.dp)) { + knockRequestsBanner(Modifier) + } } }, sheetContent = { subcomposing: Boolean -> @@ -539,5 +545,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) onJoinCallClick = {}, onViewAllPinnedMessagesClick = { }, forceJumpToBottomVisibility = true, + knockRequestsBanner = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt index 34c58bdb06..a659ce3c15 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt @@ -40,5 +40,7 @@ internal fun MessagesViewWithIdentityChangePreview( onCreatePollClick = {}, onJoinCallClick = {}, onViewAllPinnedMessagesClick = {}, + knockRequestsBanner = {} + ) } 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 1d4e1a43b3..dc68f76ee6 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 @@ -533,6 +533,7 @@ private fun AndroidComposeTestRule.setMessa onCreatePollClick = onCreatePollClick, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, + knockRequestsBanner = {} ) } } From 350a9c0464c15bcd7564c1fedd94c2906b7a8dde Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 16:31:37 +0100 Subject: [PATCH 028/203] knock requests : change banner background --- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index a8fc985b7d..8cbb93e202 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -77,7 +77,7 @@ private fun VisibleKnockRequestsBannerView( Surface( modifier.fillMaxWidth(), shape = MaterialTheme.shapes.small, - color = ElementTheme.colors.bgCanvasDefault, + color = ElementTheme.colors.bgCanvasDefaultLevel1, shadowElevation = 24.dp ) { Column( @@ -180,7 +180,7 @@ private fun KnockRequestAvatarListView( modifier = Modifier .size(size = avatarSize) .clip(CircleShape) - .background(color = ElementTheme.colors.bgCanvasDefault) + .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) .zIndex(-index.toFloat()), ) { Avatar( From 603deb7b7616dadf0b117e643372e89197076276 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 17:14:59 +0100 Subject: [PATCH 029/203] knock requests : refine and clean banner --- .../DefaultKnockRequestsBannerRenderer.kt | 5 +- .../impl/banner/KnockRequestsBannerEvents.kt | 15 ++ .../banner/KnockRequestsBannerPresenter.kt | 27 ++- .../impl/banner/KnockRequestsBannerState.kt | 66 ++++---- .../KnockRequestsBannerStateProvider.kt | 34 ++-- .../impl/banner/KnockRequestsBannerView.kt | 160 +++++++++--------- .../features/messages/impl/MessagesNode.kt | 4 +- .../features/messages/impl/MessagesView.kt | 46 +++-- .../MessagesViewWithIdentityChangePreview.kt | 3 +- .../messages/impl/MessagesViewTest.kt | 2 +- 10 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt index f77dd1fa91..9fce2f2173 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt @@ -17,16 +17,13 @@ import javax.inject.Inject @ContributesBinding(RoomScope::class) class DefaultKnockRequestsBannerRenderer @Inject constructor( private val presenter: KnockRequestsBannerPresenter, -): KnockRequestsBannerRenderer { - +) : KnockRequestsBannerRenderer { @Composable override fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) { val state = presenter.present() KnockRequestsBannerView( state = state, - onDismissClick = {}, onViewRequestsClick = onViewRequestsClick, ) } - } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt new file mode 100644 index 0000000000..28938a2c26 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import io.element.android.features.knockrequests.impl.KnockRequest + +sealed interface KnockRequestsBannerEvents { + data class Accept(val knockRequest: KnockRequest) : KnockRequestsBannerEvents + data object Dismiss : KnockRequestsBannerEvents +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index 3c6d0a4066..513042fc37 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -8,12 +8,35 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject -class KnockRequestsBannerPresenter @Inject constructor(): Presenter { +class KnockRequestsBannerPresenter @Inject constructor() : Presenter { @Composable override fun present(): KnockRequestsBannerState { - return KnockRequestsBannerState.Hidden + var shouldShowBanner by remember { mutableStateOf(false) } + + fun handleEvents(event: KnockRequestsBannerEvents) { + when (event) { + is KnockRequestsBannerEvents.Accept -> Unit + is KnockRequestsBannerEvents.Dismiss -> { + shouldShowBanner = false + } + } + } + + return KnockRequestsBannerState( + knockRequests = persistentListOf(), + acceptAction = AsyncAction.Uninitialized, + canAccept = false, + isVisible = shouldShowBanner, + eventSink = ::handleEvents, + ) } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 3f993a1d40..4d73ec8748 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -8,7 +8,6 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import io.element.android.features.knockrequests.impl.KnockRequest @@ -18,42 +17,39 @@ import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList -@Immutable -sealed interface KnockRequestsBannerState { - data object Hidden : KnockRequestsBannerState - data class Visible( - val knockRequests: ImmutableList, - val acceptAction: AsyncAction, - val canAccept: Boolean, - ) : KnockRequestsBannerState { - - val subtitle = if (knockRequests.size == 1) { - knockRequests.first().userId.value - } else { - null - } +data class KnockRequestsBannerState( + val isVisible: Boolean, + val knockRequests: ImmutableList, + val acceptAction: AsyncAction, + val canAccept: Boolean, + val eventSink: (KnockRequestsBannerEvents) -> Unit, +) { + val subtitle = if (knockRequests.size == 1) { + knockRequests.first().userId.value + } else { + null + } - val reason = if (knockRequests.size == 1) { - knockRequests.first().reason - } else { - null - } + val reason = if (knockRequests.size == 1) { + knockRequests.first().reason + } else { + null + } - @Composable - fun formattedTitle(): String { - return when (knockRequests.size) { - 0 -> "" - 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) - else -> { - val firstRequest = knockRequests.first() - val otherRequestsCount = knockRequests.size - 1 - pluralStringResource( - id = CommonPlurals.screen_room_multiple_knock_requests_title, - count = otherRequestsCount, - firstRequest.getBestName(), - otherRequestsCount - ) - } + @Composable + fun formattedTitle(): String { + return when (knockRequests.size) { + 0 -> "" + 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) + else -> { + val firstRequest = knockRequests.first() + val otherRequestsCount = knockRequests.size - 1 + pluralStringResource( + id = CommonPlurals.screen_room_multiple_knock_requests_title, + count = otherRequestsCount, + firstRequest.getBestName(), + otherRequestsCount + ) } } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt index 330c919c6a..87295d94ee 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -16,15 +16,23 @@ import kotlinx.collections.immutable.toImmutableList class KnockRequestsBannerStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - KnockRequestsBannerState.Hidden, - aVisibleKnockRequestsBannerState(), - aVisibleKnockRequestsBannerState( + aKnockRequestsBannerState(), + aKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest( + reason = "A very long reason that should probably be truncated, " + + "but could be also expanded so you can see it over the lines, wow," + + "very amazing reason, I know, right, I'm so good at writing reasons." + ) + ) + ), + aKnockRequestsBannerState( knockRequests = listOf( aKnockRequest(), aKnockRequest(displayName = "Alice") ) ), - aVisibleKnockRequestsBannerState( + aKnockRequestsBannerState( knockRequests = listOf( aKnockRequest(), aKnockRequest(displayName = "Alice"), @@ -32,24 +40,28 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider = listOf(aKnockRequest()), acceptAction: AsyncAction = AsyncAction.Uninitialized, canAccept: Boolean = true, -) = KnockRequestsBannerState.Visible( + isVisible: Boolean = true, + eventSink: (KnockRequestsBannerEvents) -> Unit = {} +) = KnockRequestsBannerState( knockRequests = knockRequests.toImmutableList(), acceptAction = acceptAction, - canAccept = canAccept + canAccept = canAccept, + isVisible = isVisible, + eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 8cbb93e202..024efd2985 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -7,6 +7,9 @@ package io.element.android.features.knockrequests.impl.banner +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -27,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex @@ -52,96 +56,102 @@ private const val MAX_AVATAR_COUNT = 3 @Composable fun KnockRequestsBannerView( state: KnockRequestsBannerState, - onDismissClick: () -> Unit, onViewRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { - when (state) { - is KnockRequestsBannerState.Hidden -> Unit - is KnockRequestsBannerState.Visible -> VisibleKnockRequestsBannerView( - state = state, - onDismissClick = onDismissClick, - onViewRequestsClick = onViewRequestsClick, - modifier = modifier - ) + AnimatedVisibility( + visible = state.isVisible, + enter = expandVertically(), + exit = shrinkVertically(), + modifier = modifier, + ) { + Surface( + shape = MaterialTheme.shapes.small, + color = ElementTheme.colors.bgCanvasDefaultLevel1, + shadowElevation = 24.dp, + modifier = Modifier.padding(16.dp), + ) { + KnockRequestsBannerContent( + state = state, + onViewRequestsClick = onViewRequestsClick, + ) + } } } @Composable -private fun VisibleKnockRequestsBannerView( - state: KnockRequestsBannerState.Visible, - onDismissClick: () -> Unit, +private fun KnockRequestsBannerContent( + state: KnockRequestsBannerState, onViewRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { - Surface( - modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.small, - color = ElementTheme.colors.bgCanvasDefaultLevel1, - shadowElevation = 24.dp + fun onDismissClick() { + state.eventSink(KnockRequestsBannerEvents.Dismiss) + } + + Column( + modifier + .fillMaxWidth() + .padding(all = 16.dp) ) { - Column( - Modifier - .fillMaxWidth() - .padding(all = 16.dp) - ) { - Row { - KnockRequestAvatarView(state.knockRequests) - Spacer(modifier = Modifier.width(10.dp)) - Column(modifier = Modifier.weight(1f)) { + Row { + KnockRequestAvatarView(state.knockRequests) + Spacer(modifier = Modifier.width(10.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = state.formattedTitle(), + style = ElementTheme.typography.fontBodyMdMedium, + color = MaterialTheme.colorScheme.primary, + textAlign = TextAlign.Start, + ) + if (state.subtitle != null) { Text( - text = state.formattedTitle(), - style = ElementTheme.typography.fontBodyMdMedium, - color = MaterialTheme.colorScheme.primary, + text = state.subtitle, + style = ElementTheme.typography.fontBodySmRegular, + color = MaterialTheme.colorScheme.secondary, textAlign = TextAlign.Start, ) - if (state.subtitle != null) { - Text( - text = state.subtitle, - style = ElementTheme.typography.fontBodySmRegular, - color = MaterialTheme.colorScheme.secondary, - textAlign = TextAlign.Start, - ) - } } - Icon( - modifier = Modifier.clickable(onClick = onDismissClick), - imageVector = CompoundIcons.Close(), - contentDescription = stringResource(CommonStrings.action_close) - ) - } - if (state.reason != null) { - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = state.reason, - color = ElementTheme.colors.textPrimary, - style = ElementTheme.typography.fontBodyMdRegular, - ) } + Icon( + modifier = Modifier.clickable(onClick = ::onDismissClick), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_close) + ) + } + if (state.reason != null) { Spacer(modifier = Modifier.height(16.dp)) - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { - if (state.knockRequests.size > 1) { + Text( + text = state.reason, + color = ElementTheme.colors.textPrimary, + style = ElementTheme.typography.fontBodyMdRegular, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { + if (state.knockRequests.size > 1) { + Button( + text = "View all", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } else { + OutlinedButton( + text = "View", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + if (state.canAccept) { Button( - text = "View all", - onClick = onViewRequestsClick, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), - ) - } else { - OutlinedButton( - text = "View", - onClick = onViewRequestsClick, + text = "Accept", + onClick = {}, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), ) - if (state.canAccept) { - Button( - text = "Accept", - onClick = {}, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), - ) - } } } } @@ -178,11 +188,11 @@ private fun KnockRequestAvatarListView( Box( contentAlignment = Alignment.Center, modifier = Modifier - .size(size = avatarSize) - .clip(CircleShape) - .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) - .zIndex(-index.toFloat()), - ) { + .size(size = avatarSize) + .clip(CircleShape) + .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) + .zIndex(-index.toFloat()), + ) { Avatar( modifier = Modifier.padding(2.dp), avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), @@ -197,8 +207,6 @@ private fun KnockRequestAvatarListView( internal fun KnockRequestsBannerViewPreview(@PreviewParameter(KnockRequestsBannerStateProvider::class) state: KnockRequestsBannerState) = ElementPreview { KnockRequestsBannerView( state = state, - onDismissClick = {}, onViewRequestsClick = {}, - modifier = Modifier.padding(16.dp) ) } 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 0658309e52..4ee44fbc80 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 @@ -238,9 +238,9 @@ class MessagesNode @AssistedInject constructor( onCreatePollClick = this::onCreatePollClick, onJoinCallClick = this::onJoinCallClick, onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick, - knockRequestsBanner = { modifier -> + knockRequestsBannerView = { knockRequestsBannerRenderer.View( - modifier = modifier, + modifier = Modifier, onViewRequestsClick = this::onViewKnockRequestsClick ) }, 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 28ebc2a7b4..c0af0088c1 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 @@ -118,7 +118,7 @@ fun MessagesView( onViewAllPinnedMessagesClick: () -> Unit, modifier: Modifier = Modifier, forceJumpToBottomVisibility: Boolean = false, - knockRequestsBanner: @Composable (Modifier) -> Unit, + knockRequestsBannerView: @Composable () -> Unit, ) { OnLifecycleEvent { _, event -> state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.LifecycleEvent(event)) @@ -196,8 +196,8 @@ fun MessagesView( MessagesViewContent( state = state, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding), + .padding(padding) + .consumeWindowInsets(padding), onContentClick = ::onContentClick, onMessageLongClick = ::onMessageLongClick, onUserDataClick = { hidingKeyboard { onUserDataClick(it) } }, @@ -216,7 +216,7 @@ fun MessagesView( forceJumpToBottomVisibility = forceJumpToBottomVisibility, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, - knockRequestsBanner = knockRequestsBanner, + knockRequestsBannerView = knockRequestsBannerView, ) }, snackbarHost = { @@ -286,13 +286,13 @@ private fun MessagesViewContent( forceJumpToBottomVisibility: Boolean, onSwipeToReply: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, - knockRequestsBanner: @Composable (Modifier) -> Unit, + knockRequestsBannerView: @Composable () -> Unit, ) { Box( modifier = modifier - .fillMaxSize() - .navigationBarsPadding() - .imePadding(), + .fillMaxSize() + .navigationBarsPadding() + .imePadding(), ) { AttachmentsBottomSheet( state = state.composerState, @@ -375,9 +375,7 @@ private fun MessagesViewContent( onViewAllClick = onViewAllPinnedMessagesClick, ) } - Box(modifier = Modifier.padding(all = 16.dp)) { - knockRequestsBanner(Modifier) - } + knockRequestsBannerView() } }, sheetContent = { subcomposing: Boolean -> @@ -404,13 +402,13 @@ private fun MessagesViewComposerBottomSheetContents( Column(modifier = Modifier.fillMaxWidth()) { SuggestionsPickerView( modifier = Modifier - .heightIn(max = 230.dp) - // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions - .nestedScroll(object : NestedScrollConnection { - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return available - } - }), + .heightIn(max = 230.dp) + // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions + .nestedScroll(object : NestedScrollConnection { + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return available + } + }), roomId = state.roomId, roomName = state.roomName.dataOrNull(), roomAvatarData = state.roomAvatar.dataOrNull(), @@ -458,8 +456,8 @@ private fun MessagesViewTopBar( title = { val roundedCornerShape = RoundedCornerShape(8.dp) val titleModifier = Modifier - .clip(roundedCornerShape) - .clickable { onRoomDetailsClick() } + .clip(roundedCornerShape) + .clickable { onRoomDetailsClick() } if (roomName != null && roomAvatar != null) { RoomAvatarAndNameRow( roomName = roomName, @@ -514,9 +512,9 @@ private fun RoomAvatarAndNameRow( private fun CantSendMessageBanner() { Row( modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.secondary) - .padding(16.dp), + .fillMaxWidth() + .background(MaterialTheme.colorScheme.secondary) + .padding(16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { @@ -545,6 +543,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) onJoinCallClick = {}, onViewAllPinnedMessagesClick = { }, forceJumpToBottomVisibility = true, - knockRequestsBanner = {}, + knockRequestsBannerView = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt index a659ce3c15..5dc55b0dc4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt @@ -40,7 +40,6 @@ internal fun MessagesViewWithIdentityChangePreview( onCreatePollClick = {}, onJoinCallClick = {}, onViewAllPinnedMessagesClick = {}, - knockRequestsBanner = {} - + knockRequestsBannerView = {} ) } 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 dc68f76ee6..b15f358828 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 @@ -533,7 +533,7 @@ private fun AndroidComposeTestRule.setMessa onCreatePollClick = onCreatePollClick, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, - knockRequestsBanner = {} + knockRequestsBannerView = {} ) } } From 7957973b5f6e682ff3b62ef71f184d0ba9737b93 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 6 Dec 2024 17:07:09 +0000 Subject: [PATCH 030/203] Update screenshots --- ...krequests.impl.banner_KnockRequestsBannerView_Day_0_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_1_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_2_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_3_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_4_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_5_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_6_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_0_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_1_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_2_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_3_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_4_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_5_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_6_en.png | 3 +++ ...knockrequests.impl.list_KnockRequestsListView_Day_1_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_1_en.png | 4 ++-- ...es.designsystem.components.avatar_Avatar_Avatars_81_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_82_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_83_en.png | 3 +++ 19 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png new file mode 100644 index 0000000000..3b63b61f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 +size 29335 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png new file mode 100644 index 0000000000..1708abde01 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0f508d82473c7b1dc9da20d25808843c3e1bfcac632ac2bf33151cc7626bf35 +size 34821 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png new file mode 100644 index 0000000000..d5a84e03df --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5262c97044361ab44066a8876b8417853c8ef5c1449390242c0248c06a0fc568 +size 17855 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png new file mode 100644 index 0000000000..c148a2032f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f247767c5f965cf75f1c7556f49bd5e3c7207b2effd9cd3d19a913581556842 +size 18744 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png new file mode 100644 index 0000000000..b606b20db0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78f259c410ad179c3f93b18fc7c10829a355e5a0f56b0dd4c1f4b4efc72910f0 +size 27309 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png new file mode 100644 index 0000000000..3b63b61f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 +size 29335 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png new file mode 100644 index 0000000000..3b63b61f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 +size 29335 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png new file mode 100644 index 0000000000..7b685bcbcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 +size 27357 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png new file mode 100644 index 0000000000..6d822fab84 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77bc7bd3f4dab9e05ce7f68ddc50d06da03d414c33ce8e361bb83ccec38ad3c8 +size 32231 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png new file mode 100644 index 0000000000..9c7829d9fe --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cff0d41eb0ab572946867cb9ea57fcab1fb72b155273fc68b12855c100cdb0c0 +size 15974 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png new file mode 100644 index 0000000000..34c0b0c9ff --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fcbb3f101b864cd0a5eeade5d43a76e42573705a90604706be696a810e7096c +size 17021 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png new file mode 100644 index 0000000000..06ad640021 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f89ebacc5f45844b3606a214a4c0df8ca150003de50bd7a04680d85ddea2ee15 +size 25224 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png new file mode 100644 index 0000000000..7b685bcbcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 +size 27357 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png new file mode 100644 index 0000000000..7b685bcbcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 +size 27357 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png index 66c6102947..2548547d60 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9595edf8183796a0a0efa838a3c5265e07f70452933e6255d79f536cfca64ac -size 26138 +oid sha256:a67bb9ba78ea1d8802ce503e7d6a16be2ff46415df0e435efd7a36f3ee711b0d +size 26453 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png index 055bfccd93..613fccf45c 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37e24295a404463010f6d36ab94b1ac3c2e193d9122637abbeb0a43abcb427c1 -size 25669 +oid sha256:be2ae50ce1b0e23fec7bcbbe9ca0f0381af4d0f29f1e7498be837d9d53b75542 +size 26003 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png new file mode 100644 index 0000000000..3c5b96d217 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5abc97134f8a5f0d037367c9278d8f007543463e13c8e043241f292949681ff6 +size 17728 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png new file mode 100644 index 0000000000..1100737467 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:972653df5c62859c175a62d7809193afd0cb68832e3567760082f1b164e3424b +size 16990 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png new file mode 100644 index 0000000000..72c687d439 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:491cb82462df122b0e8d21c5b70e8db8ac19c28aaee1289db6f4774e7d31d53f +size 19556 From 0d7259af7f6c490e3e279c184cb83ad81f66ec3c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 23:07:21 +0000 Subject: [PATCH 031/203] Update dependency com.lemonappdev:konsist to v0.17.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 748a62bd88..daaa4aaf38 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -150,7 +150,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.6.2" test_mockk = "io.mockk:mockk:1.13.13" -test_konsist = "com.lemonappdev:konsist:0.17.1" +test_konsist = "com.lemonappdev:konsist:0.17.3" test_turbine = "app.cash.turbine:turbine:1.2.0" test_truth = "com.google.truth:truth:1.4.4" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.18" From e8218b3a7c6d4ef8156fbabc11b973b1f45eb302 Mon Sep 17 00:00:00 2001 From: bmarty <3940906+bmarty@users.noreply.github.com> Date: Mon, 9 Dec 2024 00:30:45 +0000 Subject: [PATCH 032/203] Sync Strings from Localazy --- .../src/main/res/values-hu/translations.xml | 2 + .../src/main/res/values-it/translations.xml | 19 +- .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-fr/translations.xml | 4 +- .../src/main/res/values-it/translations.xml | 7 + .../src/main/res/values-cs/translations.xml | 17 + .../src/main/res/values-de/translations.xml | 17 + .../src/main/res/values-el/translations.xml | 17 + .../src/main/res/values-et/translations.xml | 17 + .../src/main/res/values-fr/translations.xml | 17 + .../src/main/res/values-hu/translations.xml | 17 + .../src/main/res/values-it/translations.xml | 17 + .../src/main/res/values-ru/translations.xml | 17 + .../src/main/res/values-sk/translations.xml | 8 + .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 1 + .../src/main/res/values-fr/translations.xml | 6 +- .../src/main/res/values-hu/translations.xml | 1 + .../src/main/res/values-it/translations.xml | 1 + .../src/main/res/values-it/translations.xml | 1 + .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-hu/translations.xml | 1 + .../src/main/res/values-it/translations.xml | 1 + .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-fr/translations.xml | 4 +- .../src/main/res/values-it/translations.xml | 2 + .../main/res/values-pt-rBR/translations.xml | 2 + .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 3 + .../src/main/res/values-de/translations.xml | 3 + .../src/main/res/values-el/translations.xml | 2 + .../src/main/res/values-et/translations.xml | 3 + .../src/main/res/values-fa/translations.xml | 1 + .../src/main/res/values-fi/translations.xml | 1 + .../src/main/res/values-fr/translations.xml | 9 +- .../src/main/res/values-hu/translations.xml | 3 + .../src/main/res/values-in/translations.xml | 1 + .../src/main/res/values-it/translations.xml | 2 + .../src/main/res/values-nl/translations.xml | 1 + .../src/main/res/values-pl/translations.xml | 1 + .../src/main/res/values-pt/translations.xml | 1 + .../src/main/res/values-ru/translations.xml | 3 + .../src/main/res/values-sk/translations.xml | 2 + .../src/main/res/values-sv/translations.xml | 1 + .../src/main/res/values-uk/translations.xml | 1 + .../src/main/res/values-zh/translations.xml | 1 + .../src/main/res/values-fr/translations.xml | 6 +- .../src/main/res/values-it/translations.xml | 7 +- .../src/main/res/values-fr/translations.xml | 14 +- .../src/main/res/values-it/translations.xml | 19 +- .../main/res/values-pt-rBR/translations.xml | 10 +- .../src/main/res/values-it/translations.xml | 2 + .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-hu/translations.xml | 4 + .../src/main/res/values-it/translations.xml | 13 + .../src/main/res/values-pl/translations.xml | 6 +- .../src/main/res/values-hu/translations.xml | 4 +- .../src/main/res/values-it/translations.xml | 4 +- .../src/main/res/values-fr/translations.xml | 4 +- .../src/main/res/values-it/translations.xml | 1 + .../src/main/res/values-be/translations.xml | 1 - .../src/main/res/values-cs/translations.xml | 21 +- .../src/main/res/values-de/translations.xml | 30 +- .../src/main/res/values-el/translations.xml | 16 - .../src/main/res/values-et/translations.xml | 26 +- .../src/main/res/values-fa/translations.xml | 1 - .../src/main/res/values-fi/translations.xml | 1 - .../src/main/res/values-fr/translations.xml | 38 +- .../src/main/res/values-hu/translations.xml | 35 +- .../src/main/res/values-in/translations.xml | 1 - .../src/main/res/values-it/translations.xml | 42 +- .../src/main/res/values-nl/translations.xml | 1 - .../src/main/res/values-pl/translations.xml | 4 +- .../src/main/res/values-pt/translations.xml | 1 - .../src/main/res/values-ru/translations.xml | 26 +- .../src/main/res/values-sk/translations.xml | 7 - .../src/main/res/values-sv/translations.xml | 1 - .../src/main/res/values-uk/translations.xml | 1 - .../src/main/res/values-zh/translations.xml | 1 - .../src/main/res/values/localazy.xml | 13 + ...pl.list_KnockRequestsListView_Day_0_de.png | 3 + ...pl.list_KnockRequestsListView_Day_1_de.png | 3 + ...pl.list_KnockRequestsListView_Day_2_de.png | 3 + ...pl.list_KnockRequestsListView_Day_3_de.png | 3 + ...pl.list_KnockRequestsListView_Day_4_de.png | 3 + ...pl.list_KnockRequestsListView_Day_5_de.png | 3 + ...pl.list_KnockRequestsListView_Day_6_de.png | 3 + ...pl.list_KnockRequestsListView_Day_7_de.png | 3 + ...pl.list_KnockRequestsListView_Day_8_de.png | 3 + ...pl.list_KnockRequestsListView_Day_9_de.png | 3 + ...tachments.preview_AttachmentsView_0_de.png | 4 +- ...tachments.preview_AttachmentsView_1_de.png | 4 +- ...tachments.preview_AttachmentsView_2_de.png | 4 +- ...tachments.preview_AttachmentsView_3_de.png | 4 +- ...tachments.preview_AttachmentsView_4_de.png | 3 - ...tachments.preview_AttachmentsView_5_de.png | 4 +- ...tachments.preview_AttachmentsView_6_de.png | 3 - ...ent_TimelineItemEncryptedView_Day_2_de.png | 4 +- ...ent_TimelineItemEncryptedView_Day_4_de.png | 4 +- ...ent_TimelineItemEncryptedView_Day_5_de.png | 3 + ...ent_TimelineItemEncryptedView_Day_6_de.png | 3 + ...ent_TimelineItemEncryptedView_Day_7_de.png | 3 + ...nents_TimelineItemEventRowUtd_Day_0_de.png | 4 +- ...es.messages.impl_MessagesView_Day_0_de.png | 4 +- ...s.messages.impl_MessagesView_Day_10_de.png | 4 +- ...s.messages.impl_MessagesView_Day_11_de.png | 4 +- ...es.messages.impl_MessagesView_Day_1_de.png | 4 +- ...es.messages.impl_MessagesView_Day_4_de.png | 4 +- ...es.messages.impl_MessagesView_Day_5_de.png | 4 +- ...es.messages.impl_MessagesView_Day_7_de.png | 4 +- ...es.messages.impl_MessagesView_Day_8_de.png | 4 +- ...es.messages.impl_MessagesView_Day_9_de.png | 4 +- ...nboarding.impl_OnBoardingView_Day_0_de.png | 4 +- ...nboarding.impl_OnBoardingView_Day_1_de.png | 4 +- ...nboarding.impl_OnBoardingView_Day_2_de.png | 4 +- ...nboarding.impl_OnBoardingView_Day_3_de.png | 4 +- ...nboarding.impl_OnBoardingView_Day_4_de.png | 4 +- ...viewer.api.viewer_MediaViewerView_2_de.png | 3 - ....local.pdf_PdfPagesErrorView_Day_0_de.png} | 0 ...iewer.impl.viewer_MediaViewerView_2_de.png | 3 + ...ser_CaptionWarningBottomSheet_Day_0_de.png | 3 + ...oser_MarkdownTextComposerEdit_Day_0_de.png | 4 +- ...mposer_TextComposerAddCaption_Day_0_de.png | 4 +- ...tcomposer_TextComposerCaption_Day_0_de.png | 4 +- ...poser_TextComposerEditCaption_Day_0_de.png | 4 +- ...textcomposer_TextComposerEdit_Day_0_de.png | 4 +- ...mposer_TextComposerFormatting_Day_0_de.png | 4 +- ...extcomposer_TextComposerReply_Day_0_de.png | 4 +- ...xtcomposer_TextComposerReply_Day_10_de.png | 4 +- ...xtcomposer_TextComposerReply_Day_11_de.png | 4 +- ...extcomposer_TextComposerReply_Day_1_de.png | 4 +- ...extcomposer_TextComposerReply_Day_2_de.png | 4 +- ...extcomposer_TextComposerReply_Day_3_de.png | 4 +- ...extcomposer_TextComposerReply_Day_4_de.png | 4 +- ...extcomposer_TextComposerReply_Day_5_de.png | 4 +- ...extcomposer_TextComposerReply_Day_6_de.png | 4 +- ...extcomposer_TextComposerReply_Day_7_de.png | 4 +- ...extcomposer_TextComposerReply_Day_8_de.png | 4 +- ...extcomposer_TextComposerReply_Day_9_de.png | 4 +- ...xtcomposer_TextComposerSimple_Day_0_de.png | 4 +- screenshots/html/data.js | 1366 +++++++++-------- 142 files changed, 1265 insertions(+), 928 deletions(-) create mode 100644 features/knockrequests/impl/src/main/res/values-cs/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-de/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-el/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-et/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-fr/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-hu/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-it/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-ru/translations.xml create mode 100644 features/knockrequests/impl/src/main/res/values-sk/translations.xml create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png create mode 100644 screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png delete mode 100644 screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png delete mode 100644 screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png create mode 100644 screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png create mode 100644 screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png create mode 100644 screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png delete mode 100644 screenshots/de/libraries.mediaviewer.api.viewer_MediaViewerView_2_de.png rename screenshots/de/{libraries.mediaviewer.api.local.pdf_PdfPagesErrorView_Day_0_de.png => libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_de.png} (100%) create mode 100644 screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png create mode 100644 screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png diff --git a/features/createroom/impl/src/main/res/values-hu/translations.xml b/features/createroom/impl/src/main/res/values-hu/translations.xml index ee935c8c60..4a905afaa5 100644 --- a/features/createroom/impl/src/main/res/values-hu/translations.xml +++ b/features/createroom/impl/src/main/res/values-hu/translations.xml @@ -13,6 +13,8 @@ Ezt bármikor módosíthatja a szobabeállításokban." "Szobahozzáférés" "Bárki kérheti, hogy csatlakozzon a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést" "Csatlakozás kérése" + "Egyes karakterek nem engedélyezettek. Csak a betűk, a számjegyek és a következő szimbólumok támogatottak: $ & \'() * +/; =? @ [] - . _" + "Ez a szobacím már létezik. Próbálja meg szerkeszteni a szobacím mezőt, vagy módosítsa a szoba nevét." "Ahhoz, hogy ez a szoba látható legyen a nyilvános szobák címtárában, meg kell adnia a szoba címét." "Szoba címe" "Szoba neve" diff --git a/features/createroom/impl/src/main/res/values-it/translations.xml b/features/createroom/impl/src/main/res/values-it/translations.xml index 205f1db36a..c60e10660b 100644 --- a/features/createroom/impl/src/main/res/values-it/translations.xml +++ b/features/createroom/impl/src/main/res/values-it/translations.xml @@ -3,11 +3,22 @@ "Nuova stanza" "Invita persone" "Si è verificato un errore durante la creazione della stanza" - "I messaggi in questa stanza sono cifrati. La crittografia non può essere disattivata in seguito." - "Stanza privata (solo su invito)" - "I messaggi non sono cifrati e chiunque può leggerli. Puoi attivare la crittografia in un secondo momento." - "Stanza pubblica (chiunque)" + "Solo le persone invitate possono accedere a questa stanza. Tutti i messaggi sono cifrati end-to-end." + "Stanza privata" + "Chiunque può trovare questa stanza. +Puoi modificarlo in qualsiasi momento nelle impostazioni della stanza." + "Stanza pubblica" + "Chiunque può entrare in questa stanza" + "Chiunque" + "Accesso alla stanza" + "Chiunque può chiedere di entrare nella stanza, ma un amministratore o un moderatore dovrà accettare la richiesta" + "Chiedi di entrare" + "Alcuni caratteri non sono consentiti. Sono supportate solo lettere, cifre e i seguenti simboli ! $ & \'() * +/; =? @ [] - . _" + "L\'indirizzo di questa stanza esiste già. Prova a modificare il campo dell\'indirizzo o a cambiare il nome della stanza" + "Affinché questa stanza sia visibile nell\'elenco delle stanze pubbliche, è necessario un indirizzo della stanza." + "Indirizzo della stanza" "Nome stanza" + "Visibilità della stanza" "Crea una stanza" "Argomento (facoltativo)" "Si è verificato un errore durante il tentativo di avviare una chat" diff --git a/features/deactivation/impl/src/main/res/values-fr/translations.xml b/features/deactivation/impl/src/main/res/values-fr/translations.xml index 3b8c5c0812..875142bc01 100644 --- a/features/deactivation/impl/src/main/res/values-fr/translations.xml +++ b/features/deactivation/impl/src/main/res/values-fr/translations.xml @@ -3,7 +3,7 @@ "Veuillez confirmer que vous souhaitez désactiver votre compte. Cette action ne peut pas être annulée." "Supprimer tous mes messages" "Attention : les futurs utilisateurs pourraient voir des conversations incomplètes." - "La désactivation de votre compte est %1$s, cela va:" + "La désactivation de votre compte est %1$s, cela va :" "irréversible" "%1$s votre compte (vous ne pourrez plus vous reconnecter et votre identifiant ne pourra pas être réutilisé)." "Désactiver définitivement" diff --git a/features/joinroom/impl/src/main/res/values-fr/translations.xml b/features/joinroom/impl/src/main/res/values-fr/translations.xml index 5e89edb1fa..c420fcfe78 100644 --- a/features/joinroom/impl/src/main/res/values-fr/translations.xml +++ b/features/joinroom/impl/src/main/res/values-fr/translations.xml @@ -2,7 +2,7 @@ "Annuler la demande" "Oui, annuler" - "Êtes-vous sûr de vouloir annuler votre demande d’accès à ce salon?" + "Êtes-vous sûr de vouloir annuler votre demande d’accès à ce salon ?" "Annuler la demande d’adhésion" "Rejoindre" "Demander à joindre" @@ -13,6 +13,6 @@ "Les Spaces ne sont pas encore pris en charge" "Cliquez ci-dessous et un administrateur sera prévenu. Une fois votre demande approuvée, pour pourrez rejoindre la discussion." "Vous devez être un membre du salon pour pouvoir lire l’historique des messages." - "Vous souhaitez rejoindre ce salon?" + "Vous souhaitez rejoindre ce salon ?" "La prévisualisation n’est pas disponible" diff --git a/features/joinroom/impl/src/main/res/values-it/translations.xml b/features/joinroom/impl/src/main/res/values-it/translations.xml index 55af91d9d7..16202e2f87 100644 --- a/features/joinroom/impl/src/main/res/values-it/translations.xml +++ b/features/joinroom/impl/src/main/res/values-it/translations.xml @@ -1,7 +1,14 @@ + "Cancella richiesta" + "Sì, annulla" + "Sei sicuro di voler annullare la tua richiesta di accesso a questa stanza?" + "Annulla la richiesta di accesso" "Entra nella stanza" "Bussa per partecipare" + "Messaggio (opzionale)" + "Riceverai un invito a entrare nella stanza se la tua richiesta viene accettata." + "Richiesta di accesso inviata" "%1$s non supporta ancora gli spazi. Puoi accedere agli spazi sul web." "Gli spazi non sono ancora supportati" "Clicca sul pulsante qui sotto e un amministratore della stanza riceverà una notifica. Potrai partecipare alla conversazione una volta approvato." diff --git a/features/knockrequests/impl/src/main/res/values-cs/translations.xml b/features/knockrequests/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..b02fdf8595 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,17 @@ + + + "Ano, přijmout všechny" + "Opravdu chcete přijmout všechny žádosti o vstup?" + "Přijmout všechny požadavky" + "Přijmout vše" + "Ano, odmítnout a vykázat" + "Opravdu chcete odmítnout a vykázat %1$s? Tento uživatel nebude moci znovu požádat o vstup do této místnosti." + "Odmítnout a zakázat vstup" + "Ano, odmítnout" + "Opravdu chcete odmítnout %1$s žádost o vstup do této místnosti?" + "Odmítnout vstup" + "Odmítnout a vykázat" + "Když někdo požádá o vstup do místnosti, uvidíte jeho žádost zde." + "Žádná čekající žádost o vstup" + "Žádosti o vstup" + diff --git a/features/knockrequests/impl/src/main/res/values-de/translations.xml b/features/knockrequests/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..693b1c8882 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,17 @@ + + + "Ja, akzeptiere alle" + "Sind Sie sicher, dass Sie alle Beitrittsanfragen akzeptieren möchten?" + "Akzeptiere alle Anfragen" + "Alle akzeptieren" + "Ja, ablehnen und sperren" + "Sind Sie sicher, dass Sie %1$s ablehnen und sperren möchten ? Dieser Benutzer kann keinen erneuten Zugriff auf diesen Raum anfordern." + "Ablehnen und Zugriff verbieten" + "Ja, ablehnen" + "Sind Sie sicher, dass Sie die %1$s Anfrage, diesem Chatroom beizutreten, ablehnen möchten ?" + "Zugriff verweigern" + "Ablehnen und sperren" + "Falls jemand um Aufnahme in den Raum bittet, können Sie dessen Anfrage hier sehen." + "Keine ausstehende Beitrittsanfrage" + "Beitrittsanfragen" + diff --git a/features/knockrequests/impl/src/main/res/values-el/translations.xml b/features/knockrequests/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..794312dd93 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,17 @@ + + + "Ναι, αποδοχή όλων" + "Σίγουρα θες να αποδεχτείς όλα τα αιτήματα συμμετοχής;" + "Αποδοχή όλων των αιτημάτων" + "Αποδοχή όλων" + "Ναι, απόρριψη και αποκλεισμός" + "Σίγουρα θες να απορρίψειε και να αποκλείσεις τον χρήστη %1$s; Αυτός ο χρήστης δεν θα μπορεί να ζητήσει πρόσβαση για να συμμετάσχει ξανά σε αυτό το δωμάτιο." + "Απόρριψη και αποκλεισμός πρόσβασης" + "Ναι, απόρριψη" + "Σίγουρα θες να απορρίψεις το αίτημα του χρήστη %1$s να συμμετάσχει στο δωμάτιο;" + "Απόρριψη πρόσβασης" + "Απόρριψη και αποκλεισμός" + "Όταν κάποιος θα ζητήσει να συμμετάσχει στο δωμάτιο, θα μπορείς να δεις το αίτημά του εδώ." + "Δεν υπάρχει εκκρεμές αίτημα συμμετοχής" + "Αιτήματα συμμετοχής" + diff --git a/features/knockrequests/impl/src/main/res/values-et/translations.xml b/features/knockrequests/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..bd647dcb09 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,17 @@ + + + "Jah, võta kõik vastu" + "Kas sa oled kindel, et soovid kõik vastu liitumist soovinud võtta?" + "Võta kõik vastu" + "Nõustu kõigiga" + "Jah, keeldu liitumisest ning keela ligipääs" + "Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa ning seada talle suhtluskeelu? Seetõttu ta ei saa ka enam hiljem liitumispalvet saata." + "Keeldu liitumisest ja keela ligipääs" + "Jah, keeldu" + "Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa?" + "Keela ligipääs" + "Keeldu ja määra suhtluskeeld" + "Kui keegi soovib jututoaga liituda, siis need päringud on kuvatud siin." + "Pole ühtegi liitumispalvet" + "Liitumispalved" + diff --git a/features/knockrequests/impl/src/main/res/values-fr/translations.xml b/features/knockrequests/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..6632df1dd3 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,17 @@ + + + "Oui, tout accepter" + "Êtes-vous sûr de vouloir accepter toutes les demandes pour rejoindre le salon ?" + "Tout accepter" + "Tout accepter" + "Oui, rejeter et bannir" + "Êtes-vous sûr de vouloir rejeter la demande et bannir %1$s ? Cet utilisateur ne pourra pas demander à nouveau à rejoindre ce salon." + "Refuser et interdire l’accès" + "Oui, refuser" + "Êtes-vous sûr de vouloir refuser la demande de %1$s à rejoindre le salon ?" + "Refuser l’accès" + "Refuser et bannir" + "Lorsque quelqu’un demandera à rejoindre le salon, vous pourrez voir sa demande ici." + "Personne ne demande à rejoindre le salon" + "Demandes en attente" + diff --git a/features/knockrequests/impl/src/main/res/values-hu/translations.xml b/features/knockrequests/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..2093d0103d --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,17 @@ + + + "Igen, az összes elfogadása" + "Biztos, hogy elfogadja az összes csatlakozási kérelmet?" + "Minden kérés elfogadása" + "Összes elfogadása" + "Igen, elutasítás és kitiltás" + "Biztos, hogy elutasítja %1$s kérését és ki is tiltja? Többé nem fogja tudni azt kérni, hogy csatlakozhasson ehhez a szobához." + "A hozzáférés elutasítása és kitiltás" + "Igen, elutasítás" + "Biztos, hogy elutasítja %1$s kérését, hogy csatlakozzon a szobához?" + "Hozzáférés elutasítása" + "Elutasítás és kitiltás" + "Ha valaki csatlakozni kíván a szobához, itt láthatja a kérését." + "Nincs függőben lévő csatlakozási kérelem" + "Csatlakozási kérelmek" + diff --git a/features/knockrequests/impl/src/main/res/values-it/translations.xml b/features/knockrequests/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..a3e05bb8ef --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,17 @@ + + + "Sì, accetta tutte" + "Sei sicuro di voler accettare tutte le richieste di accesso?" + "Accetta tutte le richieste" + "Accetta tutte" + "Sì, rifiuta e blocca" + "Sei sicuro di voler rifiutare e bloccare %1$s? Questo utente non potrà richiedere nuovamente l\'accesso per entrare in questa stanza." + "Rifiuta e blocca l\'accesso" + "Sì, rifiuta" + "Sei sicuro di voler rifiutare la richiesta di %1$s ad entrare in a questa stanza?" + "Rifiuta l\'accesso" + "Rifiuta e blocca" + "Quando qualcuno ti chiederà di entrare nella stanza, potrai vedere la sua richiesta qui." + "Nessuna richiesta di accesso in sospeso" + "Richieste di accesso" + diff --git a/features/knockrequests/impl/src/main/res/values-ru/translations.xml b/features/knockrequests/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..2542195139 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,17 @@ + + + "Да, принять все" + "Вы действительно хотите принять все заявки на присоединение?" + "Принять все запросы" + "Принять всё" + "Да, отклонить и запретить" + "Вы уверен, что хочешь отклонить и запретить %1$s? Этот пользователь больше не сможет запросить доступ к этой комнате." + "Отклонить и запретить доступ" + "Да, отклонить" + "Вы уверены, что хотите отклонить %1$s запрос на присоединение к этой комнате?" + "Отклонить доступ" + "Отклонить и запретить" + "Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате." + "Нет ожидающих запросов на присоединение" + "Запросы на присоединение" + diff --git a/features/knockrequests/impl/src/main/res/values-sk/translations.xml b/features/knockrequests/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..4dff32780a --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,8 @@ + + + "Prijať všetky" + "Odmietnuť a zakázať" + "Keď niekto požiada, aby sa pripojil k miestnosti, jeho žiadosť si môžete pozrieť tu." + "Žiadna čakajúca žiadosť o pripojenie" + "Žiadosti o pripojenie" + diff --git a/features/leaveroom/api/src/main/res/values-fr/translations.xml b/features/leaveroom/api/src/main/res/values-fr/translations.xml index bed2bdd365..2c801ed76a 100644 --- a/features/leaveroom/api/src/main/res/values-fr/translations.xml +++ b/features/leaveroom/api/src/main/res/values-fr/translations.xml @@ -1,6 +1,6 @@ - "Êtes-vous sûr de vouloir quitter cette discussion? Vous ne pourrez pas la rejoindre à nouveau sans y être invité." + "Êtes-vous sûr de vouloir quitter cette discussion ? Vous ne pourrez pas la rejoindre à nouveau sans y être invité." "Êtes-vous sûr de vouloir quitter ce salon ? Vous êtes la seule personne ici. Si vous partez, personne ne pourra rejoindre le salon à l’avenir, y compris vous." "Êtes-vous sûr de vouloir quitter ce salon ? Ce salon n’est pas public et vous ne pourrez pas le rejoindre sans invitation." "Êtes-vous sûr de vouloir quitter le salon ?" diff --git a/features/lockscreen/impl/src/main/res/values-cs/translations.xml b/features/lockscreen/impl/src/main/res/values-cs/translations.xml index b9984b1dc3..fce1142f2c 100644 --- a/features/lockscreen/impl/src/main/res/values-cs/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-cs/translations.xml @@ -3,6 +3,7 @@ "Biometrické ověřování" "biometrické odemknutí" "Odemkněte pomocí biometrie" + "Potvrďte biometrické údaje" "Zapomněli jste PIN?" "Změnit PIN kód" "Povolit biometrické odemykání" diff --git a/features/lockscreen/impl/src/main/res/values-fr/translations.xml b/features/lockscreen/impl/src/main/res/values-fr/translations.xml index b77df23254..8596647aac 100644 --- a/features/lockscreen/impl/src/main/res/values-fr/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-fr/translations.xml @@ -4,12 +4,12 @@ "déverrouillage biométrique" "Déverrouiller avec la biométrie" "Confirmer la biométrie" - "Code PIN oublié?" + "Code PIN oublié ?" "Modifier le code PIN" "Autoriser le déverrouillage biométrique" "Supprimer le code PIN" - "Êtes-vous certain de vouloir supprimer le code PIN?" - "Supprimer le code PIN?" + "Êtes-vous certain de vouloir supprimer le code PIN ?" + "Supprimer le code PIN ?" "Autoriser %1$s" "Je préfère utiliser le code PIN" "Gagnez du temps en utilisant %1$s pour déverrouiller l’application à chaque fois." diff --git a/features/lockscreen/impl/src/main/res/values-hu/translations.xml b/features/lockscreen/impl/src/main/res/values-hu/translations.xml index 2df43b3fae..3a65239697 100644 --- a/features/lockscreen/impl/src/main/res/values-hu/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-hu/translations.xml @@ -3,6 +3,7 @@ "biometrikus hitelesítés" "biometrikus feloldás" "Feloldás biometrikus adatokkal" + "Biometrikus megerősítés" "Elfelejtette a PIN-kódot?" "PIN-kód módosítása" "Biometrikus feloldás engedélyezése" diff --git a/features/lockscreen/impl/src/main/res/values-it/translations.xml b/features/lockscreen/impl/src/main/res/values-it/translations.xml index cdb48f8f63..5f6fa68ff6 100644 --- a/features/lockscreen/impl/src/main/res/values-it/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-it/translations.xml @@ -3,6 +3,7 @@ "autenticazione biometrica" "sblocco con biometria" "Sblocca con biometria" + "Conferma la biometria" "PIN dimenticato?" "Modifica il codice PIN" "Consenti lo sblocco biometrico" diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml index 2b8071fd0b..5b82829b80 100644 --- a/features/login/impl/src/main/res/values-it/translations.xml +++ b/features/login/impl/src/main/res/values-it/translations.xml @@ -60,6 +60,7 @@ Prova ad accedere manualmente o scansiona il codice QR con un altro dispositivo. "Seleziona %1$s" "\"Collega un nuovo dispositivo\"" "Scansiona il codice QR con questo dispositivo" + "Disponibile solo se il provider del tuo account lo supporta." "Apri %1$s su un altro dispositivo per ottenere il codice QR" "Usa il codice QR mostrato sull\'altro dispositivo." "Riprova" diff --git a/features/logout/impl/src/main/res/values-fr/translations.xml b/features/logout/impl/src/main/res/values-fr/translations.xml index c21194989f..5e8f9d468d 100644 --- a/features/logout/impl/src/main/res/values-fr/translations.xml +++ b/features/logout/impl/src/main/res/values-fr/translations.xml @@ -14,5 +14,5 @@ "Vous êtes sur le point de vous déconnecter de votre dernier appareil. Si vous le faites maintenant, vous perdrez l’accès à l’historique de vos messages." "La récupération n’est pas configurée." "Vous êtes sur le point de vous déconnecter de votre dernière session. Si vous le faites maintenant, vous perdrez l’accès à l’historique de vos discussions chiffrées." - "Avez-vous sauvegardé votre clé de récupération?" + "Avez-vous sauvegardé votre clé de récupération ?" diff --git a/features/messages/impl/src/main/res/values-hu/translations.xml b/features/messages/impl/src/main/res/values-hu/translations.xml index 4c2a66334f..6fce42474c 100644 --- a/features/messages/impl/src/main/res/values-hu/translations.xml +++ b/features/messages/impl/src/main/res/values-hu/translations.xml @@ -31,6 +31,7 @@ "Emodzsi hozzáadása" "Ez a(z) %1$s kezdete." "Ez a beszélgetés kezdete." + "Nem támogatott hívás. Kérdezze meg, hogy a hívó fél tudja-e használni az új Element X alkalmazást." "Kevesebb megjelenítése" "Üzenet másolva" "Nincs jogosultsága arra, hogy bejegyzést tegyen közzé ebben a szobában" diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml index c1acba9be8..229a02b714 100644 --- a/features/messages/impl/src/main/res/values-it/translations.xml +++ b/features/messages/impl/src/main/res/values-it/translations.xml @@ -31,6 +31,7 @@ "Aggiungi emoji" "Questo è l\'inizio di %1$s." "Questo è l\'inizio della conversazione." + "Chiamata non supportata. Chiedi se il chiamante può utilizzare la nuova app Element X." "Mostra meno" "Messaggio copiato" "Non sei autorizzato a postare in questa stanza" diff --git a/features/onboarding/impl/src/main/res/values-de/translations.xml b/features/onboarding/impl/src/main/res/values-de/translations.xml index 56edc949a1..bd5e9c0c54 100644 --- a/features/onboarding/impl/src/main/res/values-de/translations.xml +++ b/features/onboarding/impl/src/main/res/values-de/translations.xml @@ -5,5 +5,5 @@ "Konto erstellen" "Willkommen beim schnellsten %1$s aller Zeiten. Optimiert für Geschwindigkeit und Einfachheit." "Willkommen zu %1$s. Aufgeladen, für Geschwindigkeit und Einfachheit." - "Sei in deinem Element" + "Sei in Deinem Element" diff --git a/features/poll/impl/src/main/res/values-fr/translations.xml b/features/poll/impl/src/main/res/values-fr/translations.xml index 081b5e53b1..8a0c8ea5d5 100644 --- a/features/poll/impl/src/main/res/values-fr/translations.xml +++ b/features/poll/impl/src/main/res/values-fr/translations.xml @@ -4,11 +4,11 @@ "Afficher les résultats uniquement après la fin du sondage" "Masquer les votes" "Option %1$d" - "Vos modifications n’ont pas été enregistrées. Êtes-vous certain de vouloir quitter?" + "Vos modifications n’ont pas été enregistrées. Êtes-vous certain de vouloir quitter ?" "Question ou sujet" "Quel est le sujet du sondage ?" "Créer un sondage" - "Êtes-vous certain de vouloir supprimer ce sondage?" + "Êtes-vous certain de vouloir supprimer ce sondage ?" "Supprimer le sondage" "Modifier le sondage" "Impossible de trouver des sondages en cours." diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml index 5acfeff05b..e47167e32b 100644 --- a/features/preferences/impl/src/main/res/values-it/translations.xml +++ b/features/preferences/impl/src/main/res/values-it/translations.xml @@ -8,6 +8,8 @@ "URL base di Element Call personalizzato" "Imposta un URL di base personalizzato per Element Call." "URL non valido, assicurati di includere il protocollo (http/https) e l\'indirizzo corretto." + "Carica foto e video più velocemente e riduci l\'utilizzo dei dati" + "Ottimizza la qualità dei contenuti multimediali" "Fornitore di notifiche push" "Disattiva l\'editor di testo avanzato per scrivere manualmente in Markdown" "Ricevute di visualizzazione" diff --git a/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml index 6255c7c2c9..42ac543aee 100644 --- a/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml @@ -3,6 +3,8 @@ "Escolha como receber notificações" "Modo de desenvolvedor" "Habilite para ter acesso a recursos e funcionalidades para desenvolvedores." + "URL base do Element Call personalizado" + "Defina um URL base personalizado para Element Call." "URL inválida, por favor verifique se o protocolo (http/https) e o endereço correto estão presentes." "Desative o editor de rich text para digitar Markdown manualmente." "Confirmações de leitura" diff --git a/features/rageshake/api/src/main/res/values-fr/translations.xml b/features/rageshake/api/src/main/res/values-fr/translations.xml index e21171120e..a2d1391280 100644 --- a/features/rageshake/api/src/main/res/values-fr/translations.xml +++ b/features/rageshake/api/src/main/res/values-fr/translations.xml @@ -1,7 +1,7 @@ "%1$s s’est arrêté la dernière fois qu’il a été utilisé. Souhaitez-vous partager un rapport d’incident avec nous ?" - "Vous semblez secouez votre téléphone avec frustration. Souhaitez-vous ouvrir le formulaire pour reporter un problème?" + "Vous semblez secouez votre téléphone avec frustration. Souhaitez-vous ouvrir le formulaire pour reporter un problème ?" "Rageshake" "Seuil de détection" diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index 7cbf1fcd6d..6ba7e59a2e 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -52,6 +52,7 @@ "Уласныя" "Стандартныя" "Апавяшчэнні" + "Замацаваныя паведамленні" "Ролі і дазволы" "Назва пакоя" "Бяспека" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index 09003dc610..65218dda97 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -49,9 +49,12 @@ "Pozvat přátele" "Opustit konverzaci" "Opustit místnost" + "Média a soubory" "Vlastní" "Výchozí" "Oznámení" + "Připnuté zprávy" + "Žádosti o vstup" "Role a oprávnění" "Název místnosti" "Zabezpečení" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index f3bb984173..643ed52d98 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -49,9 +49,12 @@ "Personen einladen" "Unterhaltung verlassen" "Verlassen" + "Medien und Dateien" "Benutzerdefiniert" "Standard" "Benachrichtigungen" + "Fixierte Nachrichten" + "Beitrittsanfragen" "Rollen und Berechtigungen" "Raumname" "Sicherheit" diff --git a/features/roomdetails/impl/src/main/res/values-el/translations.xml b/features/roomdetails/impl/src/main/res/values-el/translations.xml index 57874ff30b..23d2705afa 100644 --- a/features/roomdetails/impl/src/main/res/values-el/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-el/translations.xml @@ -52,6 +52,8 @@ "Προσαρμοσμένο" "Προεπιλογή" "Ειδοποιήσεις" + "Καρφιτσωμένα μηνύματα" + "Αιτήματα συμμετοχής" "Ρόλοι και δικαιώματα" "Όνομα δωματίου" "Ασφάλεια" diff --git a/features/roomdetails/impl/src/main/res/values-et/translations.xml b/features/roomdetails/impl/src/main/res/values-et/translations.xml index a8c197ce7d..7ff262314f 100644 --- a/features/roomdetails/impl/src/main/res/values-et/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-et/translations.xml @@ -49,9 +49,12 @@ "Kutsu osalejaid" "Lahku vestlusest" "Lahku jututoast" + "Meedia ja failid" "Kohandatud" "Vaikimisi" "Teavitused" + "Esiletõstetud sõnumid" + "Liitumispalved" "Rollid ja õigused" "Jututoa nimi" "Turvalisus" diff --git a/features/roomdetails/impl/src/main/res/values-fa/translations.xml b/features/roomdetails/impl/src/main/res/values-fa/translations.xml index ca25c83461..491435d52d 100644 --- a/features/roomdetails/impl/src/main/res/values-fa/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fa/translations.xml @@ -48,6 +48,7 @@ "سفارشی" "پیش‌گزیده" "آگاهی‌ها" + "پیام‌های سنجاق شده" "نقش‌ها و اجازه‌ها" "نام اتاق" "امنیت" diff --git a/features/roomdetails/impl/src/main/res/values-fi/translations.xml b/features/roomdetails/impl/src/main/res/values-fi/translations.xml index db8b78d779..6a397173f3 100644 --- a/features/roomdetails/impl/src/main/res/values-fi/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fi/translations.xml @@ -52,6 +52,7 @@ "Mukautettu" "Oletus" "Ilmoitukset" + "Kiinnitetyt viestit" "Roolit ja oikeudet" "Huoneen nimi" "Turvallisuus" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index 5766d1e0f1..4a28b4b563 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -31,7 +31,7 @@ "Modérateurs" "Membres" "Vous avez des modifications non-enregistrées." - "Enregistrer les changements?" + "Enregistrer les changements ?" "Ajouter un sujet" "Déjà membre" "Déjà invité(e)" @@ -49,9 +49,12 @@ "Inviter des amis" "Quitter la discussion" "Quitter le salon" + "Médias et fichiers" "Personnalisé" "Défaut" "Notifications" + "Messages épinglés" + "Demandes en attente" "Rôles et autorisations" "Nom du salon" "Sécurité" @@ -61,7 +64,7 @@ "Mise à jour du salon…" "Bannir" "L‘utilisateur ne pourra pas rejoindre le salon à nouveau, même si il est invité." - "Êtes-vous certain de vouloir bannir ce membre?" + "Êtes-vous certain de vouloir bannir ce membre ?" "Il n’y a pas d’utilisateur banni dans ce salon." "Bannissement de %1$s" @@ -109,7 +112,7 @@ "Autorisations" "Réinitialisation des autorisations" "La réinitialisation des autorisations entraîne la perte des réglages actuels." - "Réinitialisation des autorisations?" + "Réinitialisation des autorisations ?" "Rôles" "Détails du salon" "Rôles et autorisations" diff --git a/features/roomdetails/impl/src/main/res/values-hu/translations.xml b/features/roomdetails/impl/src/main/res/values-hu/translations.xml index 8f992b1c7d..b88519c880 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -49,9 +49,12 @@ "Ismerősök meghívása" "Beszélgetés elhagyása" "Szoba elhagyása" + "Média és fájlok" "Egyéni" "Alapértelmezett" "Értesítések" + "Kitűzött üzenetek" + "Csatlakozási kérelem" "Szerepkörök és jogosultságok" "Szoba neve" "Biztonság" diff --git a/features/roomdetails/impl/src/main/res/values-in/translations.xml b/features/roomdetails/impl/src/main/res/values-in/translations.xml index cd3ba93146..730adaa293 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -52,6 +52,7 @@ "Khusus" "Bawaan" "Notifikasi" + "Pesan yang disematkan" "Peran dan perizinan" "Nama ruangan" "Keamanan" diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index fe752f2874..eb48791320 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -52,6 +52,8 @@ "Personalizzato" "Predefinito" "Notifiche" + "Messaggi fissati" + "Richieste di accesso" "Ruoli e autorizzazioni" "Nome stanza" "Sicurezza" diff --git a/features/roomdetails/impl/src/main/res/values-nl/translations.xml b/features/roomdetails/impl/src/main/res/values-nl/translations.xml index a3a4dc965a..6c677ea68e 100644 --- a/features/roomdetails/impl/src/main/res/values-nl/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-nl/translations.xml @@ -52,6 +52,7 @@ "Aangepast" "Standaard" "Meldingen" + "Vastgezette berichten" "Rollen en rechten" "Naam van de kamer" "Beveiliging" diff --git a/features/roomdetails/impl/src/main/res/values-pl/translations.xml b/features/roomdetails/impl/src/main/res/values-pl/translations.xml index 80195a14ea..aabbadf3fe 100644 --- a/features/roomdetails/impl/src/main/res/values-pl/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pl/translations.xml @@ -52,6 +52,7 @@ "Niestandardowy" "Domyślny" "Powiadomienia" + "Przypięte wiadomości" "Role i uprawnienia" "Nazwa pokoju" "Bezpieczeństwo" diff --git a/features/roomdetails/impl/src/main/res/values-pt/translations.xml b/features/roomdetails/impl/src/main/res/values-pt/translations.xml index 5dde97ec98..294c0e4a76 100644 --- a/features/roomdetails/impl/src/main/res/values-pt/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pt/translations.xml @@ -52,6 +52,7 @@ "Personalizado" "Predefinição" "Notificações" + "Mensagens afixadas" "Cargos e permissões" "Nome da sala" "Segurança" diff --git a/features/roomdetails/impl/src/main/res/values-ru/translations.xml b/features/roomdetails/impl/src/main/res/values-ru/translations.xml index 0ec4c6fd80..42e8fb0562 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -49,9 +49,12 @@ "Пригласить в комнату" "Покинуть беседу" "Покинуть комнату" + "Медиа и файлы" "Пользовательский" "По умолчанию" "Уведомления" + "Закрепленные сообщения" + "Запросы на вступление" "Роли и разрешения" "Название комнаты" "Безопасность" diff --git a/features/roomdetails/impl/src/main/res/values-sk/translations.xml b/features/roomdetails/impl/src/main/res/values-sk/translations.xml index c905333c2c..4b2d289b35 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -52,6 +52,8 @@ "Vlastné" "Predvolené" "Oznámenia" + "Pripnuté správy" + "Žiadosti o vstup" "Roly a povolenia" "Názov miestnosti" "Bezpečnosť" diff --git a/features/roomdetails/impl/src/main/res/values-sv/translations.xml b/features/roomdetails/impl/src/main/res/values-sv/translations.xml index eed1cfcbb1..7f20d978d4 100644 --- a/features/roomdetails/impl/src/main/res/values-sv/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sv/translations.xml @@ -52,6 +52,7 @@ "Anpassad" "Förval" "Aviseringar" + "Fästa meddelanden" "Roller och behörigheter" "Rumsnamn" "Säkerhet" diff --git a/features/roomdetails/impl/src/main/res/values-uk/translations.xml b/features/roomdetails/impl/src/main/res/values-uk/translations.xml index 616f293554..49a7f147cf 100644 --- a/features/roomdetails/impl/src/main/res/values-uk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-uk/translations.xml @@ -52,6 +52,7 @@ "Власні" "Типово" "Сповіщення" + "Закріплені повідомлення" "Ролі та дозволи" "Назва кімнати" "Безпека" diff --git a/features/roomdetails/impl/src/main/res/values-zh/translations.xml b/features/roomdetails/impl/src/main/res/values-zh/translations.xml index a2f6e26e9b..1198de917c 100644 --- a/features/roomdetails/impl/src/main/res/values-zh/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-zh/translations.xml @@ -52,6 +52,7 @@ "自定义" "默认" "通知" + "置顶消息" "角色与权限" "聊天室名称" "安全" diff --git a/features/roomlist/impl/src/main/res/values-fr/translations.xml b/features/roomlist/impl/src/main/res/values-fr/translations.xml index 315efb3478..04fb371eeb 100644 --- a/features/roomlist/impl/src/main/res/values-fr/translations.xml +++ b/features/roomlist/impl/src/main/res/values-fr/translations.xml @@ -9,7 +9,7 @@ "Configurer la récupération" "Confirmez votre clé de récupération pour conserver l’accès à votre stockage de clés et à l’historique des messages." "Saisissez votre clé de récupération" - "Clé de récupération oubliée?" + "Clé de récupération oubliée ?" "Le stockage de vos clés n’est pas synchronisé" "Afin de ne jamais manquer un appel important, veuillez modifier vos paramètres pour autoriser les notifications en plein écran lorsque votre appareil est verrouillé." "Améliorez votre expérience d’appel" @@ -39,8 +39,8 @@ En attendant, vous pouvez désélectionner des filtres pour voir vos autres salo "Salons" "Vous n’êtes membre d’aucun salon" "Non-lus" - "Félicitations! -Vous n’avez plus de messages non-lus!" + "Félicitations ! +Vous n’avez plus de messages non-lus !" "Conversations" "Marquer comme lu" "Marquer comme non lu" diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml index af305d4b7d..cd248d7372 100644 --- a/features/roomlist/impl/src/main/res/values-it/translations.xml +++ b/features/roomlist/impl/src/main/res/values-it/translations.xml @@ -7,8 +7,10 @@ "Genera una nuova chiave di recupero che può essere usata per ripristinare la cronologia dei messaggi crittografati nel caso in cui tu perda l\'accesso ai tuoi dispositivi." "Configura il recupero" "Configura il ripristino" - "Il backup della chat non è attualmente sincronizzato. Devi confermare la chiave di recupero per mantenere l\'accesso al backup della chat." - "Inserisci la chiave di recupero" + "Conferma la chiave di recupero per mantenere l\'accesso all\'archiviazione delle chiavi e alla cronologia dei messaggi." + "Inserisci la tua chiave di recupero" + "Hai dimenticato la chiave di recupero?" + "L\'archiviazione delle chiavi non è sincronizzata" "Per non perdere mai una chiamata importante, modifica le impostazioni per consentire le notifiche a schermo intero quando il telefono è bloccato." "Migliora la tua esperienza di chiamata" "Vuoi davvero rifiutare l\'invito ad entrare in %1$s?" @@ -17,6 +19,7 @@ "Rifiuta l\'invito alla conversazione" "Nessun invito" "%1$s (%2$s) ti ha invitato" + "Richiesta di accesso inviata" "Si tratta di una procedura che si effettua una sola volta, grazie per l\'attesa." "Configurazione del tuo account." "Crea una nuova conversazione o stanza" diff --git a/features/securebackup/impl/src/main/res/values-fr/translations.xml b/features/securebackup/impl/src/main/res/values-fr/translations.xml index 867d43b7e7..8684fac185 100644 --- a/features/securebackup/impl/src/main/res/values-fr/translations.xml +++ b/features/securebackup/impl/src/main/res/values-fr/translations.xml @@ -28,23 +28,23 @@ "Vous ne pouvez pas confirmer ? Vous devez réinitialiser votre identité." "Désactiver" "Vous perdrez vos messages chiffrés si vous vous déconnectez de toutes vos sessions." - "Êtes-vous certain de vouloir désactiver la sauvegarde?" - "Désactiver la sauvegarde supprimera votre clé de récupération actuelle et désactivera d’autres mesures de sécurité. Dans ce cas:" + "Êtes-vous certain de vouloir désactiver la sauvegarde ?" + "Désactiver la sauvegarde supprimera votre clé de récupération actuelle et désactivera d’autres mesures de sécurité. Dans ce cas :" "Pas d’accès à l’historique des discussions chiffrées sur vos nouveaux appareils" "Perte de l’accès à vos messages chiffrés si vous êtes déconnectés de %1$s partout" - "Êtes-vous certain de vouloir désactiver la sauvegarde?" + "Êtes-vous certain de vouloir désactiver la sauvegarde ?" "Obtenez une nouvelle clé de récupération dans le cas où vous avez oublié l’ancienne. Après le changement, l’ancienne clé ne sera plus utilisable." "Générer une nouvelle clé" "Ne partagez cela avec personne !" "Clé de récupération modifée" - "Changer la clé de récupération?" + "Changer la clé de récupération ?" "Créer une nouvelle clé de récupération" - "Assurez vous que personne d’autre ne regarde votre écran!" + "Assurez vous que personne d’autre ne regarde votre écran !" "Veuillez réessayer pour confirmer l’accès à votre stockage de clés." "Clé de récupération incorrecte" "Si vous avez une clé de sécurité ou une phrase de sécurité, cela fonctionnera également." "Saisissez la clé ici…" - "Clé de récupération perdue?" + "Clé de récupération perdue ?" "Clé de récupération confirmée" "Saisissez votre clé de récupération" "Clé de récupération copiée" @@ -54,7 +54,7 @@ "Taper pour copier la clé" "Sauvegarder la clé" "La clé ne pourra plus être affichée après cette étape." - "Avez-vous sauvegardé votre clé de récupération?" + "Avez-vous sauvegardé votre clé de récupération ?" "Votre sauvegarde est protégée par votre clé de récupération. Si vous avez besoin d’une nouvelle clé après la configuration, vous pourrez en créer une nouvelle en cliquant sur \"Changer la clé de récupération\"." "Générer la clé de récupération" "Ne partagez cela avec personne !" diff --git a/features/securebackup/impl/src/main/res/values-it/translations.xml b/features/securebackup/impl/src/main/res/values-it/translations.xml index d6b629ea93..e9b8849a92 100644 --- a/features/securebackup/impl/src/main/res/values-it/translations.xml +++ b/features/securebackup/impl/src/main/res/values-it/translations.xml @@ -2,11 +2,15 @@ "Disattiva il backup" "Attiva il backup" - "Il backup ti garantisce di non perdere la cronologia dei messaggi. %1$s." - "Backup" + "Archivia la tua identità crittografica e le chiavi dei messaggi in modo sicuro sul server. Ciò ti consentirà di visualizzare la cronologia dei messaggi su tutti i nuovi dispositivi. %1$s." + "Archiviazione chiavi" + "L\'archiviazione delle chiavi deve essere attivata per configurare il ripristino." + "Carica le chiavi da questo dispositivo" + "Consenti l\'archiviazione delle chiavi" "Cambia la chiave di recupero" + "Recupera la tua identità crittografica e la cronologia dei messaggi con una chiave di recupero se hai perso tutti i dispositivi esistenti." "Inserisci la chiave di recupero" - "Il backup delle conversazioni non è attualmente sincronizzato." + "L\'archiviazione delle chiavi non è sincronizzata." "Configura il recupero" "Ottieni l\'accesso ai tuoi messaggi cifrati se perdi tutti i tuoi dispositivi o se sei disconnesso da %1$s ovunque." "Apri %1$s in un dispositivo desktop" @@ -31,28 +35,29 @@ "Vuoi davvero disattivare il backup?" "Ottieni una nuova chiave di recupero se hai perso quella esistente. Dopo averla cambiata, quella vecchia non funzionerà più." "Genera una nuova chiave di recupero" - "Assicurati di conservare la chiave di recupero in un posto sicuro" + "Non condividerla con nessuno!" "Chiave di recupero cambiata" "Cambiare la chiave di recupero?" "Crea una nuova chiave di recupero" "Assicurati che nessuno possa vedere questa schermata!" - "Riprova per confermare l\'accesso al backup della chat." + "Riprova per confermare l\'accesso all\'archivio delle chiavi." "Chiave di recupero errata" "Se hai una chiave di sicurezza o una password, andrà bene anche questo." "Inserisci…" "Hai perso la chiave di recupero?" "Chiave di recupero confermata" + "Inserisci la tua chiave di recupero" "Chiave di recupero copiata" "Generazione…" "Salva la chiave di recupero" - "Annota la chiave di recupero in un posto sicuro o salvala in un gestore di password." + "Annota questa chiave di recupero in un posto sicuro, come un gestore di password, una nota cifrata o una cassaforte fisica." "Tocca per copiare la chiave di recupero" "Salva la tua chiave di recupero" "Dopo questo passaggio non potrai accedere alla nuova chiave di recupero." "Hai salvato la chiave di recupero?" "Il backup della chat è protetto da una chiave di recupero. Se hai bisogno di una nuova chiave di recupero dopo la configurazione, puoi ricrearla selezionando \"Cambia chiave di recupero\"." "Genera la tua chiave di recupero" - "Assicurati di conservare la chiave di recupero in un posto sicuro" + "Non condividerla con nessuno!" "Configurazione del recupero completata" "Configura il recupero" "Sì, reimposta ora" diff --git a/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml b/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml index c78c7ad85a..eed0e7f576 100644 --- a/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml @@ -2,11 +2,11 @@ "Desativar o backup" "Ativar o backup" - "O backup garante que você não perca seu histórico de mensagens. %1$s." + "Armazene sua identidade criptográfica e chaves de mensagem com segurança no servidor. Isso permitirá que você visualize seu histórico de mensagens em quaisquer novos dispositivos.%1$s ." "Armazenamento de chaves" "Alterar chave de recuperação" "Insira a chave de recuperação" - "Seu backup das conversas está atualmente fora de sincronia." + "Seu armazenamento de chaves está fora de sincronia no momento." "Configurar a recuperação" "Tenha acesso às suas mensagens criptografadas se você perder todos os seus dispositivos ou for desconectado do %1$s em qualquer lugar." "Desligar" @@ -18,7 +18,7 @@ "Tem certeza de que deseja desativar o backup?" "Obtenha uma nova chave de recuperação caso tenha perdido a existente. Depois de alterar sua chave de recuperação, a antiga não funcionará mais." "Gere uma nova chave de recuperação" - "Certifique-se de que você pode armazenar sua chave de recuperação em algum lugar seguro" + "Não compartilhe isso com ninguém!" "Chave de recuperação alterada" "Alterar chave de recuperação?" "Certifique-se de que ninguém possa ver essa tela!" @@ -29,14 +29,14 @@ "Chave de recuperação copiada" "Gerando…" "Salvar chave de recuperação" - "Anote sua chave de recuperação em algum lugar seguro ou salve-a em um gerenciador de senhas." + "Anote essa chave de recuperação em algum lugar seguro, como um gerenciador de senhas, uma nota criptografada ou um cofre físico." "Toque para copiar a chave de recuperação" "Salve sua chave de recuperação" "Você não poderá acessar sua nova chave de recuperação após essa etapa." "Você salvou sua chave de recuperação?" "Seu backup das conversas é protegido por uma chave de recuperação. Se precisar de uma nova chave de recuperação após a configuração, você pode recriá-la selecionando “Alterar chave de recuperação”." "Gere sua chave de recuperação" - "Certifique-se de que você pode armazenar sua chave de recuperação em algum lugar seguro" + "Não compartilhe isso com ninguém!" "Configuração de recuperação bem-sucedida" "Configurar a recuperação" "Inserir…" diff --git a/features/userprofile/shared/src/main/res/values-it/translations.xml b/features/userprofile/shared/src/main/res/values-it/translations.xml index daacd44255..277ed7e27e 100644 --- a/features/userprofile/shared/src/main/res/values-it/translations.xml +++ b/features/userprofile/shared/src/main/res/values-it/translations.xml @@ -13,5 +13,7 @@ "Sblocca" "Potrai vedere di nuovo tutti i suoi messaggi." "Sblocca utente" + "Usa l\'app web per verificare questo utente." + "Verifica %1$s" "Si è verificato un errore durante il tentativo di avviare una chat" diff --git a/features/verifysession/impl/src/main/res/values-fr/translations.xml b/features/verifysession/impl/src/main/res/values-fr/translations.xml index a057a73a65..bd3f194e4b 100644 --- a/features/verifysession/impl/src/main/res/values-fr/translations.xml +++ b/features/verifysession/impl/src/main/res/values-fr/translations.xml @@ -22,7 +22,7 @@ "Ouvrir une session existante" "Réessayer la vérification" "Je suis prêt.e" - "En attente de correspondance" + "En attente de correspondance…" "Comparer un groupe unique d’Emojis." "Comparez les emoji uniques en veillant à ce qu’ils apparaissent dans le même ordre." "Connecté" diff --git a/features/verifysession/impl/src/main/res/values-hu/translations.xml b/features/verifysession/impl/src/main/res/values-hu/translations.xml index 2f25c0006e..7e786dfc6c 100644 --- a/features/verifysession/impl/src/main/res/values-hu/translations.xml +++ b/features/verifysession/impl/src/main/res/values-hu/translations.xml @@ -35,6 +35,10 @@ "Ellenőrzés kérve" "Nem egyeznek" "Megegyeznek" + "Győződjön meg róla, hogy az alkalmazás nyitva van a másik eszközön, mielőtt innen elindítja az ellenőrzést." + "Nyissa meg az alkalmazást egy másik ellenőrzött eszközön" + "A másik eszközön egy felugró ablaknak kell megjelennie. Kezdje el az ellenőrzést onnan." + "Ellenőrzés megkezdése a másik eszközön" "A folytatáshoz fogadja el az ellenőrzési folyamat indítási kérését a másik munkamenetében." "Várakozás a kérés elfogadására" "Kijelentkezés…" diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml index c1154a8c89..322ab0e6d3 100644 --- a/features/verifysession/impl/src/main/res/values-it/translations.xml +++ b/features/verifysession/impl/src/main/res/values-it/translations.xml @@ -17,6 +17,7 @@ "Confronta i numeri" "La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile." "Inserisci la chiave di recupero" + "La richiesta è scaduta, è stata rifiutata o c\'è stata una mancata corrispondenza nella verifica." "Dimostra la tua identità per accedere alla cronologia dei messaggi crittografati." "Apri una sessione esistente" "Riprova la verifica" @@ -24,8 +25,20 @@ "In attesa di un riscontro" "Confronta un set unico di emoji." "Confronta le emoji uniche, assicurandoti che appaiano nello stesso ordine." + "Accesso effettuato" + "La richiesta è scaduta, è stata rifiutata o c\'è stata una mancata corrispondenza nella verifica." + "Verifica fallita" + "Continua solo se tu hai avviato questa verifica." + "Verifica l\'altro dispositivo per proteggere la cronologia dei messaggi." + "Ora puoi leggere o inviare messaggi in modo sicuro sull\'altro dispositivo." + "Dispositivo verificato" + "Richiesta di verifica" "Non corrispondono" "Corrispondono" + "Assicurati di avere l\'app aperta sull\'altro dispositivo prima di iniziare la verifica da qui." + "Apri l\'app su un altro dispositivo verificato" + "Dovresti vedere un popup sull\'altro dispositivo. Inizia subito la verifica da lì." + "Avvia la verifica sull\'altro dispositivo" "Accetta la richiesta di avviare il processo di verifica nell\'altra sessione per continuare." "In attesa di accettare la richiesta" "Disconnessione in corso…" diff --git a/features/verifysession/impl/src/main/res/values-pl/translations.xml b/features/verifysession/impl/src/main/res/values-pl/translations.xml index 46c24d253e..caa5471b96 100644 --- a/features/verifysession/impl/src/main/res/values-pl/translations.xml +++ b/features/verifysession/impl/src/main/res/values-pl/translations.xml @@ -12,7 +12,7 @@ "Oczekiwanie na inne urządzenie…" "Coś tu nie gra. Albo upłynął limit czasu, albo żądanie zostało odrzucone." "Upewnij się, że emoji poniżej pasują do tych pokazanych na innej sesji." - "Porównaj emotki" + "Porównaj emoji" "Upewnij się, że liczby poniżej pasują do tych wyświetlanych na innej sesji." "Porównaj liczby" "Twoja nowa sesja jest teraz zweryfikowana. Ma ona dostęp do Twoich zaszyfrowanych wiadomości, a inni użytkownicy będą widzieć ją jako zaufaną." @@ -24,7 +24,7 @@ "Jestem gotowy(a)" "Oczekiwanie na dopasowanie" "Porównaj unikalny zestaw emoji." - "Porównaj unikalne emoji, upewniając się, że pojawiły się w tej samej kolejności." + "Porównaj unikalny zestaw emoji i upewnij się, że są w tej samej kolejności." "Zalogowano" "Albo upłynął limit czasu żądania, albo żądanie zostało odrzucone, albo wystąpił błąd weryfikacji." "Weryfikacja nie powiodła się" @@ -36,6 +36,6 @@ "Nie pasują do siebie" "Pasują do siebie" "Zaakceptuj prośbę o rozpoczęcie procesu weryfikacji w innej sesji, aby kontynuować." - "Oczekiwanie na zaakceptowanie żądania" + "Oczekiwanie na zaakceptowanie prośby" "Wylogowywanie…" diff --git a/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml b/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml index 879e21c466..bfa63c86c3 100644 --- a/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml @@ -28,8 +28,8 @@ "%1$s meghívta" "%1$s csatlakozott a szobához" "Csatlakozott a szobához" - "%1$s kérte, hogy csatlakozhasson" - "%1$s engedélyezte, hogy %2$s csatlakozhasson" + "%1$s kéri, hogy csatlakozhasson" + "%1$s hozzáférést kapott a következőhöz: %2$s" "Engedélyezte, hogy %1$s csatlakozhasson" "Kérte, hogy csatlakozhasson" "%1$s elutasította %2$s kérését, hogy csatlakozhasson" diff --git a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml index 02e1cebef0..1b7709dcb4 100644 --- a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml @@ -28,8 +28,8 @@ "%1$s ti ha invitato" "%1$s si è unito alla stanza" "Ti sei unito alla stanza" - "%1$s ha chiesto di unirsi" - "%1$s ha permesso a %2$s di unirsi" + "%1$s ha richiesto di entrare" + "%1$s ha permesso a %2$s di entrare" "Hai permesso a %1$s di partecipare" "Hai richiesto di unirti" "%1$s ha rifiutato la richiesta di unirsi di %2$s" diff --git a/libraries/push/impl/src/main/res/values-fr/translations.xml b/libraries/push/impl/src/main/res/values-fr/translations.xml index eceefa879c..eb386ed459 100644 --- a/libraries/push/impl/src/main/res/values-fr/translations.xml +++ b/libraries/push/impl/src/main/res/values-fr/translations.xml @@ -24,7 +24,7 @@ "Vous a invité(e) à discuter" "%1$s vous a invité à discuter" - "Mentionné(e): %1$s" + "Mentionné(e) : %1$s" "Nouveaux messages" "%d nouveau message" @@ -68,7 +68,7 @@ "Vérifier que l’application peut afficher des notifications." "Vous n’avez pas cliqué sur la notification." "Impossible d’afficher la notification." - "Vous avez cliqué sur la notification!" + "Vous avez cliqué sur la notification !" "Affichage des notifications" "Veuillez cliquer sur la notification pour continuer le test." "Vérifier que l’application reçoit les Push." diff --git a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml index 4db0f2cb95..04e45678eb 100644 --- a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml @@ -4,6 +4,7 @@ "Attiva/disattiva l\'elenco puntato" "Chiudi le opzioni di formattazione" "Attiva/disattiva il blocco di codice" + "Aggiungi una didascalia" "Messaggio…" "Crea un collegamento" "Modifica collegamento" diff --git a/libraries/ui-strings/src/main/res/values-be/translations.xml b/libraries/ui-strings/src/main/res/values-be/translations.xml index 1e98029dda..fbea21c512 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -296,7 +296,6 @@ "Замацаваныя паведамленні" "Адклікаць праверку і адправіць" "Усё роўна адправіць паведамленне" - "Замацаваныя паведамленні" "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не ўдалося атрымаць інфармацыю пра карыстальніка" "%1$s з %2$s" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 652031d2c2..f1b88a1c76 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -48,8 +48,10 @@ "Potvrdit heslo" "Pokračovat" "Kopírovat" + "Kopírovat titulek" "Kopírovat odkaz" "Kopírovat odkaz na zprávu" + "Kopírovat text" "Vytvořit" "Vytvořit místnost" "Deaktivovat" @@ -96,6 +98,7 @@ "Odmítnout" "Odstranit" "Odstranit titulek" + "Odstranit zprávu" "Odpovědět" "Odpovědět ve vlákně" "Nahlásit chybu" @@ -300,12 +303,15 @@ Důvod: %1$s." "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" "Zatřeste zařízením pro nahlášení chyby" - "Přijmout vše" - "Odmítnout a vykázat" - "Když někdo požádá o vstup do místnosti, uvidíte jeho žádost zde." - "Žádná čekající žádost o vstup" - "Žádosti o vstup" + "Obrázky a videa nahraná do této místnosti budou zobrazeny zde." + "Zatím nebyla nahrána žádná média" + "Načítání souborů…" + "Načítání médií…" + "Soubory" + "Média" + "Média a soubory" "Výběr média se nezdařil, zkuste to prosím znovu." + "Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Přidržte zprávu a vyberte „%1$s“, kterou chcete zahrnout sem." @@ -326,8 +332,6 @@ Důvod: %1$s." "Vaše zpráva nebyla odeslána, protože%1$s neověřil(a) všechna zařízení" "Jedno nebo více vašich zařízení není ověřeno. Zprávu můžete přesto odeslat, nebo ji můžete prozatím zrušit a zkusit to znovu později, až ověříte všechna svá zařízení." "Vaše zpráva nebyla odeslána, protože jste neověřili jedno nebo více zařízení" - "Připnuté zprávy" - "Žádosti o vstup" "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nepodařilo se načíst údaje o uživateli" @@ -357,4 +361,7 @@ Důvod: %1$s." "Poloha" "Verze: %1$s (%2$s)" "en" + "Historické zprávy nejsou na tomto zařízení k dispozici" + "Nelze dešifrovat zprávu" + "Tato zpráva byla zablokována buď proto, že jste neověřili své zařízení, nebo proto, že odesílatel potřebuje ověřit vaši totožnost." diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 826730030d..4f2ca9bf97 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -299,20 +299,17 @@ Grund: %1$s." "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" "Schüttel heftig zum Melden von Fehlern" - "Ja, akzeptiere alle" - "Sind Sie sicher, dass Sie alle Beitrittsanfragen akzeptieren möchten?" - "Akzeptiere alle Anfragen" - "Alle akzeptieren" - "Ja, ablehnen und sperren" - "Sind Sie sicher, dass Sie %1$s ablehnen und sperren möchten ? Dieser Benutzer kann keinen erneuten Zugriff auf diesen Raum anfordern." - "Ablehnen und Zugriff verbieten" - "Ja, ablehnen" - "Sind Sie sicher, dass Sie die %1$s Anfrage, diesem Chatroom beizutreten, ablehnen möchten ?" - "Zugriff verweigern" - "Ablehnen und sperren" - "Falls jemand um Aufnahme in den Raum bittet, können Sie dessen Anfrage hier sehen." - "Keine ausstehende Beitrittsanfrage" - "Beitrittsanfragen" + "In diesen Chatroom hochgeladene Bilder und Videos werden hier angezeigt." + "Noch keine Medien hochgeladen" + "Dateien werden geladen…" + "Medien werden geladen…" + "Dateien" + "Medien" + "Medien und Dateien" + "Dateiformat" + "Dateiname" + "Hochgeladen von" + "Hochgeladen am" "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." @@ -334,8 +331,6 @@ Grund: %1$s." "Deine Nachricht wurde nicht gesendet, weil %1$s nicht alle Geräte verifiziert hat" "Mindestens eines Ihrer Geräte ist nicht verifiziert worden. Sie können die Nachricht trotzdem senden, oder den Vorgang zunächst abbrechen und es später erneut versuchen, nachdem Sie alle Ihrer Geräte verifiziert haben." "Ihre Nachricht wurde nicht geschickt, da Sie eines oder mehrere Ihrer Geräte nicht verifiziert haben." - "Fixierte Nachrichten" - "Beitrittsanfragen" "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Benutzerdetails konnten nicht abgerufen werden" @@ -364,4 +359,7 @@ Grund: %1$s." "Standort" "Version: %1$s (%2$s)" "en" + "Der Nachrichtenverlauf ist auf diesem Gerät nicht verfügbar" + "Nachricht kann nicht entschlüsselt werden" + "Diese Nachricht wurde entweder blockiert, weil Ihr Gerät nicht verifiziert ist oder weil der Absender Ihre Identität überprüfen muss." diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index 39b8bc5b9a..89e96e3f3c 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -299,20 +299,6 @@ "Γεια, μίλα μου στην εφαρμογή %1$s :%2$s" "%1$s Android" "Κούνησε δυνατά τη συσκευή σου για να αναφέρεις κάποιο σφάλμα" - "Ναι, αποδοχή όλων" - "Σίγουρα θες να αποδεχτείς όλα τα αιτήματα συμμετοχής;" - "Αποδοχή όλων των αιτημάτων" - "Αποδοχή όλων" - "Ναι, απόρριψη και αποκλεισμός" - "Σίγουρα θες να απορρίψειε και να αποκλείσεις τον χρήστη %1$s; Αυτός ο χρήστης δεν θα μπορεί να ζητήσει πρόσβαση για να συμμετάσχει ξανά σε αυτό το δωμάτιο." - "Απόρριψη και αποκλεισμός πρόσβασης" - "Ναι, απόρριψη" - "Σίγουρα θες να απορρίψεις το αίτημα του χρήστη %1$s να συμμετάσχει στο δωμάτιο;" - "Απόρριψη πρόσβασης" - "Απόρριψη και αποκλεισμός" - "Όταν κάποιος θα ζητήσει να συμμετάσχει στο δωμάτιο, θα μπορείς να δεις το αίτημά του εδώ." - "Δεν υπάρχει εκκρεμές αίτημα συμμετοχής" - "Αιτήματα συμμετοχής" "Αποτυχία επιλογής πολυμέσου, δοκίμασε ξανά." "Οι λεζάντες ενδέχεται να μην είναι ορατές σε άτομα που χρησιμοποιούν παλαιότερες εφαρμογές." "Αποτυχία μεταφόρτωσης μέσου, δοκίμασε ξανά." @@ -334,8 +320,6 @@ "Το μήνυμά σου δεν στάλθηκε επειδή ο χρήστης %1$s δεν έχει επαληθεύσει όλες τις συσκευές" "Μία ή περισσότερες από τις συσκευές σου δεν έχουν επαληθευτεί. Μπορείς να στείλεις το μήνυμα ούτως ή άλλως, ή μπορείς να το ακυρώσεις προς το παρόν και να προσπαθήσεις ξανά αργότερα αφού επαληθεύσεις όλες τις συσκευές σου." "Το μήνυμά σου δεν στάλθηκε επειδή δεν έχεις επαληθεύσει τουλάχιστον μία από τις συσκευές σου" - "Καρφιτσωμένα μηνύματα" - "Αιτήματα συμμετοχής" "Αποτυχία μεταφόρτωσης μέσου, δοκίμασε ξανά." "Δεν ήταν δυνατή η ανάκτηση στοιχείων χρήστη" diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index 3871a58914..b09e10b8b3 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -299,20 +299,13 @@ Põhjus: %1$s." "Hei, suhtle minuga %1$s võrgus: %2$s" "%1$s Android" "Veast teatamiseks raputa nutiseadet ägedalt" - "Jah, võta kõik vastu" - "Kas sa oled kindel, et soovid kõik vastu liitumist soovinud võtta?" - "Võta kõik vastu" - "Nõustu kõigiga" - "Jah, keeldu liitumisest ning keela ligipääs" - "Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa ning seada talle suhtluskeelu? Seetõttu ta ei saa ka enam hiljem liitumispalvet saata." - "Keeldu liitumisest ja keela ligipääs" - "Jah, keeldu" - "Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa?" - "Keela ligipääs" - "Keeldu ja määra suhtluskeeld" - "Kui keegi soovib jututoaga liituda, siis need päringud on kuvatud siin." - "Pole ühtegi liitumispalvet" - "Liitumispalved" + "Antud jututuppa üleslaaditud pildid ja videod kuvatakse siin." + "Mitte keegi pole veel meediat üles laadinud" + "Laadime faile…" + "Laadime meediat…" + "Failid" + "Meedia" + "Meedia ja failid" "Meediafaili valimine ei õnnestunud. Palun proovi uuesti." "Selgitused ja alapealkirjad ei pruugi olla nähtavad vanemate rakenduste kasutajatele." "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." @@ -334,8 +327,6 @@ Põhjus: %1$s." "Sinu sõnum on saatmata, kuna %1$s pole verifitseerinud kõiki oma seadmeid" "Üks või enam sinu seadet on verifitseerimata. Sa võid sõnumi ikkagi ära saata või katkestad saatmise ning proovid uuesti, kui oled kõik oma seadmed verifitseerinud." "Kuna sul on üks või enam verifitseerimata seadet, siis sinu sõnum jäi saatmata" - "Esiletõstetud sõnumid" - "Liitumispalved" "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." "Kasutaja andmete laadimine ei õnnestunud" @@ -364,4 +355,7 @@ Põhjus: %1$s." "Asukoht" "Versioon: %1$s (%2$s)" "et" + "Vanu sõnumeid ei saa selles seadmes näha" + "Sõnumi dekrüptimine ei õnnestu" + "Kuna seade on verifitseerimata või saatja pole sind verifitseerinud, siis sõnumi näitamine on blokeeritud." diff --git a/libraries/ui-strings/src/main/res/values-fa/translations.xml b/libraries/ui-strings/src/main/res/values-fa/translations.xml index cadcd7c6fa..b41246e3b7 100644 --- a/libraries/ui-strings/src/main/res/values-fa/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fa/translations.xml @@ -263,7 +263,6 @@ "پیام‌های سنجاق شده" "داردید برای بازنشانی هویتتان به حساب %1$s می‌روید. پس از آن به کاره برگردانده خواهید شد." "فرستادن پیام به هر روی" - "پیام‌های سنجاق شده" "پردازش رسانه برای بارگذاری شکست خورد. لطفاً دوباره تلاش کنید." "%1$s از %2$s" "%1$s پیام‌های سنجاق شده" diff --git a/libraries/ui-strings/src/main/res/values-fi/translations.xml b/libraries/ui-strings/src/main/res/values-fi/translations.xml index bb6696ebfa..2cc06a4271 100644 --- a/libraries/ui-strings/src/main/res/values-fi/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fi/translations.xml @@ -311,7 +311,6 @@ Syy: %1$s." "Viestiäsi ei lähetetty, koska %1$s ei ole vahvistanut kaikkia laitteitaan." "Yksi tai useampi laitteistasi on vahvistamaton. Voit lähettää viestin silti tai peruuttaa sen toistaiseksi ja yrittää uudelleen myöhemmin, kun olet vahvistanut kaikki laitteesi." "Viestiäsi ei lähetetty, koska et ole vahvistanut yhtä tai useampaa laitettasi." - "Kiinnitetyt viestit" "Median käsittely epäonnistui, yritä uudelleen." "Käyttäjän tietojen hakeminen epäonnistui" "%1$s / %2$s" diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index 24bd75cf46..ac4bff851e 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -158,7 +158,7 @@ "Erreur" "Une erreur s’est produite, il est possible que vous ne receviez pas de notifications pour les nouveaux messages. Veuillez résoudre les problèmes liés aux notifications depuis les paramètres. -Raison: %1$s." +Raison : %1$s." "Tout le monde" "Échec" "Favori" @@ -275,8 +275,8 @@ Raison: %1$s." "Erreur" "Succès" "Attention" - "Vos modifications n’ont pas été enregistrées. Êtes-vous certain de vouloir quitter?" - "Enregistrer les changements?" + "Vos modifications n’ont pas été enregistrées. Êtes-vous certain de vouloir quitter ?" + "Enregistrer les changements ?" "Votre serveur d’accueil doit être mis à jour pour prendre en charge le protocole MAS (Matrix Authentication Service) et la création de compte." "Échec de la création du permalien" "%1$s n’a pas pu charger la carte. Veuillez réessayer ultérieurement." @@ -299,20 +299,19 @@ Raison: %1$s." "Salut, parle-moi sur %1$s : %2$s" "%1$s Android" "Rageshake pour signaler un problème" - "Oui, tout accepter" - "Êtes-vous sûr de vouloir accepter toutes les demandes pour rejoindre le salon ?" - "Tout accepter" - "Tout accepter" - "Oui, rejeter et bannir" - "Êtes-vous sûr de vouloir rejeter la demande et bannir %1$s? Cet utilisateur ne pourra pas demander à nouveau à rejoindre ce salon." - "Refuser et interdire l’accès" - "Oui, refuser" - "Êtes-vous sûr de vouloir refuser la demande de %1$s à rejoindre le salon?" - "Refuser l’accès" - "Refuser et bannir" - "Lorsque quelqu’un demandera à rejoindre le salon, vous pourrez voir sa demande ici." - "Personne ne demande à rejoindre le salon" - "Demandes en attente" + "Ce fichier sera supprimé du salon et les membres n’y auront plus accès." + "Supprimer le fichier ?" + "Les images et vidéos envoyées dans ce salon seront affichées ici." + "Aucun média n’a encore été envoyé dans ce salon" + "Chargement des fichiers…" + "Chargement des médias…" + "Fichiers" + "Média" + "Médias et fichiers" + "Format du fichier" + "Nom du fichier" + "Envoyé par" + "Envoyé le" "Échec de la sélection du média, veuillez réessayer." "Les légendes peuvent ne pas être visibles pour les utilisateurs d’anciennes applications." "Échec du traitement des médias à télécharger, veuillez réessayer." @@ -334,8 +333,6 @@ Raison: %1$s." "Votre message n’a pas été envoyé car %1$s n’a pas vérifié tous ses appareils" "Un ou plusieurs de vos appareils ne sont pas vérifiés. Vous pouvez quand même envoyer le message, ou vous pouvez annuler et réessayer plus tard après avoir vérifié tous vos appareils." "Votre message n’a pas été envoyé car vous n’avez pas vérifié tous vos appareils" - "Messages épinglés" - "Demandes en attente" "Échec du traitement des médias à télécharger, veuillez réessayer." "Impossible de récupérer les détails de l’utilisateur" @@ -364,4 +361,7 @@ Raison: %1$s." "Position" "Version : %1$s ( %2$s )" "fr" + "Les anciens messages ne sont pas disponibles sur cet appareil" + "Impossible de déchiffrer le message" + "Ce message a été bloqué soit parce que vous n’avez pas vérifié votre session, soit parce que l’expéditeur doit vérifier votre identité." diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index d6e41bc748..b30c6f6896 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -32,6 +32,7 @@ "Hangüzenet felvétele." "Rögzítés leállítása" "Elfogadás" + "Felirat hozzáadása" "Hozzáadás az idővonalhoz" "Vissza" "Hívás" @@ -45,8 +46,10 @@ "Jelszó megerősítése" "Folytatás" "Másolás" + "Felirat másolása" "Hivatkozás másolása" "Üzenetre mutató hivatkozás másolása" + "Szöveg másolása" "Létrehozás" "Szoba létrehozása" "Deaktiválás" @@ -57,6 +60,7 @@ "Elvetés" "Kész" "Szerkesztés" + "Felirat szerkesztése" "Szavazás szerkesztése" "Engedélyezés" "Szavazás lezárása" @@ -91,6 +95,8 @@ "Reakció" "Elutasítás" "Eltávolítás" + "Felirat eltávolítása" + "Üzenet eltávolítása" "Válasz" "Válasz az üzenetszálban" "Hiba jelentése" @@ -123,6 +129,7 @@ "Igen" "Névjegy" "Elfogadható használatra vonatkozó szabályzat" + "Felirat hozzáadása" "Speciális beállítások" "Elemzések" "Megjelenítés" @@ -143,6 +150,7 @@ "Ne jelenjen meg többé" "(szerkesztve)" "Szerkesztés" + "Felirat szerkesztése" "* %1$s %2$s" "Titkosítás" "Titkosítás engedélyezve" @@ -246,6 +254,7 @@ Ok: %1$s." "Nem sikerült elküldeni a meghívót (meghívókat)" "Feloldás" "Némítás feloldása" + "Nem támogatott hívás" "Nem támogatott esemény" "Felhasználónév" "Az ellenőrzés megszakítva" @@ -290,7 +299,21 @@ Ok: %1$s." "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" + "Ez a fájl el lesz távolítva a szobából, és a tagok nem férhetnek hozzá." + "Törli a fájlt?" + "Az ebbe a szobába feltöltött képek és videók itt jelennek meg." + "Még nincs feltöltött média" + "Fájlok betöltése…" + "Média betöltése…" + "Fájlok" + "Média" + "Média és fájlok" + "Fájlformátum" + "Fájlnév" + "Feltöltötte:" + "Feltöltve:" "Nem sikerült kiválasztani a médiát, próbálja újra." + "Előfordulhat, hogy a feliratok nem láthatók a régebbi alkalmazásokat használók számára." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." "Nem sikerült a média feltöltése, próbálja újra." "Nyomjon hosszan az üzenetre, és válassza a „%1$s” lehetőséget, hogy itt szerepeljen." @@ -310,13 +333,20 @@ Ok: %1$s." "Az üzenet nem lett elküldve, mert %1$s nem ellenőrizte az összes eszközét" "Egy vagy több eszköze nincs ellenőrizve. Így is elküldheti az üzenetet, vagy egyelőre megszakíthatja, és később, az összes eszköz ellenőrzése után újrapróbálkozhat." "Az üzenet nem lett elküldve, mert egy vagy több eszközét nem ellenőrizte" - "Kitűzött üzenetek" "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." "Nem sikerült letölteni a felhasználói adatokat" + + "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" + "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" + + "Összes megtekintése" "%1$s / %2$s" "%1$s kitűzött üzenet" "Üzenet betöltése…" "Összes megtekintése" + "Elfogadás" + "%1$s szeretne csatlakozni ehhez a szobához" + "Megtekintés" "Csevegés" "Csatlakozási kérés elküldve" "Hely megosztása" @@ -331,4 +361,7 @@ Ok: %1$s." "Hely" "Verzió: %1$s (%2$s)" "hu" + "A korábbi üzenetek nem érhetők el ezen az eszközön" + "Nem sikerült visszafejteni az üzenetet" + "Ez az üzenet azért lett blokkolva, mert vagy nem ellenőrizte az eszközt, vagy a feladónak ellenőriznie kell az Ön személyazonosságát." diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index 3755164173..5cf002c334 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -305,7 +305,6 @@ Alasan: %1$s." "Pesan Anda tidak terkirim karena %1$s belum memverifikasi semua perangkat" "Satu atau beberapa perangkat Anda tidak terverifikasi. Anda tetap dapat mengirim pesan, atau Anda dapat membatalkannya dan mencoba lagi nanti setelah Anda memverifikasi semua perangkat." "Pesan Anda tidak terkirim karena Anda belum memverifikasi satu atau beberapa perangkat Anda" - "Pesan yang disematkan" "Gagal memproses media untuk diunggah, silakan coba lagi." "Tidak dapat mengambil detail pengguna" "%1$s dari %2$s" diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index 529fc66c06..69bfd8cc95 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -32,6 +32,7 @@ "Registra un messaggio vocale." "Ferma la registrazione" "Accetta" + "Aggiungi didascalia" "Aggiungi alla conversazione" "Indietro" "Chiama" @@ -45,8 +46,10 @@ "Conferma password" "Continua" "Copia" + "Copia didascalia" "Copia collegamento" "Copia collegamento al messaggio" + "Copia testo" "Crea" "Crea una stanza" "Disattiva" @@ -57,6 +60,7 @@ "Annulla" "Fine" "Modifica" + "Modifica didascalia" "Modifica sondaggio" "Attiva" "Termina sondaggio" @@ -64,6 +68,7 @@ "Password dimenticata?" "Inoltra" "Indietro" + "Ignora" "Invita" "Invita persone" "Invita persone su %1$s" @@ -84,12 +89,14 @@ "OK" "Impostazioni" "Apri con" - "Pin" + "Fissa" "Risposta rapida" "Citazione" "Reagisci" "Rifiuta" "Rimuovi" + "Rimuovi didascalia" + "Rimuovi messaggio" "Rispondi" "Rispondi nella discussione" "Segnala un problema" @@ -104,6 +111,7 @@ "Invia messaggio" "Condividi" "Condividi collegamento" + "Mostra" "Accedi di nuovo" "Disconnetti" "Disconnetti comunque" @@ -121,6 +129,7 @@ "Sì" "Informazioni" "Regole sull\'utilizzo consentito" + "Aggiunta didascalia" "Impostazioni avanzate" "Statistiche di utilizzo" "Aspetto" @@ -136,11 +145,14 @@ "Scuro" "Errore di decrittazione" "Opzioni sviluppatore" + "ID dispositivo" "Conversazione diretta" "Non mostrarlo più" "(modificato)" "Modifica in corso" + "Modifica didascalia" "* %1$s %2$s" + "Crittografia" "Crittografia abilitata" "Inserisci il PIN" "Errore" @@ -154,6 +166,7 @@ Motivo:. %1$s" "File" "File salvato" "Inoltra messaggio" + "Usati di frequente" "GIF" "Immagine" "In risposta a %1$s" @@ -234,22 +247,30 @@ Motivo:. %1$s" "Argomento" "Di cosa parla questa stanza?" "Impossibile decrittografare" + "Inviato da un dispositivo non sicuro" "Non hai accesso a questo messaggio" + "L\'identità verificata del mittente è cambiata" "Non è stato possibile spedire inviti a uno o più utenti." "Impossibile inviare inviti" "Sblocca" "Annulla silenzioso" + "Chiamata non supportata" "Evento non supportato" "Nome utente" "Verifica annullata" "Verifica completata" + "Verifica fallita" + "Verificato" "Verifica dispositivo" + "Verifica l\'identità" "Video" "Messaggio vocale" "In attesa…" "In attesa del messaggio" "Tu" "L\'identità di %1$s sembra essere cambiata. %2$s" + "L\'identità di %1$s %2$s sembra essere cambiata. %3$s" + "(%1$s)" "Conferma" "Errore" "Operazione riuscita" @@ -278,7 +299,13 @@ Motivo:. %1$s" "Ehi, parliamo su %1$s: %2$s" "%1$s Android" "Scuoti per segnalare un problema" + "Le immagini e i video caricati in questa stanza verranno mostrati qui." + "Nessun file multimediale ancora caricato" + "File" + "Contenuti multimediali" + "File e contenuti multimediali" "Selezione del file multimediale fallita, riprova." + "Le didascalie potrebbero non essere visibili agli utenti di app meno recenti." "Elaborazione del file multimediale da caricare fallita, riprova." "Caricamento del file multimediale fallito, riprova." "Premi su un messaggio e scegli “%1$s” per includerlo qui." @@ -298,14 +325,22 @@ Motivo:. %1$s" "Il tuo messaggio non è stato inviato perché %1$s non ha verificato tutti i dispositivi." "Uno o più dispositivi non sono verificati. Puoi inviare il messaggio comunque, oppure annullarlo e riprovare più tardi dopo aver verificato tutti i tuoi dispositivi." "Il tuo messaggio non è stato inviato perché non hai verificato uno o più dispositivi." - "Messaggi fissati" "Elaborazione del file multimediale da caricare fallita, riprova." "Impossibile recuperare i dettagli dell\'utente" + + "%1$s +%2$d vogliono entrare in questa stanza" + "%1$s +%2$d vogliono entrare in questa stanza" + + "Visualizza tutte" "%1$s di %2$s" "%1$s Messaggi fissati" "Caricamento messaggio…" "Mostra tutti" + "Accetta" + "%1$s vuole entrare in questa stanza" + "Visualizza" "Conversazione" + "Richiesta di accesso inviata" "Condividi posizione" "Condividi la mia posizione" "Apri in Apple Maps" @@ -318,4 +353,7 @@ Motivo:. %1$s" "Posizione" "Versione: %1$s (%2$s)" "it" + "La cronologia messaggi non è disponibile su questo dispositivo" + "Impossibile decifrare il messaggio" + "Questo messaggio è stato bloccato perché il dispositivo non è verificato o perché il mittente deve verificare la tua identità." diff --git a/libraries/ui-strings/src/main/res/values-nl/translations.xml b/libraries/ui-strings/src/main/res/values-nl/translations.xml index 71f173b9bf..b9a672e706 100644 --- a/libraries/ui-strings/src/main/res/values-nl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nl/translations.xml @@ -302,7 +302,6 @@ Reden: %1$s." "Je bericht is niet verzonden omdat %1$s niet alle apparaten heeft geverifieerd" "Een of meer van je apparaten zijn niet geverifieerd. Je kunt het bericht toch verzenden, of je kunt het voorlopig annuleren en het later opnieuw proberen nadat je al je apparaten hebt geverifieerd." "Je bericht is niet verzonden omdat je een of meerdere apparaten niet geverifieerd hebt" - "Vastgezette berichten" "Het verwerken van media voor uploaden is mislukt. Probeer het opnieuw." "Kon gebruikersgegevens niet ophalen" "%1$s van %2$s" 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 6373c2fec6..3d84d351a8 100644 --- a/libraries/ui-strings/src/main/res/values-pl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pl/translations.xml @@ -126,7 +126,7 @@ "O programie" "Polityka użytkowania" "Ustawienia zaawansowane" - "Analityka" + "Dane analityczne" "Wygląd" "Dźwięk" "Zablokowani użytkownicy" @@ -262,6 +262,7 @@ Powód: %1$s." "Oczekiwanie na tę wiadomość" "Ty" "Tożsamość %1$s mogła ulec zmianie. %2$s" + "Wygląda na to, że tożsamość %1$s %2$s uległa zmianie. %3$s" "(%1$s)" "Potwierdzenie" "Błąd" @@ -312,7 +313,6 @@ Powód: %1$s." "Twoja wiadomość nie została wysłana, ponieważ %1$s nie zweryfikował swoich wszystkich urządzeń" "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ń." - "Przypięte wiadomości" "Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie." "Nie można pobrać danych użytkownika" "%1$s z %2$s" diff --git a/libraries/ui-strings/src/main/res/values-pt/translations.xml b/libraries/ui-strings/src/main/res/values-pt/translations.xml index 437d6b63e6..967647b836 100644 --- a/libraries/ui-strings/src/main/res/values-pt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt/translations.xml @@ -310,7 +310,6 @@ Razão: %1$s." "A sua mensagem não foi enviada porque %1$s não verificou todos os dispositivos" "Um ou mais dos teus dispositivos não foram verificados. Podes enviar a mensagem na mesma, ou podes cancelar por agora e tentar novamente mais tarde, depois de teres verificado todos os teus dispositivos." "A sua mensagem não foi enviada porque não verificou um ou mais dos seus dispositivos" - "Mensagens afixadas" "Falha ao processar multimédia para carregamento, por favor tente novamente." "Não foi possível obter os detalhes de utilizador." "%1$s de %2$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 35e9388ae7..7bb6675bd5 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -303,20 +303,13 @@ "Привет, поговори со мной по %1$s: %2$s" "%1$s Android" "Встряхните устройство, чтобы сообщить об ошибке" - "Да, принять все" - "Вы действительно хотите принять все заявки на присоединение?" - "Принять все запросы" - "Принять всё" - "Да, отклонить и запретить" - "Вы уверен, что хочешь отклонить и запретить %1$s? Этот пользователь больше не сможет запросить доступ к этой комнате." - "Отклонить и запретить доступ" - "Да, отклонить" - "Вы уверены, что хотите отклонить %1$s запрос на присоединение к этой комнате?" - "Отклонить доступ" - "Отклонить и запретить" - "Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате." - "Нет ожидающих запросов на присоединение" - "Запросы на присоединение" + "Здесь будут показаны изображения и видео, загруженные в данную комнату." + "Пока что нет загруженных медиафайлов" + "Загрузка файлов…" + "Загрузка медиа…" + "Файлы" + "Медиа" + "Медиа и файлы" "Не удалось выбрать носитель, попробуйте еще раз." "Подпись может быть не видна пользователям старых приложений." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." @@ -339,8 +332,6 @@ "Ваше сообщение не было отправлено, потому что %1$s не проверил одно или несколько устройств" "Одно или несколько ваших устройств не проверены. Вы можете отправить сообщение в любом случае или отменить его пока и повторить попытку позже, проверив все свои устройства." "Ваше сообщение не было отправлено, поскольку вы не подтвердили одно или несколько своих устройств." - "Закрепленные сообщения" - "Запросы на вступление" "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось получить данные о пользователе" @@ -370,4 +361,7 @@ "Местоположение" "Версия: %1$s (%2$s)" "ru" + "На этом устройстве недоступна история сообщений" + "Не удалось расшифровать сообщение" + "Это сообщение было заблокировано по причине того, что вы не подтвердили свое устройство, либо отправителю необходимо подтвердить вашу личность." diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index 64646a6245..c43567ea44 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -300,11 +300,6 @@ Dôvod: %1$s." "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" "%1$s Android" "Zúrivo potriasť pre nahlásenie chyby" - "Prijať všetky" - "Odmietnuť a zakázať" - "Keď niekto požiada, aby sa pripojil k miestnosti, jeho žiadosť si môžete pozrieť tu." - "Žiadna čakajúca žiadosť o pripojenie" - "Žiadosti o pripojenie" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa nahrať médiá, skúste to prosím znova." @@ -326,8 +321,6 @@ Dôvod: %1$s." "Vaša správa nebola odoslaná, pretože %1$s neoveril/a všetky zariadenia." "Jedno alebo viac vašich zariadení nie je overených. Správu môžete odoslať aj tak, alebo môžete zatiaľ zrušiť a skúsiť to znova neskôr po overení všetkých svojich zariadení." "Vaša správa nebola odoslaná, pretože ste neoverili jedno alebo viac svojich zariadení" - "Pripnuté správy" - "Žiadosti o vstup" "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa získať údaje o používateľovi" diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index 8d3b71030a..5fe04394f8 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -282,7 +282,6 @@ Anledning:%1$s." "Fästa meddelanden" "Du är på väg att gå till ditt %1$s-konto för att återställa din identitet. Därefter kommer du att tas tillbaka till appen." "Kan du inte bekräfta? Gå till ditt konto för att återställa din identitet." - "Fästa meddelanden" "Misslyckades att bearbeta media för uppladdning, vänligen pröva igen." "Kunde inte hämta användarinformation" "%1$s av %2$s" diff --git a/libraries/ui-strings/src/main/res/values-uk/translations.xml b/libraries/ui-strings/src/main/res/values-uk/translations.xml index 5c86a27959..15506c139c 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -315,7 +315,6 @@ "Ваше повідомлення не було надіслано, тому що %1$s не перевірив усі пристрої" "Один або кілька ваших пристроїв не підтверджено. Ви можете відправити повідомлення в будь-якому випадку, або ж скасувати відправку і спробувати пізніше, коли перевірите всі свої пристрої." "Ваше повідомлення не було надіслано, оскільки ви не підтвердили один або декілька своїх пристроїв" - "Закріплені повідомлення" "Не вдалося обробити медіафайл для завантаження, спробуйте ще раз." "Не вдалося отримати дані користувача" "%1$s із %2$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 80e69850ff..0451985bb2 100644 --- a/libraries/ui-strings/src/main/res/values-zh/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh/translations.xml @@ -305,7 +305,6 @@ "您的消息未发送,因为%1$s尚未验证所有设备" "您有未验证的设备。您仍然可以发送消息;也可以暂时取消,并在验证所有设备后稍后重试。" "您的消息未发送,因为您有尚未验证的设备。" - "置顶消息" "处理要上传的媒体失败,请重试。" "无法获取用户信息" "%1$s / %2$s" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 75b0f6853a..60cb373eba 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -299,6 +299,19 @@ Reason: %1$s." "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" + "This file will be removed from the room and members won’t have access to it." + "Delete file?" + "Images and videos uploaded to this room will be shown here." + "No media uploaded yet" + "Loading files…" + "Loading media…" + "Files" + "Media" + "Media and files" + "File format" + "File name" + "Uploaded by" + "Uploaded on" "Failed selecting media, please try again." "Captions might not be visible to people using older apps." "Failed processing media to upload, please try again." diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png new file mode 100644 index 0000000000..134e9bdab9 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9620eeb151631783e732ac036c562887b49ae075f48f579f7c83cf05ee982ecf +size 8262 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png new file mode 100644 index 0000000000..db76d16a1b --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f88e93f921e81c8b57a9a6d37a11798df9b550d1ad4224f5605403940f752df4 +size 28956 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png new file mode 100644 index 0000000000..5dc14a3d58 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e87303b2679ab4315c4f4357a1602b61fd48a28757cd9424cff6eeffca589839 +size 34791 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png new file mode 100644 index 0000000000..06b28f4d82 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9459af0c6974c83ba734436c42574a3894caddc07daf4dd54c18ba17b8e5c3a5 +size 43379 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png new file mode 100644 index 0000000000..d22b967291 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f93153775942f24e599c8a57ca3bb5b8f3924e7717b647c7f10cc10d9a44e4fb +size 55896 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png new file mode 100644 index 0000000000..2db28246d3 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86f1d4fc547228af5e380f016751e886d15ebf0bf08b3248c8d6ffcca4a77109 +size 31542 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png new file mode 100644 index 0000000000..2cf7dca91f --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:764b6feaeabb60fc0d77978af75740a935351df26049824658bdefb6e4853074 +size 31763 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png new file mode 100644 index 0000000000..0af2113d9c --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa4db76cd0989126176b4e972f142ffed85a6986aeaccfb238ec090de4b9872d +size 31844 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png new file mode 100644 index 0000000000..df489c8ea1 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0239309f37dd691f9e4c17705e4a5124d271684e9a578b10bd7e2463b9b2df18 +size 28380 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png new file mode 100644 index 0000000000..d97d375fea --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b49e768d7ece4275118c3cc394d9f9cd93c0e62ccbef649f71554a1124572450 +size 31549 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png index 5fa9d54233..8988c0e9e2 100644 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:432e20dbee2b01faebb0699d11b83dc08f3995b23db622b2645ebdcec1b9cdbc -size 398406 +oid sha256:9ccd9d75d94f50b0deba10aa5cce3a8053e22f125b02b841f08a5b97f98f4d47 +size 396664 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png index 157f8f6272..c1c8843eec 100644 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db1ad6cc1ca2d83fd389b11eaf7554d3790ed9710e5af524d7c3dfe73987b0c3 -size 19892 +oid sha256:fe2284acf2a5d906181e44d06cd13d5fb8dae51396f348f8484528e75bf00fd1 +size 54684 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png index 4202f8ccbf..0dba7fd3a3 100644 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd72e9a88a9aa3dcd012657e218a372e2e0078f2748b62df107b0dc9ae56ec75 -size 22245 +oid sha256:694ca900f166ac3a23d87e60020211e8b98c02c16afece44364949ff1c19c149 +size 54650 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png index d5f291ce6a..99a2236795 100644 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13765804d661727d44f40f53d8865183f94651f16d30831ee9ab8b90fd9a4efe -size 23251 +oid sha256:5101ba438b9f028712fd288160fe6fdec87148962ab02a6d15d02c62fb058945 +size 80736 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png deleted file mode 100644 index 0bf11f0975..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f7a22a39cd00987efa645df5626eec5e405f08ca046c23d53ce7e506f1654e5 -size 54411 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png index 732ac86d77..8988c0e9e2 100644 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b415770af2855daf5cc2a09a38ec955ed5c80f958575ff2f5ddfb053df7d7870 -size 54385 +oid sha256:9ccd9d75d94f50b0deba10aa5cce3a8053e22f125b02b841f08a5b97f98f4d47 +size 396664 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png deleted file mode 100644 index f5f28f06a1..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff9a1d9f654b9c2cc5e21ffca1c2e85014f9b0b190b534de0667bc49c05e328f -size 80690 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png index 3caf8ac57f..435710b521 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ceb7481fba9bc16a2b5aa75915f71100a6136804383854cace129d54871492e -size 13673 +oid sha256:9da05541fb126929f9e48f4e64db3373979f9535da19f91ef8d9ac911ec9b9f9 +size 13683 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png index 60ae5aae0c..4c366e8e5e 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4d1d4a86d53c785d75b559a581dfb5191c740e71f781339e25b14ede5d0b3cc -size 8950 +oid sha256:ed80cafdbc8c53fd8c031818fd4df8d7a63f85cc896d1262301d77bfb0ea9c6f +size 14415 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png new file mode 100644 index 0000000000..225fbedca2 --- /dev/null +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c377c4db1993dc4d81c4726e3cd4d5e50a5ce4e953132f630d5ad3b34c2b34d3 +size 24991 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png new file mode 100644 index 0000000000..1793f2cd28 --- /dev/null +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9fa624de81147b6cc30b329c776fcd1230ab2049283ccf5190cf30bd95b2a29 +size 11154 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png new file mode 100644 index 0000000000..60ae5aae0c --- /dev/null +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4d1d4a86d53c785d75b559a581dfb5191c740e71f781339e25b14ede5d0b3cc +size 8950 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png index c577e8c111..0362d1fd5a 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32eeac69c1c30c1df6252b38eaed414f2b316fb08e914778e544481ae79ace7f -size 38115 +oid sha256:882a80aee379aaa550dd33f70d24deca6fb4faf93cc11f830a2080c9279fcb6c +size 37983 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png index 55bd273fb7..b3c0d8a1a9 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a3c9133b95879d7f0014a3abd993f3ef0f07ac97795b1bc9a77700867a40104 -size 59909 +oid sha256:ece8c46ed93d567c0c7e083d6b35dcf58d515c418f1348bc1a596ae5cd0e415e +size 57824 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png index f0e7376bf1..144d57de77 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a1a3f69c036c334179a9fb219d12a12825b7a9c40f38854583ec98ec3ca9f4b -size 59939 +oid sha256:95b86ce0454c63decc5283ce07a81f76773e3312eca6a27ef0026959041d30fc +size 57852 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png index b62aa5fe3e..e741b95858 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:944a96ffbc396029275d6a20bd2227c310a7262da774503bd3a52bc4d762647a -size 63273 +oid sha256:9d186fcc63bb524c23b6b42e6c4fab6f902271c755fc9c7de21ff609b2ab6483 +size 61170 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png index 714c056792..d40b5db1ad 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:156369e5f7e94f58f825f2249f201a5d7b40655f1425b774a17445e518fcf161 -size 59093 +oid sha256:6ccc4aa5c095c7c01585d66f5caf146eba7060ba361ec03719c8474abb6f5ccf +size 57002 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png index 6ee127e60f..a430ccafa8 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18244f7e05810813387b5b7452b83f2da6d3ffc90795315a598a14cc684e18ca -size 56125 +oid sha256:a7320a1c07fdef11132084db9a5d0c33daf96e2a62cce7c687dc2342bf5f7544 +size 55422 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png index 28d2d44b52..66ed2c1d1c 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d8c86158553769b70234e06c359002dcff225e0d0106f0d1bd39c6850aee7ad -size 57724 +oid sha256:da56456bff1a3c3227dac7fd307ea2e65b6ed06d18198321d23db71e282370c8 +size 55629 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png index c19d6aca20..8805b4d219 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c99fc5403f85c8c124825d89199c57e66ba83fdb2d3a240ba3aa5c371dfd39d -size 60006 +oid sha256:af12ee2fff1baacc2035bb2fcc91280801d3e00e47eef153f5a2d8d420aeef5e +size 59278 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png index 4a5a3a5696..e6d07e3ba6 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80100385d90b8b40d6b65307cb2dc365e1650c4957c0fd62bf22bda5a6a84165 -size 62388 +oid sha256:76d16e02ae85372018b0e4a75640344fcf6ef6ccfe628a3d0df3d39cd0cad0fe +size 60292 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png index b3b07021b1..e2b84ff5cb 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cea15456754678c29eda54cd40b67d6a979baf5f244839119d107d9135ac5f84 -size 48428 +oid sha256:2f52f75694f30f122948890b47aed021f724862b9e13f8c33a722e0e85c89446 +size 47720 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png index 427e5ca89f..735b7b6834 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1ce9cdff91b1779c8012cbb5a3d97fa3d3a2f234137504e649401a55f18bb79 -size 315251 +oid sha256:832fe4b9ed7b53c161374cc72438c49f254569b33d64ded5f661152976b0d883 +size 315128 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png index f633bfa019..441a167459 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:139052189bb9fd0925057ca6f24c153e2b74f2152bd96176cbf748acdc32657a -size 310709 +oid sha256:a5bb6a1bec8b521c06651f31825f2245441232cd89c4da53280ddc29c3554f91 +size 310728 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png index 473d54b929..b77eaee61d 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb3ef7da385418584ceae0bf4f8f2b66f2342b5f924118976807a89a974d69d0 -size 313709 +oid sha256:d9dff767d267c198d1cdcdc79c313b7fd04d1d90436dd754fc0e07b69a2dddc5 +size 313727 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png index 45cc609343..42ac955c15 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cf4084319b5af180e18fe7ef56be0f02e28007da76b2a575300d40c809d9d22 -size 308069 +oid sha256:fead83f04b6fa70a2f3b1cd9c05e99a37b5ab8d8a6fab2c0d3d665c6d0838613 +size 307936 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png index 9dc1afd109..d94ddc8e5b 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:286c322d0b255ff738fbd8f1985582e8e7d9d88e7918054bf2fd47e184490b60 -size 316029 +oid sha256:7b6fd1e8ef08f97c918548e19e63a6419f2142aedd587a39877f53d5ea56f14f +size 315910 diff --git a/screenshots/de/libraries.mediaviewer.api.viewer_MediaViewerView_2_de.png b/screenshots/de/libraries.mediaviewer.api.viewer_MediaViewerView_2_de.png deleted file mode 100644 index e5a2758a48..0000000000 --- a/screenshots/de/libraries.mediaviewer.api.viewer_MediaViewerView_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e19bc448799c94926aa3ad247ad4da2ec5b1c40166dce4f3c17679faa426b73 -size 71611 diff --git a/screenshots/de/libraries.mediaviewer.api.local.pdf_PdfPagesErrorView_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_de.png similarity index 100% rename from screenshots/de/libraries.mediaviewer.api.local.pdf_PdfPagesErrorView_Day_0_de.png rename to screenshots/de/libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_de.png diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png new file mode 100644 index 0000000000..013fa9a110 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4d2b461afe45b703554689435ca19adab44e3190e23b1a71435644164deaba1 +size 71628 diff --git a/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png b/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png new file mode 100644 index 0000000000..034b838c95 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:581bc19df178842dc2168c5a4a5b68e252064dfa2ac464b33669f64d931f0ce3 +size 21056 diff --git a/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png b/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png index d341017e17..630a153541 100644 --- a/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6809dcd5a339af0adf428e6aa198ca1719bee480e2cc7130a2cfd0feb88e06e -size 62949 +oid sha256:42488fd254e1f3af4daa2dbc89d474f6fc33ff32e65fe3a5f1c225fcf440b4f8 +size 55344 diff --git a/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png index eb9d617feb..a7adc543f6 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72d90402ff38639d384be74db19bc8ee5e8dfc15def5e24839c404809e74829c -size 65689 +oid sha256:e21e91e0d2fa375a1228f56aca68ec6db1209e5c49970d88d8e72a83362308e2 +size 61266 diff --git a/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png index 16aabac42d..aa645ae51b 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70681c983dd8027ef21a7cf7f1ce21b2aa49608026a2f265d4ea2b452f394cc0 -size 57592 +oid sha256:077d64f7179ad1d32ff633c5e4074270095d9579508b9a396c05437774810a41 +size 48936 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png index aaf06e57ab..4217c75857 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed323016c3aa4c61d248f7f515c4008bea91b5ab07513f1ad85c448b28a8e071 -size 66415 +oid sha256:51f950b5fbb386756f920bcffe055eac581edfa78f566ff857dacc41761a03df +size 61347 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png index d341017e17..630a153541 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6809dcd5a339af0adf428e6aa198ca1719bee480e2cc7130a2cfd0feb88e06e -size 62949 +oid sha256:42488fd254e1f3af4daa2dbc89d474f6fc33ff32e65fe3a5f1c225fcf440b4f8 +size 55344 diff --git a/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png index 5c10dd38bc..972c016e3e 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:875f73e545458cc85207df61fbf1373f88aa862853ab21cfd6fb351bb91948be -size 59545 +oid sha256:d992143ff08f81f2b2bce2128e8c6ad7520ad98953a3114b44565ec2f43e2d95 +size 52946 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png index 51f5f5770a..9081a2f732 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee02ad92f874446ebd74e46502fd45614fc97c0034675a4447dce351798d8be6 -size 82047 +oid sha256:6b6804e4c9415ba75da5ca9b16be07c6b1eda1b6d84e2d563ef692cb93617d32 +size 75820 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png index 7b5d6c9bcd..65b9db12be 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b5861f697ef912bb02b8419dbdf7243510e1942a3f716a60c63a6aecd79a3b9 -size 65337 +oid sha256:3faaf28767463aa8fe5961a61c6fdf992c7d618b19733bc91150471a88020cb6 +size 58864 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png index ef37f98bd8..b6c1f2e50e 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4b9230dda20f3871bb6253731bf0dc04b92e09a6441851962323ccac68a6caa -size 80092 +oid sha256:84100fcdc5b70f67f9b2165bdf8eb77cccc8f05cd61b0bdcd8ba9039d7949449 +size 73845 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png index 0456b59845..a53d365ccd 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a24829845f382f53961346b127e567110209eda399edeb0e3bc87e184a8fe048 -size 91429 +oid sha256:2d6aa5d6f29e38888aaaf9539f66bd3235c9ea8a9096e48a6538086057b0b473 +size 85309 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png index ef07526a30..4b664b0055 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d1fd1eddd14d06b2b8624b213fa38fab109097a5cd718449973547d6a6f49e8 -size 68618 +oid sha256:9b853ffe20e9d4c8c10c86caec538eb302b496d7c18232ac0325a498085cc9e4 +size 62069 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png index ee40a061ca..16864c1179 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0997a54e0fdf6c321d6012b5e9f9825f2d5f4cdc060e9ea866e8c65e7d04927a -size 66870 +oid sha256:dba9bd70b11b1747e945aa12ccbb94b40c3025cf70efaf961eb44da4e75be8b8 +size 60340 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png index a3179a0f59..86b1424d2d 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acc8dfa9e4c996dec342558fa40ac6b2f26b6d8f9a218aacc3a8fbe1a49fca80 -size 74825 +oid sha256:bc3d3b82f386467567eefa83522d21c6d93c0561f5e5cc6301cbacf521686802 +size 68386 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png index 0ed418ba83..72f44ed719 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df1f9c9a662bcf58e4e3699abadaacf4aef0b0039fbcae5ce1cf050d5d6a344b -size 65775 +oid sha256:afe16af06bfedd3b030f3a7d27e8d8f63e89068625c3530c71f50a627fcc6a07 +size 59291 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png index a54b4c686d..d111477810 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e26de5d2a495c686f7214b0ff62f277482d272ba36c60055d545001ff620614c -size 66628 +oid sha256:93eeb7258381e4e5026226a6226da75975fd3db36d28c57359df0fc8f832bfc4 +size 60072 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png index 64da7bda0d..58bd7612b5 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3036d2cc2517b22c2101e4a836be340c19f8b318f63d1bc7e2e75385d9bff2ce -size 68819 +oid sha256:635fe16b8cf1bf574c989fbf2b0cd302674b66c101ddeb2cde3f2dcb7426ea00 +size 62282 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png index 0c1e0f279b..d55b3812f3 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c1cf0d780d9027d6b4d1aa3408b3e3c2df5e33e43ecce13c1f710f2773d6532 -size 76468 +oid sha256:c00782f67da13a617718e50dbb6b21e6e4ded8ee29ac1c6e2eb6ae8374050b78 +size 70410 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png index 855c2cb40d..76a546a60c 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0a312aae8b862b08675ad66b18288fe4f509e98b0767715c4b0ab706e1b995f -size 66070 +oid sha256:1906f2a814ab6ddb0f6ca509f2eb28b45515285f6db2acb7822c2c72d49b5884 +size 59543 diff --git a/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png index fc57718382..491ce0d9f0 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdee2ff1500639827661775d87822dd7262019fea3709dad0e8c503e0c43b5a7 -size 51421 +oid sha256:7fe8f3ac71d021e8799b9536bcc81e8570d742d768f3b07fc87bc9f31f17f0e7 +size 46244 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index ca26a3463f..698b0d466d 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,59 +1,59 @@ // Generated file, do not edit export const screenshots = [ ["en","en-dark","de",], -["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20056,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20063,], ["features.invite.impl.response_AcceptDeclineInviteView_Day_0_en","features.invite.impl.response_AcceptDeclineInviteView_Night_0_en",0,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",20056,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",20056,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",20056,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",20056,], -["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20056,], -["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20059,], -["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20056,], -["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20056,], -["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20056,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",20063,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",20063,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",20063,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",20063,], +["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20063,], +["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20063,], +["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20063,], +["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20063,], +["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20063,], ["features.login.impl.accountprovider_AccountProviderView_Day_0_en","features.login.impl.accountprovider_AccountProviderView_Night_0_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_1_en","features.login.impl.accountprovider_AccountProviderView_Night_1_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_2_en","features.login.impl.accountprovider_AccountProviderView_Night_2_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_3_en","features.login.impl.accountprovider_AccountProviderView_Night_3_en",0,], ["features.messages.impl.actionlist_ActionListViewContent_Day_0_en","features.messages.impl.actionlist_ActionListViewContent_Night_0_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20056,], +["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20063,], ["features.messages.impl.actionlist_ActionListViewContent_Day_1_en","features.messages.impl.actionlist_ActionListViewContent_Night_1_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20056,], -["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20056,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20056,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20056,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20056,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20056,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",20056,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",20056,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",20056,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",20056,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en",20056,], -["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20056,], -["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20056,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20056,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20056,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20056,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20056,], +["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20063,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20063,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20063,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20063,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20063,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",20063,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",20063,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",20063,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",20063,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en",20063,], +["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20063,], +["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20063,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20063,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20063,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20063,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20063,], ["libraries.designsystem.components.async_AsyncActionView_Day_0_en","libraries.designsystem.components.async_AsyncActionView_Night_0_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20056,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20063,], ["libraries.designsystem.components.async_AsyncActionView_Day_2_en","libraries.designsystem.components.async_AsyncActionView_Night_2_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20056,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20063,], ["libraries.designsystem.components.async_AsyncActionView_Day_4_en","libraries.designsystem.components.async_AsyncActionView_Night_4_en",0,], -["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20056,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20063,], ["libraries.designsystem.components.async_AsyncIndicatorFailure_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorFailure_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncIndicatorLoading_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorLoading_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncLoading_Day_0_en","libraries.designsystem.components.async_AsyncLoading_Night_0_en",0,], -["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20056,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20063,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_0_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_0_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_1_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_1_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_2_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_2_en",0,], @@ -63,15 +63,13 @@ export const screenshots = [ ["libraries.matrix.ui.components_AttachmentThumbnail_Day_6_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_6_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_7_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_7_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_8_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_8_en",0,], -["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20059,], -["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20059,], -["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20059,], -["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20059,], -["features.messages.impl.attachments.preview_AttachmentsView_4_en","",20056,], -["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20056,], -["features.messages.impl.attachments.preview_AttachmentsView_6_en","",20059,], -["features.messages.impl.attachments.preview_AttachmentsView_7_en","",0,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20056,], +["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20063,], +["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20063,], +["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20063,], +["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20063,], +["features.messages.impl.attachments.preview_AttachmentsView_4_en","",0,], +["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20063,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20063,], ["libraries.designsystem.components.avatar_Avatar_Avatars_0_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_10_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_11_en","",0,], @@ -147,20 +145,23 @@ export const screenshots = [ ["libraries.designsystem.components.avatar_Avatar_Avatars_75_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_76_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_77_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_78_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_79_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_7_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_80_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_8_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_9_en","",0,], ["libraries.designsystem.components.button_BackButton_Buttons_en","",0,], ["libraries.designsystem.components_Badge_Day_0_en","libraries.designsystem.components_Badge_Night_0_en",0,], ["libraries.designsystem.components_BigCheckmark_Day_0_en","libraries.designsystem.components_BigCheckmark_Night_0_en",0,], ["libraries.designsystem.components_BigIcon_Day_0_en","libraries.designsystem.components_BigIcon_Night_0_en",0,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20056,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20056,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20056,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20056,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20056,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20056,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20056,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20063,], ["libraries.designsystem.components_BloomInitials_Day_0_en","libraries.designsystem.components_BloomInitials_Night_0_en",0,], ["libraries.designsystem.components_BloomInitials_Day_1_en","libraries.designsystem.components_BloomInitials_Night_1_en",0,], ["libraries.designsystem.components_BloomInitials_Day_2_en","libraries.designsystem.components_BloomInitials_Night_2_en",0,], @@ -171,115 +172,116 @@ export const screenshots = [ ["libraries.designsystem.components_BloomInitials_Day_7_en","libraries.designsystem.components_BloomInitials_Night_7_en",0,], ["libraries.designsystem.components_Bloom_Day_0_en","libraries.designsystem.components_Bloom_Night_0_en",0,], ["libraries.designsystem.theme.components_BottomSheetDragHandle_Day_0_en","libraries.designsystem.theme.components_BottomSheetDragHandle_Night_0_en",0,], -["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20056,], -["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20056,], -["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20056,], -["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20056,], -["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20056,], +["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20063,], +["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20063,], +["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20063,], +["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20063,], +["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20063,], ["libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.molecules_ButtonRowMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonRowMolecule_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_0_en","features.messages.impl.timeline.components_CallMenuItem_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_1_en","features.messages.impl.timeline.components_CallMenuItem_Night_1_en",0,], -["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20056,], -["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20056,], +["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20063,], +["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20063,], ["features.messages.impl.timeline.components_CallMenuItem_Day_4_en","features.messages.impl.timeline.components_CallMenuItem_Night_4_en",0,], ["features.call.impl.ui_CallScreenPipView_Day_0_en","features.call.impl.ui_CallScreenPipView_Night_0_en",0,], ["features.call.impl.ui_CallScreenPipView_Day_1_en","features.call.impl.ui_CallScreenPipView_Night_1_en",0,], ["features.call.impl.ui_CallScreenView_Day_0_en","features.call.impl.ui_CallScreenView_Night_0_en",0,], -["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20056,], -["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20056,], -["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20056,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20056,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20056,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20063,], +["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20063,], +["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20063,], +["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20066,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",20063,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20063,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20063,], ["features.login.impl.changeserver_ChangeServerView_Day_0_en","features.login.impl.changeserver_ChangeServerView_Night_0_en",0,], -["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20056,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20056,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20063,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20063,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20056,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20063,], ["libraries.designsystem.theme.components_Checkboxes_Toggles_en","",0,], ["libraries.designsystem.theme.components_CircularProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components_ClickableLinkText_Text_en","",0,], ["libraries.designsystem.theme_ColorAliases_Day_0_en","libraries.designsystem.theme_ColorAliases_Night_0_en",0,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20056,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20056,], -["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20056,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20063,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20063,], +["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20063,], ["libraries.textcomposer_ComposerModeView_Day_1_en","libraries.textcomposer_ComposerModeView_Night_1_en",0,], ["libraries.textcomposer_ComposerModeView_Day_2_en","libraries.textcomposer_ComposerModeView_Night_2_en",0,], ["libraries.textcomposer_ComposerModeView_Day_3_en","libraries.textcomposer_ComposerModeView_Night_3_en",0,], ["libraries.textcomposer.components_ComposerOptionsButton_Day_0_en","libraries.textcomposer.components_ComposerOptionsButton_Night_0_en",0,], ["libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en","",0,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20056,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20056,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20063,], ["features.preferences.impl.developer.tracing_ConfigureTracingView_Day_0_en","features.preferences.impl.developer.tracing_ConfigureTracingView_Night_0_en",0,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20056,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20056,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20056,], -["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20056,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20063,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20063,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20063,], +["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20063,], ["libraries.designsystem.components.dialogs_ConfirmationDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ConfirmationDialog_Day_0_en","libraries.designsystem.components.dialogs_ConfirmationDialog_Night_0_en",0,], ["features.networkmonitor.api.ui_ConnectivityIndicatorView_Day_0_en","features.networkmonitor.api.ui_ConnectivityIndicatorView_Night_0_en",0,], -["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20056,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20056,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20056,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20056,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20056,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20056,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20056,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20056,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20056,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20056,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20056,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20056,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20056,], -["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",20056,], -["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",20056,], -["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",20056,], -["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",20056,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20056,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20056,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20063,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20063,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20063,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20063,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20063,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20063,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20063,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20063,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20063,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20063,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20063,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20063,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20063,], +["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",20063,], +["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",20063,], +["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",20063,], +["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",20063,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20063,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20063,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_0_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_0_en",0,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20056,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20056,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20056,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20063,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20063,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20063,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_4_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_4_en",0,], -["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20056,], -["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20056,], -["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",20056,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20063,], +["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20063,], +["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",20063,], ["features.licenses.impl.details_DependenciesDetailsView_Day_0_en","features.licenses.impl.details_DependenciesDetailsView_Night_0_en",0,], -["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20056,], -["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20056,], -["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20056,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20056,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20056,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20056,], -["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",20056,], +["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20063,], +["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20063,], +["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20063,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20063,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20063,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20063,], +["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",20063,], ["libraries.designsystem.theme.components_DialogWithDestructiveButton_Dialog_with_destructive_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithOnlyMessageAndOkButton_Dialog_with_only_message_and_ok_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithThirdButton_Dialog_with_third_button_Dialogs_en","",0,], @@ -291,12 +293,12 @@ export const screenshots = [ ["libraries.designsystem.text_DpScale_1_0f__en","",0,], ["libraries.designsystem.text_DpScale_1_5f__en","",0,], ["libraries.designsystem.theme.components_DropdownMenuItem_Menus_en","",0,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20056,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20056,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20056,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20056,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20056,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20056,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20063,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20063,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20063,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20063,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20063,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20063,], ["libraries.matrix.ui.components_EditableAvatarView_Day_0_en","libraries.matrix.ui.components_EditableAvatarView_Night_0_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_1_en","libraries.matrix.ui.components_EditableAvatarView_Night_1_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_2_en","libraries.matrix.ui.components_EditableAvatarView_Night_2_en",0,], @@ -306,9 +308,9 @@ export const screenshots = [ ["libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiItem_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiItem_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiPicker_Night_0_en",0,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20056,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20056,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20056,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20063,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20063,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20063,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], ["libraries.designsystem.theme.components_FilledButtonLargeLowPadding_Buttons_en","",0,], @@ -323,15 +325,15 @@ export const screenshots = [ ["libraries.designsystem.theme.components_FloatingActionButton_Floating_Action_Buttons_en","",0,], ["libraries.designsystem.atomic.pages_FlowStepPage_Day_0_en","libraries.designsystem.atomic.pages_FlowStepPage_Night_0_en",0,], ["features.messages.impl.timeline.focus_FocusRequestStateView_Day_0_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_0_en",0,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20056,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20056,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20056,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20063,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20063,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20063,], ["libraries.textcomposer.components_FormattingOption_Day_0_en","libraries.textcomposer.components_FormattingOption_Night_0_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_0_en","features.messages.impl.forward_ForwardMessagesView_Night_0_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_1_en","features.messages.impl.forward_ForwardMessagesView_Night_1_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_2_en","features.messages.impl.forward_ForwardMessagesView_Night_2_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20056,], -["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20056,], +["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20063,], +["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20063,], ["libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Night_0_en",0,], ["libraries.designsystem.components.button_GradientFloatingActionButton_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButton_Night_0_en",0,], ["features.messages.impl.timeline.components.group_GroupHeaderView_Day_0_en","features.messages.impl.timeline.components.group_GroupHeaderView_Night_0_en",0,], @@ -353,53 +355,63 @@ export const screenshots = [ ["libraries.designsystem.icons_IconsCompound_Day_5_en","libraries.designsystem.icons_IconsCompound_Night_5_en",0,], ["libraries.designsystem.icons_IconsOther_Day_0_en","libraries.designsystem.icons_IconsOther_Night_0_en",0,], ["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_0_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_0_en",0,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20056,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20056,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20063,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20063,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_0_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_10_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_10_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_11_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_11_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_1_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_1_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_2_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_2_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_3_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_3_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20056,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20063,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_5_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_5_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_6_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_6_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_7_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_7_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20056,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20063,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_9_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_9_en",0,], -["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20059,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20056,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20056,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20063,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20063,], ["libraries.designsystem.atomic.molecules_InfoListItemMolecule_Day_0_en","libraries.designsystem.atomic.molecules_InfoListItemMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.organisms_InfoListOrganism_Day_0_en","libraries.designsystem.atomic.organisms_InfoListOrganism_Night_0_en",0,], -["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20056,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20063,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20059,], -["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20059,], -["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20059,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20056,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20056,], +["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20063,], ["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",0,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20066,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20066,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], ["features.leaveroom.api_LeaveRoomView_Day_0_en","features.leaveroom.api_LeaveRoomView_Night_0_en",0,], -["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",20056,], -["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",20056,], -["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",20056,], -["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",20056,], -["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",20056,], -["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",20056,], +["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",20063,], +["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",20063,], +["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",20063,], +["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",20063,], +["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",20063,], +["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",20063,], ["libraries.designsystem.background_LightGradientBackground_Day_0_en","libraries.designsystem.background_LightGradientBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_LinearProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en","",0,], @@ -456,29 +468,29 @@ export const screenshots = [ ["libraries.designsystem.theme.components_ListSupportingTextSmallPadding_List_supporting_text_-_small_padding_List_sections_en","",0,], ["libraries.textcomposer.components_LiveWaveformView_Day_0_en","libraries.textcomposer.components_LiveWaveformView_Night_0_en",0,], ["appnav.room.joined_LoadingRoomNodeView_Day_0_en","appnav.room.joined_LoadingRoomNodeView_Night_0_en",0,], -["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20056,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20056,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20056,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20056,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20063,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20063,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20063,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20063,], ["appnav.loggedin_LoggedInView_Day_0_en","appnav.loggedin_LoggedInView_Night_0_en",0,], -["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20056,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20056,], -["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20056,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20056,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20056,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20056,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20056,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20056,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20056,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20056,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20056,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20056,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20056,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20056,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20056,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20056,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20063,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20063,], +["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20063,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20063,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20063,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20063,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20063,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20063,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20063,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20063,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20063,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20063,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20063,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20063,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20063,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20063,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20056,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20063,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNeutral_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNeutral_Night_0_en",0,], @@ -488,24 +500,28 @@ export const screenshots = [ ["libraries.matrix.ui.components_MatrixUserHeader_Day_1_en","libraries.matrix.ui.components_MatrixUserHeader_Night_1_en",0,], ["libraries.matrix.ui.components_MatrixUserRow_Day_0_en","libraries.matrix.ui.components_MatrixUserRow_Night_0_en",0,], ["libraries.matrix.ui.components_MatrixUserRow_Day_1_en","libraries.matrix.ui.components_MatrixUserRow_Night_1_en",0,], -["libraries.mediaviewer.api.player_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.api.player_MediaPlayerControllerView_Night_0_en",0,], -["libraries.mediaviewer.api.player_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.api.player_MediaPlayerControllerView_Night_1_en",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_0_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_10_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_2_en","",20056,], -["libraries.mediaviewer.api.viewer_MediaViewerView_3_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_4_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_5_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_6_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_7_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_8_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_9_en","",0,], +["libraries.mediaviewer.impl.local.file_MediaFileView_Day_0_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_0_en",0,], +["libraries.mediaviewer.impl.local.file_MediaFileView_Day_1_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_1_en",0,], +["libraries.mediaviewer.impl.local.image_MediaImageView_Day_0_en","libraries.mediaviewer.impl.local.image_MediaImageView_Night_0_en",0,], +["libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_0_en",0,], +["libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_1_en",0,], +["libraries.mediaviewer.impl.local.video_MediaVideoView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaVideoView_Night_0_en",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_0_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_10_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_1_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20066,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_3_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_4_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_5_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_6_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_7_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_8_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_9_en","",0,], ["libraries.designsystem.theme.components_MediumTopAppBar_App_Bars_en","",0,], ["libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en","libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en",0,], ["libraries.designsystem.theme.components.previews_Menu_Menus_en","",0,], ["features.messages.impl.messagecomposer_MessageComposerViewVoice_Day_0_en","features.messages.impl.messagecomposer_MessageComposerViewVoice_Night_0_en",0,], -["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20056,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20063,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_0_en","features.messages.impl.timeline.components_MessageEventBubble_Night_0_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_10_en","features.messages.impl.timeline.components_MessageEventBubble_Night_10_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_11_en","features.messages.impl.timeline.components_MessageEventBubble_Night_11_en",0,], @@ -522,7 +538,7 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessageEventBubble_Day_7_en","features.messages.impl.timeline.components_MessageEventBubble_Night_7_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_8_en","features.messages.impl.timeline.components_MessageEventBubble_Night_8_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_9_en","features.messages.impl.timeline.components_MessageEventBubble_Night_9_en",0,], -["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20056,], +["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20063,], ["features.messages.impl.timeline.components_MessageStateEventContainer_Day_0_en","features.messages.impl.timeline.components_MessageStateEventContainer_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonAdd_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonAdd_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonExtra_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonExtra_Night_0_en",0,], @@ -530,23 +546,23 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessagesReactionButton_Day_1_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_1_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_2_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_2_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_3_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_3_en",0,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20056,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20056,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20056,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20056,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20056,], -["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20056,], -["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20056,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20056,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20056,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20056,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20056,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20056,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20056,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20056,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20056,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20063,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20063,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20063,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20063,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20063,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20063,], +["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20063,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20063,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20063,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20063,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20063,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20063,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20063,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20063,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20063,], ["features.migration.impl_MigrationView_Day_0_en","features.migration.impl_MigrationView_Night_0_en",0,], -["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20056,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20063,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom_Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom_Sheets_en","",0,], ["appicon.element_MonochromeIcon_en","",0,], @@ -555,29 +571,29 @@ export const screenshots = [ ["libraries.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_Multiple_selection_List_item_-_selection_in_trailing_content_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItemSelected_Multiple_selection_List_item_-_selection_in_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItem_Multiple_selection_List_item_-_no_selection_List_items_en","",0,], -["features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Day_0_en","features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Night_0_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20056,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20056,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20056,], +["features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Day_0_en","features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Night_0_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20063,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20063,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20063,], ["libraries.oidc.impl.webview_OidcView_Day_0_en","libraries.oidc.impl.webview_OidcView_Night_0_en",0,], ["libraries.oidc.impl.webview_OidcView_Day_1_en","libraries.oidc.impl.webview_OidcView_Night_1_en",0,], ["libraries.designsystem.atomic.pages_OnBoardingPage_Day_0_en","libraries.designsystem.atomic.pages_OnBoardingPage_Night_0_en",0,], -["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",20056,], -["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",20056,], -["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",20056,], -["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",20056,], -["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",20056,], +["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",20063,], +["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",20063,], +["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",20063,], +["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",20063,], +["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",20063,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_OutlinedButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], @@ -589,66 +605,67 @@ export const screenshots = [ ["libraries.designsystem.components_PageTitleWithIconFull_Day_2_en","libraries.designsystem.components_PageTitleWithIconFull_Night_2_en",0,], ["libraries.designsystem.components_PageTitleWithIconFull_Day_3_en","libraries.designsystem.components_PageTitleWithIconFull_Night_3_en",0,], ["libraries.designsystem.components_PageTitleWithIconFull_Day_4_en","libraries.designsystem.components_PageTitleWithIconFull_Night_4_en",0,], +["libraries.designsystem.components_PageTitleWithIconFull_Day_5_en","libraries.designsystem.components_PageTitleWithIconFull_Night_5_en",0,], ["libraries.designsystem.components_PageTitleWithIconMinimal_Day_0_en","libraries.designsystem.components_PageTitleWithIconMinimal_Night_0_en",0,], -["libraries.mediaviewer.api.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.api.local.pdf_PdfPagesErrorView_Night_0_en",20056,], -["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",20056,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20056,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20056,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20056,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20056,], +["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20066,], +["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",20063,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20063,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20063,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20063,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20063,], ["features.lockscreen.impl.components_PinEntryTextField_Day_0_en","features.lockscreen.impl.components_PinEntryTextField_Night_0_en",0,], ["libraries.designsystem.components_PinIcon_Day_0_en","libraries.designsystem.components_PinIcon_Night_0_en",0,], ["features.lockscreen.impl.unlock.keypad_PinKeypad_Day_0_en","features.lockscreen.impl.unlock.keypad_PinKeypad_Night_0_en",0,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20056,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20056,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20063,], ["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_0_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_0_en",0,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20056,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20056,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20056,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20056,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20056,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20056,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20063,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20063,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20063,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20063,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20063,], ["libraries.designsystem.atomic.atoms_PlaceholderAtom_Day_0_en","libraries.designsystem.atomic.atoms_PlaceholderAtom_Night_0_en",0,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20056,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20056,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20056,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20056,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20056,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20063,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20063,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20063,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20063,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20063,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en",0,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Night_0_en",0,], -["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20056,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20056,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20056,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20056,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20056,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20056,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20056,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20056,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20056,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20056,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20056,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20063,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20063,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20063,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20063,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20063,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20063,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20063,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20063,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20063,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20063,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20063,], ["features.poll.api.pollcontent_PollTitleView_Day_0_en","features.poll.api.pollcontent_PollTitleView_Night_0_en",0,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], @@ -665,197 +682,197 @@ export const screenshots = [ ["libraries.designsystem.components.preferences_PreferenceTextLight_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceTextWithEndBadgeDark_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceTextWithEndBadgeLight_Preferences_en","",0,], -["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20056,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20056,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20056,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20056,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20063,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20063,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20063,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20063,], ["features.messages.impl.timeline.components.event_ProgressButton_Day_0_en","features.messages.impl.timeline.components.event_ProgressButton_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20056,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20056,], -["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20059,], -["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20059,], -["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20059,], -["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20059,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20056,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20056,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20056,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20056,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20056,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20056,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20056,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20056,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20056,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20056,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20063,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20063,], +["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20063,], +["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20063,], +["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20063,], +["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20063,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20063,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20063,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20063,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20063,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20063,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20063,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20063,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20063,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20063,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20063,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20056,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20056,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20063,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20063,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], ["features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Night_0_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20056,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20056,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20056,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20056,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20056,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20056,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20056,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20063,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20063,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20063,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20063,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20063,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20063,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20063,], ["libraries.designsystem.atomic.atoms_RedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_RedIndicatorAtom_Night_0_en",0,], ["features.messages.impl.timeline.components_ReplySwipeIndicator_Day_0_en","features.messages.impl.timeline.components_ReplySwipeIndicator_Night_0_en",0,], -["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20056,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20056,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20056,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20056,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20056,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20056,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20056,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20056,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20056,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20056,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20056,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20056,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20063,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20063,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20063,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20063,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20063,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20063,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20063,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20063,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20063,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20063,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20063,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20063,], ["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_0_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_0_en",0,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20056,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20056,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20056,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20056,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20056,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20063,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20063,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20063,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20063,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20063,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_0_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_0_en",0,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",0,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20056,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_13_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",20056,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20056,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20056,], -["features.roomdetails.impl_RoomDetails_0_en","",20056,], -["features.roomdetails.impl_RoomDetails_10_en","",20056,], -["features.roomdetails.impl_RoomDetails_11_en","",20056,], -["features.roomdetails.impl_RoomDetails_12_en","",20056,], -["features.roomdetails.impl_RoomDetails_13_en","",20056,], -["features.roomdetails.impl_RoomDetails_1_en","",20056,], -["features.roomdetails.impl_RoomDetails_2_en","",20056,], -["features.roomdetails.impl_RoomDetails_3_en","",20056,], -["features.roomdetails.impl_RoomDetails_4_en","",20056,], -["features.roomdetails.impl_RoomDetails_5_en","",20056,], -["features.roomdetails.impl_RoomDetails_6_en","",20056,], -["features.roomdetails.impl_RoomDetails_7_en","",20056,], -["features.roomdetails.impl_RoomDetails_8_en","",20056,], -["features.roomdetails.impl_RoomDetails_9_en","",20056,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20056,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20056,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",20056,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",20056,], -["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",20056,], -["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",20056,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20063,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_13_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",20063,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20063,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20063,], +["features.roomdetails.impl_RoomDetails_0_en","",20063,], +["features.roomdetails.impl_RoomDetails_10_en","",20063,], +["features.roomdetails.impl_RoomDetails_11_en","",20063,], +["features.roomdetails.impl_RoomDetails_12_en","",20063,], +["features.roomdetails.impl_RoomDetails_13_en","",20063,], +["features.roomdetails.impl_RoomDetails_1_en","",20063,], +["features.roomdetails.impl_RoomDetails_2_en","",20063,], +["features.roomdetails.impl_RoomDetails_3_en","",20063,], +["features.roomdetails.impl_RoomDetails_4_en","",20063,], +["features.roomdetails.impl_RoomDetails_5_en","",20063,], +["features.roomdetails.impl_RoomDetails_6_en","",20063,], +["features.roomdetails.impl_RoomDetails_7_en","",20063,], +["features.roomdetails.impl_RoomDetails_8_en","",20063,], +["features.roomdetails.impl_RoomDetails_9_en","",20063,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20063,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20063,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",20063,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",20063,], +["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",20063,], +["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",20063,], ["features.roomlist.impl.components_RoomListContentView_Day_2_en","features.roomlist.impl.components_RoomListContentView_Night_2_en",0,], -["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",20056,], -["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",20056,], -["features.roomlist.impl.components_RoomListContentView_Day_5_en","features.roomlist.impl.components_RoomListContentView_Night_5_en",20056,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",20056,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",20056,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",20056,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",20056,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",20056,], +["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",20063,], +["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",20063,], +["features.roomlist.impl.components_RoomListContentView_Day_5_en","features.roomlist.impl.components_RoomListContentView_Night_5_en",20063,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",20063,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",20063,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",20063,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",20063,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",20063,], ["features.roomlist.impl.search_RoomListSearchContent_Day_0_en","features.roomlist.impl.search_RoomListSearchContent_Night_0_en",0,], -["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",20056,], -["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",20056,], -["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",20056,], -["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",20056,], -["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",20056,], -["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",20056,], -["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",20056,], -["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",20056,], -["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",20056,], -["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",20056,], -["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",20056,], +["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",20063,], +["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",20063,], +["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",20063,], +["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",20063,], +["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",20063,], +["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",20063,], +["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",20063,], +["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",20063,], +["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",20063,], +["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",20063,], +["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",20063,], ["features.roomlist.impl_RoomListView_Day_8_en","features.roomlist.impl_RoomListView_Night_8_en",0,], ["features.roomlist.impl_RoomListView_Day_9_en","features.roomlist.impl_RoomListView_Night_9_en",0,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20056,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20056,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20056,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20063,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20063,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20063,], ["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",0,], -["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20056,], -["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20056,], +["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20063,], ["libraries.designsystem.atomic.molecules_RoomMembersCountMolecule_Day_0_en","libraries.designsystem.atomic.molecules_RoomMembersCountMolecule_Night_0_en",0,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",20056,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",20056,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",20056,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",20056,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",20056,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",20063,], ["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",0,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",20056,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",20056,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",20056,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",20063,], ["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en",0,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20056,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20056,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20056,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20056,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20056,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20056,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20056,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20056,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20063,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20063,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20063,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20063,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20063,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20063,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20063,], ["features.roomlist.impl.components_RoomSummaryPlaceholderRow_Day_0_en","features.roomlist.impl.components_RoomSummaryPlaceholderRow_Night_0_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_0_en","features.roomlist.impl.components_RoomSummaryRow_Night_0_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_10_en","features.roomlist.impl.components_RoomSummaryRow_Night_10_en",0,], @@ -878,12 +895,12 @@ export const screenshots = [ ["features.roomlist.impl.components_RoomSummaryRow_Day_26_en","features.roomlist.impl.components_RoomSummaryRow_Night_26_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_27_en","features.roomlist.impl.components_RoomSummaryRow_Night_27_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_28_en","features.roomlist.impl.components_RoomSummaryRow_Night_28_en",0,], -["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",20056,], -["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",20056,], -["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",20056,], -["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",20056,], -["features.roomlist.impl.components_RoomSummaryRow_Day_32_en","features.roomlist.impl.components_RoomSummaryRow_Night_32_en",20059,], -["features.roomlist.impl.components_RoomSummaryRow_Day_33_en","features.roomlist.impl.components_RoomSummaryRow_Night_33_en",20059,], +["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",20063,], +["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",20063,], +["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",20063,], +["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",20063,], +["features.roomlist.impl.components_RoomSummaryRow_Day_32_en","features.roomlist.impl.components_RoomSummaryRow_Night_32_en",20063,], +["features.roomlist.impl.components_RoomSummaryRow_Day_33_en","features.roomlist.impl.components_RoomSummaryRow_Night_33_en",20063,], ["features.roomlist.impl.components_RoomSummaryRow_Day_3_en","features.roomlist.impl.components_RoomSummaryRow_Night_3_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_4_en","features.roomlist.impl.components_RoomSummaryRow_Night_4_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_5_en","features.roomlist.impl.components_RoomSummaryRow_Night_5_en",0,], @@ -891,59 +908,59 @@ export const screenshots = [ ["features.roomlist.impl.components_RoomSummaryRow_Day_7_en","features.roomlist.impl.components_RoomSummaryRow_Night_7_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_8_en","features.roomlist.impl.components_RoomSummaryRow_Night_8_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_9_en","features.roomlist.impl.components_RoomSummaryRow_Night_9_en",0,], -["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20056,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20056,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20056,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20063,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20063,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20063,], ["appicon.enterprise_RoundIcon_en","",0,], ["appicon.element_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20056,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20056,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20056,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20063,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20063,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20063,], ["libraries.designsystem.theme.components_SearchBarActiveNoneQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithContent_Search_views_en","",0,], -["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20056,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20063,], ["libraries.designsystem.theme.components_SearchBarActiveWithQueryNoBackButton_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarInactive_Search_views_en","",0,], -["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",20056,], -["features.createroom.impl.components_SearchSingleUserResultItem_en","",20056,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20056,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20056,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20056,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20056,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20056,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20056,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20056,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20056,], -["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20056,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20056,], +["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",20063,], +["features.createroom.impl.components_SearchSingleUserResultItem_en","",20063,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20063,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20063,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20063,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20063,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20063,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20063,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20063,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20063,], +["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20063,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20063,], ["libraries.matrix.ui.components_SelectedRoom_Day_0_en","libraries.matrix.ui.components_SelectedRoom_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoom_Day_1_en","libraries.matrix.ui.components_SelectedRoom_Night_1_en",0,], ["libraries.matrix.ui.components_SelectedRoom_Day_2_en","libraries.matrix.ui.components_SelectedRoom_Night_2_en",0,], @@ -951,11 +968,11 @@ export const screenshots = [ ["libraries.matrix.ui.components_SelectedUser_Day_0_en","libraries.matrix.ui.components_SelectedUser_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedUsersRowList_Day_0_en","libraries.matrix.ui.components_SelectedUsersRowList_Night_0_en",0,], ["libraries.textcomposer.components_SendButton_Day_0_en","libraries.textcomposer.components_SendButton_Night_0_en",0,], -["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20056,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20056,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20056,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20056,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20056,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20063,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20063,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20063,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20063,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20063,], ["libraries.matrix.ui.messages.sender_SenderName_Day_0_en","libraries.matrix.ui.messages.sender_SenderName_Night_0_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_1_en","libraries.matrix.ui.messages.sender_SenderName_Night_1_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_2_en","libraries.matrix.ui.messages.sender_SenderName_Night_2_en",0,], @@ -965,27 +982,27 @@ export const screenshots = [ ["libraries.matrix.ui.messages.sender_SenderName_Day_6_en","libraries.matrix.ui.messages.sender_SenderName_Night_6_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_7_en","libraries.matrix.ui.messages.sender_SenderName_Night_7_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_8_en","libraries.matrix.ui.messages.sender_SenderName_Night_8_en",0,], -["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20059,], -["features.roomlist.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20056,], -["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20056,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20056,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20056,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20056,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20056,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20056,], +["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20063,], +["features.roomlist.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20063,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20063,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20063,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20063,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20063,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20063,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20063,], ["features.share.impl_ShareView_Day_0_en","features.share.impl_ShareView_Night_0_en",0,], ["features.share.impl_ShareView_Day_1_en","features.share.impl_ShareView_Night_1_en",0,], ["features.share.impl_ShareView_Day_2_en","features.share.impl_ShareView_Night_2_en",0,], -["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20056,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20056,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20056,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20056,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20056,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20056,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20056,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20056,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20056,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20056,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20063,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20063,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20063,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20063,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20063,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20063,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20063,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20063,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20063,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20063,], ["libraries.designsystem.components.dialogs_SingleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_SingleSelectionDialog_Night_0_en",0,], ["libraries.designsystem.components.list_SingleSelectionListItemCustomFormattert_Single_selection_List_item_-_custom_formatter_List_items_en","",0,], @@ -994,7 +1011,7 @@ export const screenshots = [ ["libraries.designsystem.components.list_SingleSelectionListItemUnselectedWithSupportingText_Single_selection_List_item_-_no_selection,_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_SingleSelectionListItem_Single_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_Sliders_Sliders_en","",0,], -["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20056,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20063,], ["libraries.designsystem.theme.components_SnackbarWithActionAndCloseButton_Snackbar_with_action_and_close_button_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLineAndCloseButton_Snackbar_with_action_and_close_button_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLine_Snackbar_with_action_on_new_line_Snackbars_en","",0,], @@ -1004,40 +1021,40 @@ export const screenshots = [ ["libraries.designsystem.modifiers_SquareSizeModifierLargeHeight_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeWidth_en","",0,], ["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",0,], -["features.location.api.internal_StaticMapPlaceholder_Day_1_en","features.location.api.internal_StaticMapPlaceholder_Night_1_en",20056,], +["features.location.api.internal_StaticMapPlaceholder_Day_1_en","features.location.api.internal_StaticMapPlaceholder_Night_1_en",20063,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], -["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20056,], +["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20063,], ["libraries.designsystem.atomic.pages_SunsetPage_Day_0_en","libraries.designsystem.atomic.pages_SunsetPage_Night_0_en",0,], ["libraries.designsystem.components.button_SuperButton_Day_0_en","libraries.designsystem.components.button_SuperButton_Night_0_en",0,], ["libraries.designsystem.theme.components_Surface_en","",0,], ["libraries.designsystem.theme.components_Switch_Toggles_en","",0,], -["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20056,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20063,], ["libraries.designsystem.theme.components_TextButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20056,], -["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20059,], -["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20056,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20056,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20056,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20056,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20056,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20056,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20056,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20056,], +["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20063,], +["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20063,], +["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20063,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20063,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20063,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20063,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20063,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20063,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20063,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20063,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], ["libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en","",0,], @@ -1047,14 +1064,14 @@ export const screenshots = [ ["libraries.designsystem.theme.components_TextFieldsLight_TextFields_en","",0,], ["libraries.textcomposer.components_TextFormatting_Day_0_en","libraries.textcomposer.components_TextFormatting_Night_0_en",0,], ["libraries.designsystem.theme.components_TextLight_Text_en","",0,], -["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20056,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20056,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20056,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20063,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20063,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20063,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_0_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_1_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_2_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20056,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20056,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20063,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20063,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_5_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_6_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_6_en",0,], ["features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en",0,], @@ -1063,14 +1080,17 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20056,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20063,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_0_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_1_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20059,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20059,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20056,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20066,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20066,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20066,], ["features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en","",0,], @@ -1078,17 +1098,17 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20056,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20056,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20063,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20056,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20056,], +["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20063,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20056,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20056,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20063,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en",0,], @@ -1097,40 +1117,40 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20056,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20063,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20056,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20063,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20056,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20063,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20056,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20059,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20063,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemInformativeView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemInformativeView_Night_0_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20059,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20063,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20056,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20056,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20063,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20063,], ["features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20056,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20056,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20063,], ["features.messages.impl.timeline.components_TimelineItemReactionsView_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsView_Night_0_en",0,], -["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20056,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20063,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_0_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_0_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_1_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_1_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_2_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_2_en",0,], @@ -1139,8 +1159,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_5_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_5_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_6_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_6_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_7_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_7_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20056,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20056,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20063,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20063,], ["features.messages.impl.timeline.components_TimelineItemStateEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemStateEventRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStateView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStateView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStickerView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStickerView_Night_0_en",0,], @@ -1153,8 +1173,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_4_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_5_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20056,], -["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20059,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20063,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_2_en",0,], @@ -1177,84 +1197,84 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemVoiceView_Day_9_en","features.messages.impl.timeline.components.event_TimelineItemVoiceView_Night_9_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en",0,], -["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20056,], +["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20063,], ["features.messages.impl.timeline_TimelineView_Day_10_en","features.messages.impl.timeline_TimelineView_Night_10_en",0,], -["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20056,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20056,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20063,], ["features.messages.impl.timeline_TimelineView_Day_2_en","features.messages.impl.timeline_TimelineView_Night_2_en",0,], ["features.messages.impl.timeline_TimelineView_Day_3_en","features.messages.impl.timeline_TimelineView_Night_3_en",0,], -["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20056,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20063,], ["features.messages.impl.timeline_TimelineView_Day_5_en","features.messages.impl.timeline_TimelineView_Night_5_en",0,], -["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20056,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20063,], ["features.messages.impl.timeline_TimelineView_Day_7_en","features.messages.impl.timeline_TimelineView_Night_7_en",0,], -["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20056,], +["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20063,], ["features.messages.impl.timeline_TimelineView_Day_9_en","features.messages.impl.timeline_TimelineView_Night_9_en",0,], ["libraries.designsystem.theme.components_TopAppBar_App_Bars_en","",0,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20056,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20056,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20063,], ["features.messages.impl.typing_TypingNotificationView_Day_0_en","features.messages.impl.typing_TypingNotificationView_Night_0_en",0,], -["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20056,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20056,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20056,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20056,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20056,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20056,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20063,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20063,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20063,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20063,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20063,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20063,], ["features.messages.impl.typing_TypingNotificationView_Day_7_en","features.messages.impl.typing_TypingNotificationView_Night_7_en",0,], ["features.messages.impl.typing_TypingNotificationView_Day_8_en","features.messages.impl.typing_TypingNotificationView_Night_8_en",0,], ["libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en",0,], -["libraries.matrix.ui.components_UnresolvedUserRow_en","",20056,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",20063,], ["libraries.matrix.ui.components_UnsavedAvatar_Day_0_en","libraries.matrix.ui.components_UnsavedAvatar_Night_0_en",0,], ["libraries.designsystem.components.avatar_UserAvatarColors_Day_0_en","libraries.designsystem.components.avatar_UserAvatarColors_Night_0_en",0,], -["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20056,], -["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",20056,], -["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",20056,], -["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",20056,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20063,], +["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",20063,], +["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",20063,], +["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",20063,], ["features.createroom.impl.components_UserListView_Day_3_en","features.createroom.impl.components_UserListView_Night_3_en",0,], ["features.createroom.impl.components_UserListView_Day_4_en","features.createroom.impl.components_UserListView_Night_4_en",0,], ["features.createroom.impl.components_UserListView_Day_5_en","features.createroom.impl.components_UserListView_Night_5_en",0,], ["features.createroom.impl.components_UserListView_Day_6_en","features.createroom.impl.components_UserListView_Night_6_en",0,], -["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",20056,], +["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",20063,], ["features.createroom.impl.components_UserListView_Day_8_en","features.createroom.impl.components_UserListView_Night_8_en",0,], -["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",20056,], +["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",20063,], ["features.preferences.impl.user_UserPreferences_Day_0_en","features.preferences.impl.user_UserPreferences_Night_0_en",0,], ["features.preferences.impl.user_UserPreferences_Day_1_en","features.preferences.impl.user_UserPreferences_Night_1_en",0,], ["features.preferences.impl.user_UserPreferences_Day_2_en","features.preferences.impl.user_UserPreferences_Night_2_en",0,], -["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20059,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20056,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20056,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20056,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20056,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20056,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20056,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20056,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en",20056,], +["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20063,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20063,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20063,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20063,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20063,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20063,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20063,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20063,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en",20063,], ["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en",0,], ["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_12_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_12_en",0,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en",20056,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en",20056,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en",20063,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], @@ -1269,6 +1289,6 @@ export const screenshots = [ ["libraries.textcomposer.components_VoiceMessageRecording_Day_0_en","libraries.textcomposer.components_VoiceMessageRecording_Night_0_en",0,], ["libraries.textcomposer.components_VoiceMessage_Day_0_en","libraries.textcomposer.components_VoiceMessage_Night_0_en",0,], ["libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en","libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en",0,], -["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",20056,], +["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",20063,], ["libraries.designsystem.ruler_WithRulers_Day_0_en","libraries.designsystem.ruler_WithRulers_Night_0_en",0,], ]; From e383c7f9074d251db906eb677865a84bddfffbb5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 11:01:42 +0100 Subject: [PATCH 033/203] knock requests : use proper banner string resources --- .../knockrequests/impl/banner/KnockRequestsBannerState.kt | 7 +++---- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 7 ++++--- .../knockrequests/impl/src/main/res/values/localazy.xml | 8 ++++++++ libraries/ui-strings/src/main/res/values/localazy.xml | 8 -------- tools/localazy/config.json | 4 +++- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 4d73ec8748..5ad89b239a 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -11,10 +11,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getBestName import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.ui.strings.CommonPlurals -import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList data class KnockRequestsBannerState( @@ -40,12 +39,12 @@ data class KnockRequestsBannerState( fun formattedTitle(): String { return when (knockRequests.size) { 0 -> "" - 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) + 1 -> stringResource(R.string.screen_room_single_knock_request_title, knockRequests.first().getBestName()) else -> { val firstRequest = knockRequests.first() val otherRequestsCount = knockRequests.size - 1 pluralStringResource( - id = CommonPlurals.screen_room_multiple_knock_requests_title, + id = R.plurals.screen_room_multiple_knock_requests_title, count = otherRequestsCount, firstRequest.getBestName(), otherRequestsCount diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 024efd2985..4ef26d460e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.zIndex import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getAvatarData import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -133,21 +134,21 @@ private fun KnockRequestsBannerContent( Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { if (state.knockRequests.size > 1) { Button( - text = "View all", + text = stringResource(R.string.screen_room_multiple_knock_requests_view_all_button_title), onClick = onViewRequestsClick, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), ) } else { OutlinedButton( - text = "View", + text = stringResource(R.string.screen_room_single_knock_request_view_button_title), onClick = onViewRequestsClick, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), ) if (state.canAccept) { Button( - text = "Accept", + text = stringResource(R.string.screen_room_single_knock_request_accept_button_title), onClick = {}, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), diff --git a/features/knockrequests/impl/src/main/res/values/localazy.xml b/features/knockrequests/impl/src/main/res/values/localazy.xml index df14d665b8..d2e9dc5d14 100644 --- a/features/knockrequests/impl/src/main/res/values/localazy.xml +++ b/features/knockrequests/impl/src/main/res/values/localazy.xml @@ -14,4 +14,12 @@ "When somebody will ask to join the room, you’ll be able to see their request here." "No pending request to join" "Requests to join" + + "%1$s +%2$d other want to join this room" + "%1$s +%2$d others want to join this room" + + "View all" + "Accept" + "%1$s wants to join this room" + "View" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 75b0f6853a..6e8569cae1 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -322,18 +322,10 @@ Reason: %1$s." "Your message was not sent because you have not verified one or more of your devices" "Failed processing media to upload, please try again." "Could not retrieve user details" - - "%1$s +%2$d other want to join this room" - "%1$s +%2$d others want to join this room" - - "View all" "%1$s of %2$s" "%1$s Pinned messages" "Loading message…" "View All" - "Accept" - "%1$s wants to join this room" - "View" "Chat" "Request to join sent" "Share location" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index fe95d1930e..a365379344 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -291,7 +291,9 @@ { "name" : ":features:knockrequests:impl", "includeRegex" : [ - "screen\\.knock_requests_list\\..*" + "screen\\.knock_requests_list\\..*", + "screen\\.room\\.single_knock_request.*", + "screen\\.room\\.multiple_knock_requests.*" ] } ] From cc9365a2c4c3abd796e2ed44b31a292809942691 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 11:18:04 +0100 Subject: [PATCH 034/203] misc : introduce List.firstIfSingle extension --- .../impl/banner/KnockRequestsBannerState.kt | 14 +++----------- .../android/libraries/core/extensions/List.kt | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 5ad89b239a..e65cd2fb26 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -14,6 +14,7 @@ import io.element.android.features.knockrequests.impl.KnockRequest import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getBestName import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.extensions.firstIfSingle import kotlinx.collections.immutable.ImmutableList data class KnockRequestsBannerState( @@ -23,17 +24,8 @@ data class KnockRequestsBannerState( val canAccept: Boolean, val eventSink: (KnockRequestsBannerEvents) -> Unit, ) { - val subtitle = if (knockRequests.size == 1) { - knockRequests.first().userId.value - } else { - null - } - - val reason = if (knockRequests.size == 1) { - knockRequests.first().reason - } else { - null - } + val subtitle = knockRequests.firstIfSingle()?.userId?.value + val reason = knockRequests.firstIfSingle()?.reason @Composable fun formattedTitle(): String { diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt new file mode 100644 index 0000000000..0dee04408a --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.core.extensions + +/** + * Returns the first element if the list contains exactly one element, otherwise returns null. + */ +inline fun List.firstIfSingle(): T? { + return if (size == 1) first() else null +} From 364a374292f587780d7ce23c1e6393f58ba8a0c1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 11:22:53 +0100 Subject: [PATCH 035/203] knock request : emit accept single request from banner --- .../knockrequests/impl/banner/KnockRequestsBannerEvents.kt | 4 +--- .../impl/banner/KnockRequestsBannerPresenter.kt | 2 +- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 6 +++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt index 28938a2c26..14239d93ef 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt @@ -7,9 +7,7 @@ package io.element.android.features.knockrequests.impl.banner -import io.element.android.features.knockrequests.impl.KnockRequest - sealed interface KnockRequestsBannerEvents { - data class Accept(val knockRequest: KnockRequest) : KnockRequestsBannerEvents + data object AcceptSingleRequest : KnockRequestsBannerEvents data object Dismiss : KnockRequestsBannerEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index 513042fc37..a61236bbb0 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -24,7 +24,7 @@ class KnockRequestsBannerPresenter @Inject constructor() : Presenter Unit + is KnockRequestsBannerEvents.AcceptSingleRequest -> Unit is KnockRequestsBannerEvents.Dismiss -> { shouldShowBanner = false } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 4ef26d460e..402b8ba182 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -90,6 +90,10 @@ private fun KnockRequestsBannerContent( state.eventSink(KnockRequestsBannerEvents.Dismiss) } + fun onAcceptClick() { + state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + } + Column( modifier .fillMaxWidth() @@ -149,7 +153,7 @@ private fun KnockRequestsBannerContent( if (state.canAccept) { Button( text = stringResource(R.string.screen_room_single_knock_request_accept_button_title), - onClick = {}, + onClick = ::onAcceptClick, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), ) From 3e1b1c29d1c579aba19f7b9ad49813f669616a49 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2024 16:45:46 +0100 Subject: [PATCH 036/203] Media Gallery --- .../messages/impl/MessagesFlowNode.kt | 24 +- .../roomdetails/impl/RoomDetailsFlowNode.kt | 37 +- .../roomdetails/impl/RoomDetailsNode.kt | 6 + .../roomdetails/impl/RoomDetailsPresenter.kt | 6 + .../roomdetails/impl/RoomDetailsState.kt | 1 + .../impl/RoomDetailsStateProvider.kt | 2 + .../roomdetails/impl/RoomDetailsView.kt | 21 +- .../userprofile/impl/UserProfileFlowNode.kt | 5 + .../core/extensions/BasicExtensions.kt | 8 + .../libraries/core/preview/PreviewUtil.kt | 16 + .../components/avatar/AvatarSize.kt | 2 + .../libraries/featureflag/api/FeatureFlags.kt | 7 + .../libraries/matrix/api/room/MatrixRoom.kt | 5 + .../libraries/matrix/api/timeline/Timeline.kt | 3 +- .../matrix/impl/room/RustMatrixRoom.kt | 21 + .../matrix/impl/timeline/RustTimeline.kt | 42 +- .../matrix/test/room/FakeMatrixRoom.kt | 5 + .../mediaviewer/api/MediaGalleryEntryPoint.kt | 28 ++ .../libraries/mediaviewer/api/MediaInfo.kt | 21 +- .../mediaviewer/api/MediaViewerEntryPoint.kt | 4 + libraries/mediaviewer/impl/build.gradle.kts | 6 + .../impl/DefaultMediaGalleryEntryPoint.kt | 36 ++ .../impl/DefaultMediaViewerEntryPoint.kt | 5 + .../impl/details/MediaBottomSheetState.kt | 29 ++ .../MediaDeleteConfirmationBottomSheet.kt | 175 +++++++ .../impl/details/MediaDetailsBottomSheet.kt | 210 +++++++++ .../impl/gallery/EventItemFactory.kt | 194 ++++++++ .../impl/gallery/MediaGalleryEvents.kt | 31 ++ .../impl/gallery/MediaGalleryNavigator.kt | 14 + .../impl/gallery/MediaGalleryNode.kt | 67 +++ .../impl/gallery/MediaGalleryPresenter.kt | 264 +++++++++++ .../impl/gallery/MediaGalleryState.kt | 29 ++ .../impl/gallery/MediaGalleryStateProvider.kt | 77 ++++ .../gallery/MediaGalleryTimelineProvider.kt | 113 +++++ .../impl/gallery/MediaGalleryView.kt | 436 ++++++++++++++++++ .../mediaviewer/impl/gallery/MediaItem.kt | 102 ++++ .../impl/gallery/MediaItemsPostProcessor.kt | 71 +++ .../impl/gallery/TimelineMediaItemsFactory.kt | 119 +++++ .../impl/gallery/VirtualItemFactory.kt | 42 ++ .../TimelineMediaItemsCacheInvalidator.kt | 53 +++ .../impl/gallery/root/MediaGalleryRootNode.kt | 139 ++++++ .../impl/gallery/ui/DateItemView.kt | 45 ++ .../impl/gallery/ui/FileItemView.kt | 183 ++++++++ .../impl/gallery/ui/ImageItemView.kt | 74 +++ .../ui/MediaItemDateSeparatorProvider.kt | 30 ++ .../impl/gallery/ui/MediaItemFileProvider.kt | 45 ++ .../impl/gallery/ui/MediaItemImageProvider.kt | 30 ++ .../impl/gallery/ui/MediaItemVideoProvider.kt | 39 ++ .../impl/gallery/ui/VideoItemView.kt | 116 +++++ .../impl/local/AndroidLocalMediaFactory.kt | 9 + .../impl/viewer/MediaViewerEvents.kt | 4 + .../impl/viewer/MediaViewerNavigator.kt | 15 + .../impl/viewer/MediaViewerNode.kt | 21 +- .../impl/viewer/MediaViewerPresenter.kt | 41 +- .../impl/viewer/MediaViewerState.kt | 4 + .../impl/viewer/MediaViewerStateProvider.kt | 37 +- .../impl/viewer/MediaViewerView.kt | 61 ++- .../impl/src/main/res/values/localazy.xml | 16 + .../impl/gallery/FakeEventItemFactory.kt | 16 + .../impl/gallery/FakeMediaGalleryNavigator.kt | 19 + .../gallery/FakeMediaItemsPostProcessor.kt | 17 + .../gallery/FakeTimelineMediaItemsFactory.kt | 31 ++ .../impl/gallery/FakeVirtualItemFactory.kt | 16 + .../impl/gallery/MediaGalleryPresenterTest.kt | 261 +++++++++++ .../local/AndroidLocalMediaFactoryTest.kt | 2 + .../impl/viewer/FakeMediaViewerNavigator.kt | 24 + .../impl/viewer/MediaViewerPresenterTest.kt | 237 +++++++++- .../mediaviewer/test/FakeLocalMediaFactory.kt | 2 + tools/localazy/config.json | 7 + 69 files changed, 3822 insertions(+), 56 deletions(-) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt create mode 100644 libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt create mode 100644 libraries/mediaviewer/impl/src/main/res/values/localazy.xml create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 27b219fc1c..3b5fb67540 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -117,6 +117,7 @@ class MessagesFlowNode @AssistedInject constructor( @Parcelize data class MediaViewer( + val eventId: EventId?, val mediaInfo: MediaInfo, val mediaSource: MediaSource, val thumbnailSource: MediaSource?, @@ -241,9 +242,11 @@ class MessagesFlowNode @AssistedInject constructor( } is NavTarget.MediaViewer -> { val params = MediaViewerEntryPoint.Params( + eventId = navTarget.eventId, mediaInfo = navTarget.mediaInfo, mediaSource = navTarget.mediaSource, thumbnailSource = navTarget.thumbnailSource, + canShowInfo = true, canDownload = true, canShare = true, ) @@ -251,6 +254,10 @@ class MessagesFlowNode @AssistedInject constructor( override fun onDone() { overlay.hide() } + + override fun onViewInTimeline(eventId: EventId) { + viewInTimeline(eventId) + } } mediaViewerEntryPoint.nodeBuilder(this, buildContext) .params(params) @@ -311,11 +318,7 @@ class MessagesFlowNode @AssistedInject constructor( } override fun onViewInTimelineClick(eventId: EventId) { - val permalinkData = PermalinkData.RoomLink( - roomIdOrAlias = room.roomId.toRoomIdOrAlias(), - eventId = eventId, - ) - callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) } + viewInTimeline(eventId) } override fun onRoomPermalinkClick(data: PermalinkData.RoomLink) { @@ -341,6 +344,14 @@ class MessagesFlowNode @AssistedInject constructor( } } + private fun viewInTimeline(eventId: EventId) { + val permalinkData = PermalinkData.RoomLink( + roomIdOrAlias = room.roomId.toRoomIdOrAlias(), + eventId = eventId, + ) + callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) } + } + private fun processEventClick(event: TimelineItem.Event): Boolean { val navTarget = when (event.content) { is TimelineItemImageContent -> { @@ -415,13 +426,16 @@ class MessagesFlowNode @AssistedInject constructor( thumbnailSource: MediaSource?, ): NavTarget { return NavTarget.MediaViewer( + eventId = event.eventId, mediaInfo = MediaInfo( filename = content.filename, caption = content.caption, mimeType = content.mimeType, formattedFileSize = content.formattedFileSize, fileExtension = content.fileExtension, + senderId = event.senderId, senderName = event.safeSenderName, + senderAvatar = event.senderAvatar.url, dateSent = event.sentTime, ), mediaSource = mediaSource, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index eb13a0ba5b..cf1443fc8b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -15,6 +15,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -39,10 +40,13 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.operation.hide import io.element.android.libraries.architecture.overlay.operation.show import io.element.android.libraries.di.RoomScope +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.UserId +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction @@ -59,6 +63,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( private val messagesEntryPoint: MessagesEntryPoint, private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, private val mediaViewerEntryPoint: MediaViewerEntryPoint, + private val mediaGalleryEntryPoint: MediaGalleryEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), @@ -98,6 +103,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( @Parcelize data object PollHistory : NavTarget + @Parcelize + data object MediaGallery : NavTarget + @Parcelize data object AdminSettings : NavTarget @@ -136,6 +144,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( backstack.push(NavTarget.PollHistory) } + override fun openMediaGallery() { + backstack.push(NavTarget.MediaGallery) + } + override fun openAdminSettings() { backstack.push(NavTarget.AdminSettings) } @@ -213,6 +225,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( override fun onDone() { overlay.hide() } + + override fun onViewInTimeline(eventId: EventId) { + // Cannot happen + } } mediaViewerEntryPoint.nodeBuilder(this, buildContext) .avatar( @@ -222,10 +238,29 @@ class RoomDetailsFlowNode @AssistedInject constructor( .callback(callback) .build() } - is NavTarget.PollHistory -> { pollHistoryEntryPoint.createNode(this, buildContext) } + is NavTarget.MediaGallery -> { + val callback = object : MediaGalleryEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + + override fun onViewInTimeline(eventId: EventId) { + val permalinkData = PermalinkData.RoomLink( + roomIdOrAlias = room.roomId.toRoomIdOrAlias(), + eventId = eventId, + ) + plugins().forEach { + it.onPermalinkClick(permalinkData, pushToBackstack = false) + } + } + } + mediaGalleryEntryPoint.nodeBuilder(this, buildContext) + .callback(callback) + .build() + } is NavTarget.AdminSettings -> { createNode(buildContext) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index a56a028808..3b3d11fbd9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -45,6 +45,7 @@ class RoomDetailsNode @AssistedInject constructor( fun openRoomNotificationSettings() fun openAvatarPreview(name: String, url: String) fun openPollHistory() + fun openMediaGallery() fun openAdminSettings() fun openPinnedMessagesList() fun openKnockRequestsList() @@ -77,6 +78,10 @@ class RoomDetailsNode @AssistedInject constructor( callbacks.forEach { it.openPollHistory() } } + private fun openMediaGallery() { + callbacks.forEach { it.openMediaGallery() } + } + private fun onJoinCall() { callbacks.forEach { it.onJoinCall() } } @@ -143,6 +148,7 @@ class RoomDetailsNode @AssistedInject constructor( invitePeople = ::invitePeople, openAvatarPreview = ::openAvatarPreview, openPollHistory = ::openPollHistory, + openMediaGallery = ::openMediaGallery, openAdminSettings = this::openAdminSettings, onJoinCallClick = ::onJoinCall, onPinnedMessagesClick = ::openPinnedMessages, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 46588ae5fe..d55f4e4f7d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import im.vector.app.features.analytics.plan.Interaction import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomState @@ -79,6 +80,10 @@ class RoomDetailsPresenter @Inject constructor( val isPublic by remember { derivedStateOf { roomInfo?.isPublic.orFalse() } } val canShowPinnedMessages = isPinnedMessagesFeatureEnabled() + var canShowMediaGallery by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + canShowMediaGallery = featureFlagService.isFeatureEnabled(FeatureFlags.MediaGallery) + } val pinnedMessagesCount by remember { derivedStateOf { roomInfo?.pinnedEventIds?.size } } LaunchedEffect(Unit) { @@ -162,6 +167,7 @@ class RoomDetailsPresenter @Inject constructor( isPublic = isPublic, heroes = roomInfo?.heroes.orEmpty().toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, + canShowMediaGallery = canShowMediaGallery, pinnedMessagesCount = pinnedMessagesCount, canShowKnockRequests = canShowKnockRequests, knockRequestsCount = knockRequestsCount, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 7f15c846f9..85b5340959 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -40,6 +40,7 @@ data class RoomDetailsState( val isPublic: Boolean, val heroes: ImmutableList, val canShowPinnedMessages: Boolean, + val canShowMediaGallery: Boolean, val pinnedMessagesCount: Int?, val canShowKnockRequests: Boolean, val knockRequestsCount: Int?, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index dcf5bc3054..b3a4c0e7ee 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -101,6 +101,7 @@ fun aRoomDetailsState( isPublic: Boolean = true, heroes: List = emptyList(), canShowPinnedMessages: Boolean = true, + canShowMediaGallery: Boolean = true, pinnedMessagesCount: Int? = null, canShowKnockRequests: Boolean = false, knockRequestsCount: Int? = null, @@ -126,6 +127,7 @@ fun aRoomDetailsState( isPublic = isPublic, heroes = heroes.toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, + canShowMediaGallery = canShowMediaGallery, pinnedMessagesCount = pinnedMessagesCount, canShowKnockRequests = canShowKnockRequests, knockRequestsCount = knockRequestsCount, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index d73b8e2626..5e65ce9336 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -101,6 +101,7 @@ fun RoomDetailsView( invitePeople: () -> Unit, openAvatarPreview: (name: String, url: String) -> Unit, openPollHistory: () -> Unit, + openMediaGallery: () -> Unit, openAdminSettings: () -> Unit, onJoinCallClick: () -> Unit, onPinnedMessagesClick: () -> Unit, @@ -219,7 +220,11 @@ fun RoomDetailsView( PollsSection( openPollHistory = openPollHistory ) - + if (state.canShowMediaGallery) { + MediaGallerySection( + onClick = openMediaGallery + ) + } if (state.isEncrypted) { SecuritySection() } @@ -576,6 +581,19 @@ private fun PollsSection( } } +@Composable +private fun MediaGallerySection( + onClick: () -> Unit, +) { + PreferenceCategory { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_details_media_gallery_title)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Image())), + onClick = onClick, + ) + } +} + @Composable private fun SecuritySection() { PreferenceCategory(title = stringResource(R.string.screen_room_details_security_title)) { @@ -631,6 +649,7 @@ private fun ContentToPreview(state: RoomDetailsState) { invitePeople = {}, openAvatarPreview = { _, _ -> }, openPollHistory = {}, + openMediaGallery = {}, openAdminSettings = {}, onJoinCallClick = {}, onPinnedMessagesClick = {}, diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt index ce0d4a07f0..d795f60715 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope +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.user.CurrentSessionIdHolder import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint @@ -82,6 +83,10 @@ class UserProfileFlowNode @AssistedInject constructor( override fun onDone() { backstack.pop() } + + override fun onViewInTimeline(eventId: EventId) { + // Cannot happen + } } mediaViewerEntryPoint.nodeBuilder(this, buildContext) .avatar( diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index 8287e2d19d..f4dd61bf6e 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -61,3 +61,11 @@ fun String.replacePrefix(oldPrefix: String, newPrefix: String): String { this } } + +/** + * Surround with brackets. + */ +fun String.withBrackets(prefix: String = "(", suffix: String = ")"): String { + return "$prefix$this$suffix" +} + diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt new file mode 100644 index 0000000000..e4b20fd42b --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.core.preview + +val loremIpsum = """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut la + bore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate v + elit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proide + nt, sunt in culpa qui officia deserunt mollit anim id est laborum. + """.trimIndent() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 3f7d087f41..f3c9fb317a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -57,4 +57,6 @@ enum class AvatarSize(val dp: Dp) { KnockRequestItem(52.dp), KnockRequestBanner(32.dp), + + MediaSender(32.dp), } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 7d21ed1138..9add32499f 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -154,4 +154,11 @@ enum class FeatureFlags( defaultValue = { true }, isFinished = false, ), + MediaGallery( + key = "feature.media_gallery", + title = "Allow user to open the media gallery", + description = null, + defaultValue = { buildMeta -> buildMeta.buildType != BuildType.RELEASE }, + isFinished = false, + ), } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 989c301e92..840308af23 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -107,6 +107,11 @@ interface MatrixRoom : Closeable { */ suspend fun pinnedEventsTimeline(): Result + /** + * Create a new timeline for the media events of the room. + */ + suspend fun mediaTimeline(): Result + fun destroy() suspend fun subscribeToSync() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index 00f7a9a17c..29f8997ace 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -42,7 +42,8 @@ interface Timeline : AutoCloseable { enum class Mode { LIVE, FOCUSED_ON_EVENT, - PINNED_EVENTS + PINNED_EVENTS, + MEDIA, } val membershipChangeEventReceived: Flow diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index c3057298fa..55f639e266 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -78,6 +78,7 @@ import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener import org.matrix.rustcomponents.sdk.RoomListItem +import org.matrix.rustcomponents.sdk.RoomMessageEventMessageType import org.matrix.rustcomponents.sdk.TypingNotificationsListener import org.matrix.rustcomponents.sdk.UserPowerLevelUpdate import org.matrix.rustcomponents.sdk.WidgetCapabilities @@ -223,6 +224,26 @@ class RustMatrixRoom( } } + override suspend fun mediaTimeline(): Result { + return runCatching { + innerRoom.messageFilteredTimeline( + internalIdPrefix = "MediaGallery_", + allowedMessageTypes = listOf( + RoomMessageEventMessageType.FILE, + RoomMessageEventMessageType.IMAGE, + RoomMessageEventMessageType.VIDEO, + RoomMessageEventMessageType.AUDIO, + ) + ).let { inner -> + createTimeline(inner, mode = Timeline.Mode.MEDIA) + } + }.onFailure { + if (it is CancellationException) { + throw it + } + } + } + override fun destroy() { roomCoroutineScope.cancel() liveTimeline.close() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 200f1289b4..1a9da807a0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -64,6 +64,8 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.EditedContent import org.matrix.rustcomponents.sdk.FormattedBody @@ -170,26 +172,36 @@ class RustTimeline( } } + private val backwardsPaginationMutex = Mutex() + private val forwardsPaginationMutex = Mutex() + + private fun getPaginationMutex(direction: Timeline.PaginationDirection) = when (direction) { + Timeline.PaginationDirection.BACKWARDS -> backwardsPaginationMutex + Timeline.PaginationDirection.FORWARDS -> forwardsPaginationMutex + } + // Use NonCancellable to avoid breaking the timeline when the coroutine is cancelled. override suspend fun paginate(direction: Timeline.PaginationDirection): Result = withContext(NonCancellable) { withContext(dispatcher) { initLatch.await() - runCatching { - if (!canPaginate(direction)) throw TimelineException.CannotPaginate - updatePaginationStatus(direction) { it.copy(isPaginating = true) } - when (direction) { - Timeline.PaginationDirection.BACKWARDS -> inner.paginateBackwards(PAGINATION_SIZE.toUShort()) - Timeline.PaginationDirection.FORWARDS -> inner.focusedPaginateForwards(PAGINATION_SIZE.toUShort()) - } - }.onFailure { error -> - updatePaginationStatus(direction) { it.copy(isPaginating = false) } - if (error is TimelineException.CannotPaginate) { - Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backPaginationStatus.value}") - } else { - Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}") + getPaginationMutex(direction).withLock { + runCatching { + if (!canPaginate(direction)) throw TimelineException.CannotPaginate + updatePaginationStatus(direction) { it.copy(isPaginating = true) } + when (direction) { + Timeline.PaginationDirection.BACKWARDS -> inner.paginateBackwards(PAGINATION_SIZE.toUShort()) + Timeline.PaginationDirection.FORWARDS -> inner.focusedPaginateForwards(PAGINATION_SIZE.toUShort()) + } + }.onFailure { error -> + updatePaginationStatus(direction) { it.copy(isPaginating = false) } + if (error is TimelineException.CannotPaginate) { + Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backPaginationStatus.value}") + } else { + Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}") + } + }.onSuccess { hasReachedEnd -> + updatePaginationStatus(direction) { it.copy(isPaginating = false, hasMoreToLoad = !hasReachedEnd) } } - }.onSuccess { hasReachedEnd -> - updatePaginationStatus(direction) { it.copy(isPaginating = false, hasMoreToLoad = !hasReachedEnd) } } } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 9974e36746..b357c51fee 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -133,6 +133,7 @@ class FakeMatrixRoom( private val getMembersResult: (Int) -> Result> = { lambdaError() }, private val timelineFocusedOnEventResult: (EventId) -> Result = { lambdaError() }, private val pinnedEventsTimelineResult: () -> Result = { lambdaError() }, + private val mediaTimelineResult: () -> Result = { lambdaError() }, private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> }, private val saveComposerDraftLambda: (ComposerDraft) -> Result = { _: ComposerDraft -> Result.success(Unit) }, private val loadComposerDraftLambda: () -> Result = { Result.success(null) }, @@ -203,6 +204,10 @@ class FakeMatrixRoom( pinnedEventsTimelineResult() } + override suspend fun mediaTimeline(): Result = simulateLongTask { + mediaTimelineResult() + } + override suspend fun subscribeToSync() { subscribeToSyncLambda() } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt new file mode 100644 index 0000000000..e8b438b642 --- /dev/null +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.matrix.api.core.EventId + +interface MediaGalleryEntryPoint : FeatureEntryPoint { + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + fun onDone() + fun onViewInTimeline(eventId: EventId) + } +} diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index 5c317b1d6a..17a1052954 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.api import android.os.Parcelable import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.core.UserId import kotlinx.parcelize.Parcelize @Parcelize @@ -18,11 +19,14 @@ data class MediaInfo( val mimeType: String, val formattedFileSize: String, val fileExtension: String, + val senderId: UserId?, val senderName: String?, + val senderAvatar: String?, val dateSent: String?, ) : Parcelable fun anImageMediaInfo( + senderId: UserId? = UserId("@alice:server.org"), caption: String? = null, senderName: String? = null, dateSent: String? = null, @@ -32,7 +36,9 @@ fun anImageMediaInfo( mimeType = MimeTypes.Jpeg, formattedFileSize = "4MB", fileExtension = "jpg", + senderId = senderId, senderName = senderName, + senderAvatar = null, dateSent = dateSent, ) @@ -46,24 +52,31 @@ fun aVideoMediaInfo( mimeType = MimeTypes.Mp4, formattedFileSize = "14MB", fileExtension = "mp4", + senderId = UserId("@alice:server.org"), senderName = senderName, + senderAvatar = null, dateSent = dateSent, ) fun aPdfMediaInfo( + filename: String = "a pdf file.pdf", + caption: String? = null, senderName: String? = null, dateSent: String? = null, ): MediaInfo = MediaInfo( - filename = "a pdf file.pdf", - caption = null, + filename = filename, + caption = caption, mimeType = MimeTypes.Pdf, formattedFileSize = "23MB", fileExtension = "pdf", + senderId = UserId("@alice:server.org"), senderName = senderName, + senderAvatar = null, dateSent = dateSent, ) fun anApkMediaInfo( + senderId: UserId? = UserId("@alice:server.org"), senderName: String? = null, dateSent: String? = null, ): MediaInfo = MediaInfo( @@ -72,7 +85,9 @@ fun anApkMediaInfo( mimeType = MimeTypes.Apk, formattedFileSize = "50MB", fileExtension = "apk", + senderId = senderId, senderName = senderName, + senderAvatar = null, dateSent = dateSent, ) @@ -85,6 +100,8 @@ fun anAudioMediaInfo( mimeType = MimeTypes.Mp3, formattedFileSize = "7MB", fileExtension = "mp3", + senderId = UserId("@alice:server.org"), senderName = senderName, + senderAvatar = null, dateSent = dateSent, ) diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt index fb5ee5dece..3e262c08f2 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt @@ -12,6 +12,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource interface MediaViewerEntryPoint : FeatureEntryPoint { @@ -26,12 +27,15 @@ interface MediaViewerEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onDone() + fun onViewInTimeline(eventId: EventId) } data class Params( + val eventId: EventId?, val mediaInfo: MediaInfo, val mediaSource: MediaSource, val thumbnailSource: MediaSource?, + val canShowInfo: Boolean, val canDownload: Boolean, val canShare: Boolean, ) : NodeInputs diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index 5ebc252343..deeffe9e8b 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -33,15 +33,18 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.flick) + implementation(projects.features.networkmonitor.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.core) implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.di) implementation(projects.libraries.designsystem) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) api(projects.libraries.mediaviewer.api) implementation(projects.libraries.androidutils) @@ -49,8 +52,11 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) + testImplementation(projects.features.networkmonitor.test) + testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaviewer.test) + testImplementation(projects.services.toolbox.test) testImplementation(projects.tests.testutils) testImplementation(libs.test.junit) testImplementation(libs.test.truth) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt new file mode 100644 index 0000000000..5d4fd8b297 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint +import io.element.android.libraries.mediaviewer.impl.gallery.root.MediaGalleryRootNode +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultMediaGalleryEntryPoint @Inject constructor() : MediaGalleryEntryPoint { + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MediaGalleryEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : MediaGalleryEntryPoint.NodeBuilder { + override fun callback(callback: MediaGalleryEntryPoint.Callback): MediaGalleryEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt index 86d7bca722..f9611a7023 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt @@ -14,6 +14,7 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.architecture.createNode import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint @@ -41,17 +42,21 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint val mimeType = MimeTypes.Images return params( MediaViewerEntryPoint.Params( + eventId = null, mediaInfo = MediaInfo( filename = filename, caption = null, mimeType = mimeType, formattedFileSize = "", fileExtension = "", + senderId = UserId("@dummy:server.org"), senderName = null, + senderAvatar = null, dateSent = null, ), mediaSource = MediaSource(url = avatarUrl), thumbnailSource = null, + canShowInfo = false, canDownload = false, canShare = false, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt new file mode 100644 index 0000000000..c55e3c2295 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.details + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.MediaInfo + +sealed interface MediaBottomSheetState { + data object Hidden : MediaBottomSheetState + + data class MediaDeleteConfirmationState( + val eventId: EventId, + val mediaInfo: MediaInfo, + val thumbnailSource: MediaSource?, + ) : MediaBottomSheetState + + data class MediaDetailsBottomSheetState( + val eventId: EventId?, + val canDelete: Boolean, + val mediaInfo: MediaInfo, + val thumbnailSource: MediaSource?, + ) : MediaBottomSheetState +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt new file mode 100644 index 0000000000..9be9d84b6f --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.details + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.PageTitle +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.ui.media.MediaRequestData +import io.element.android.libraries.mediaviewer.api.anImageMediaInfo +import io.element.android.libraries.mediaviewer.impl.R +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MediaDeleteConfirmationBottomSheet( + state: MediaBottomSheetState.MediaDeleteConfirmationState, + onDelete: (EventId) -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + ModalBottomSheet( + modifier = modifier, + onDismissRequest = onDismiss, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + PageTitle( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp), + title = stringResource(R.string.screen_media_browser_delete_confirmation_title), + iconStyle = BigIcon.Style.Default(CompoundIcons.Delete(), useCriticalTint = true), + subtitle = stringResource(R.string.screen_media_browser_delete_confirmation_subtitle), + ) + MediaRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + state = state, + ) + Button( + modifier = Modifier + .fillMaxWidth() + .padding(top = 40.dp), + text = stringResource(CommonStrings.action_remove), + onClick = { + onDelete(state.eventId) + }, + destructive = true, + ) + TextButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + text = stringResource(CommonStrings.action_cancel), + onClick = { + onDismiss() + }, + ) + } + } +} + +@Composable +private fun MediaRow( + state: MediaBottomSheetState.MediaDeleteConfirmationState, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + val bgColor = if (LocalInspectionMode.current) { + ElementTheme.colors.bgDecorative1 + } else { + Color.Transparent + } + Box( + modifier = Modifier + .size(40.dp) + .background(bgColor), + ) { + if (state.thumbnailSource == null) { + BigIcon( + style = BigIcon.Style.Default(CompoundIcons.Attachment()), + ) + } else { + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .background(Color.White), + model = MediaRequestData(state.thumbnailSource, MediaRequestData.Kind.Thumbnail(100)), + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + contentDescription = null, + ) + } + } + Column( + modifier = Modifier + .padding(start = 12.dp) + .weight(1f), + ) { + // Name + Text( + modifier = Modifier.clipToBounds(), + text = state.mediaInfo.filename, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyLgRegular, + ) + // Info + Text( + text = state.mediaInfo.mimeType + " - " + state.mediaInfo.formattedFileSize, + color = MaterialTheme.colorScheme.secondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodySmRegular, + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun MediaDeleteConfirmationBottomSheetPreview() = ElementPreview { + MediaDeleteConfirmationBottomSheet( + state = MediaBottomSheetState.MediaDeleteConfirmationState( + eventId = EventId("\$eventId"), + mediaInfo = anImageMediaInfo( + senderName = "Alice", + ), + thumbnailSource = null, + ), + onDelete = {}, + onDismiss = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt new file mode 100644 index 0000000000..9e2109c978 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.details + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +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.libraries.designsystem.colors.AvatarColorsProvider +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.preview.ElementPreview +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.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.anImageMediaInfo +import io.element.android.libraries.mediaviewer.impl.R +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MediaDetailsBottomSheet( + state: MediaBottomSheetState.MediaDetailsBottomSheetState, + onViewInTimeline: (EventId) -> Unit, + onDelete: (EventId) -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + ModalBottomSheet( + modifier = modifier, + onDismissRequest = onDismiss, + ) { + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + Section( + title = stringResource(R.string.screen_media_details_uploaded_by), + ) { + SenderRow( + mediaInfo = state.mediaInfo, + ) + } + SectionText( + title = stringResource(R.string.screen_media_details_uploaded_on), + text = state.mediaInfo.dateSent.orEmpty(), + ) + SectionText( + title = stringResource(R.string.screen_media_details_filename), + text = state.mediaInfo.filename, + ) + SectionText( + title = stringResource(R.string.screen_media_details_file_format), + text = state.mediaInfo.mimeType + " - " + state.mediaInfo.formattedFileSize, + ) + if (state.eventId != null) { + Column { + HorizontalDivider() + ListItem( + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.VisibilityOn())), + headlineContent = { Text(stringResource(CommonStrings.action_view_in_timeline)) }, + style = ListItemStyle.Primary, + onClick = { + onViewInTimeline(state.eventId) + } + ) + if (state.canDelete) { + HorizontalDivider() + ListItem( + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Delete())), + headlineContent = { Text(stringResource(CommonStrings.action_remove)) }, + style = ListItemStyle.Destructive, + onClick = { + onDelete(state.eventId) + } + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + } +} + +@Composable +private fun SenderRow( + mediaInfo: MediaInfo, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + val id = mediaInfo.senderId?.value ?: "@Alice:domain" + Avatar( + AvatarData( + id = id, + name = mediaInfo.senderName, + url = mediaInfo.senderAvatar, + size = AvatarSize.MediaSender, + ) + ) + Column( + modifier = Modifier + .padding(start = 8.dp) + .weight(1f), + ) { + // Name + val avatarColors = AvatarColorsProvider.provide(id) + Text( + modifier = Modifier.clipToBounds(), + text = mediaInfo.senderName.orEmpty(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = avatarColors.foreground, + style = ElementTheme.typography.fontBodyMdMedium, + ) + // Id + Text( + text = mediaInfo.senderId?.value.orEmpty(), + color = MaterialTheme.colorScheme.secondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + } +} + +@Composable +private fun Section( + title: String, + content: @Composable () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = title.uppercase(), + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + content() + } +} + +@Composable +private fun SectionText( + title: String, + text: String, +) { + Section(title = title) { + Text( + text = text, + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun MediaDetailsBottomSheetPreview() = ElementPreview { + MediaDetailsBottomSheet( + state = MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = EventId("\$eventId"), + canDelete = true, + mediaInfo = anImageMediaInfo( + senderName = "Alice", + ), + thumbnailSource = null, + ), + onViewInTimeline = {}, + onDelete = {}, + onDismiss = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt new file mode 100644 index 0000000000..d4065f76c2 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -0,0 +1,194 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.filesize.FileSizeFormatter +import io.element.android.libraries.dateformatter.api.toHumanReadableDuration +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent +import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent +import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent +import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent +import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent +import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent +import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent +import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent +import io.element.android.libraries.matrix.api.timeline.item.event.StateContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent +import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent +import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl +import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor +import timber.log.Timber +import java.text.DateFormat +import java.util.Date +import javax.inject.Inject + +interface EventItemFactory { + fun create(currentTimelineItem: MatrixTimelineItem.Event): MediaItem.Event? +} + +@ContributesBinding(AppScope::class) +class DefaultEventItemFactory @Inject constructor( + private val fileSizeFormatter: FileSizeFormatter, + private val fileExtensionExtractor: FileExtensionExtractor, +) : EventItemFactory { + private val timeFormatter = DateFormat.getDateInstance() + + override fun create( + currentTimelineItem: MatrixTimelineItem.Event, + ): MediaItem.Event? { + val event = currentTimelineItem.event + val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp)) + return when (val content = event.content) { + CallNotifyContent, + is FailedToParseMessageLikeContent, + is FailedToParseStateContent, + LegacyCallInviteContent, + is PollContent, + is ProfileChangeContent, + RedactedContent, + is RoomMembershipContent, + is StateContent, + is StickerContent, + is UnableToDecryptContent, + UnknownContent -> { + Timber.w("Should not happen: ${content.javaClass.simpleName}") + null + } + is MessageContent -> { + when (val type = content.type) { + is EmoteMessageType, + is NoticeMessageType, + is OtherMessageType, + is LocationMessageType, + is TextMessageType -> { + Timber.w("Should not happen: ${content.type}") + null + } + is AudioMessageType -> MediaItem.File( + id = currentTimelineItem.uniqueId, + eventId = currentTimelineItem.eventId, + mediaInfo = MediaInfo( + filename = type.filename, + caption = type.caption, + mimeType = type.info?.mimetype.orEmpty(), + formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(type.filename), + senderId = event.sender, + senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), + senderAvatar = event.senderProfile.getAvatarUrl(), + dateSent = sentTime, + ), + mediaSource = type.source, + ) + is FileMessageType -> MediaItem.File( + id = currentTimelineItem.uniqueId, + eventId = currentTimelineItem.eventId, + mediaInfo = MediaInfo( + filename = type.filename, + caption = type.caption, + mimeType = type.info?.mimetype.orEmpty(), + formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(type.filename), + senderId = event.sender, + senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), + senderAvatar = event.senderProfile.getAvatarUrl(), + dateSent = sentTime, + ), + mediaSource = type.source, + ) + is ImageMessageType -> MediaItem.Image( + id = currentTimelineItem.uniqueId, + eventId = currentTimelineItem.eventId, + mediaInfo = MediaInfo( + filename = type.filename, + caption = type.caption, + mimeType = type.info?.mimetype.orEmpty(), + formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(type.filename), + senderId = event.sender, + senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), + senderAvatar = event.senderProfile.getAvatarUrl(), + dateSent = sentTime, + ), + mediaSource = type.source, + thumbnailSource = null, + ) + is StickerMessageType -> MediaItem.Image( + id = currentTimelineItem.uniqueId, + eventId = currentTimelineItem.eventId, + mediaInfo = MediaInfo( + filename = type.filename, + caption = type.caption, + mimeType = type.info?.mimetype.orEmpty(), + formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(type.filename), + senderId = event.sender, + senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), + senderAvatar = event.senderProfile.getAvatarUrl(), + dateSent = sentTime, + ), + mediaSource = type.source, + thumbnailSource = null, + ) + is VideoMessageType -> MediaItem.Video( + id = currentTimelineItem.uniqueId, + eventId = currentTimelineItem.eventId, + mediaInfo = MediaInfo( + filename = type.filename, + caption = type.caption, + mimeType = type.info?.mimetype.orEmpty(), + formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(type.filename), + senderId = event.sender, + senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), + senderAvatar = event.senderProfile.getAvatarUrl(), + dateSent = sentTime, + ), + mediaSource = type.source, + thumbnailSource = type.info?.thumbnailSource, + duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), + ) + is VoiceMessageType -> MediaItem.File( + id = currentTimelineItem.uniqueId, + eventId = currentTimelineItem.eventId, + mediaInfo = MediaInfo( + filename = type.filename, + caption = type.caption, + mimeType = type.info?.mimetype.orEmpty(), + formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(type.filename), + senderId = event.sender, + senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), + senderAvatar = event.senderProfile.getAvatarUrl(), + dateSent = sentTime, + ), + mediaSource = type.source, + ) + } + } + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt new file mode 100644 index 0000000000..717ba4edbb --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.mediaviewer.api.MediaInfo + +sealed interface MediaGalleryEvents { + data class ChangeMode(val mode: MediaGalleryMode) : MediaGalleryEvents + data class LoadMore(val direction: Timeline.PaginationDirection) : MediaGalleryEvents + data class Share(val mediaItem: MediaItem.Event) : MediaGalleryEvents + data class SaveOnDisk(val mediaItem: MediaItem.Event) : MediaGalleryEvents + data class OpenInfo(val mediaItem: MediaItem.Event) : MediaGalleryEvents + data class ViewInTimeline(val eventId: EventId) : MediaGalleryEvents + + data class ConfirmDelete( + val eventId: EventId, + val mediaInfo: MediaInfo, + val thumbnailSource: MediaSource?, + ) : MediaGalleryEvents + + data object CloseBottomSheet : MediaGalleryEvents + data class Delete(val eventId: EventId) : MediaGalleryEvents +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt new file mode 100644 index 0000000000..7ae729309a --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.core.EventId + +interface MediaGalleryNavigator { + fun onViewInTimelineClick(eventId: EventId) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt new file mode 100644 index 0000000000..4ec91570ef --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.EventId + +@ContributesNode(RoomScope::class) +class MediaGalleryNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: MediaGalleryPresenter.Factory, +) : Node(buildContext, plugins = plugins), + MediaGalleryNavigator { + private val presenter = presenterFactory.create( + navigator = this, + ) + + interface Callback : Plugin { + fun onDone() + fun onItemClick(item: MediaItem.Event) + fun onViewInTimeline(eventId: EventId) + } + + private fun onDone() { + plugins().forEach { + it.onDone() + } + } + + override fun onViewInTimelineClick(eventId: EventId) { + plugins().forEach { + it.onViewInTimeline(eventId) + } + } + + private fun onItemClick(item: MediaItem.Event) { + plugins().forEach { + it.onItemClick(item) + } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + MediaGalleryView( + state = state, + onBackClick = ::onDone, + onItemClick = ::onItemClick, + modifier = modifier, + ) + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt new file mode 100644 index 0000000000..5bd170f672 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -0,0 +1,264 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import android.content.ActivityNotFoundException +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.libraries.androidutils.R +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MatrixMediaLoader +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther +import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class MediaGalleryPresenter @AssistedInject constructor( + @Assisted private val navigator: MediaGalleryNavigator, + private val room: MatrixRoom, + private val timelineProvider: MediaGalleryTimelineProvider, + private val timelineMediaItemsFactory: TimelineMediaItemsFactory, + private val localMediaFactory: LocalMediaFactory, + private val mediaLoader: MatrixMediaLoader, + private val localMediaActions: LocalMediaActions, + private val snackbarDispatcher: SnackbarDispatcher, + private val mediaItemsPostProcessor: MediaItemsPostProcessor, +) : Presenter { + @AssistedFactory + interface Factory { + fun create( + navigator: MediaGalleryNavigator, + ): MediaGalleryPresenter + } + + @Composable + override fun present(): MediaGalleryState { + val coroutineScope = rememberCoroutineScope() + var mode by remember { mutableStateOf(MediaGalleryMode.Images) } + + val roomInfo by room.roomInfoFlow.collectAsState(null) + + var mediaBottomSheetState by remember { mutableStateOf(MediaBottomSheetState.Hidden) } + + var mediaItems by remember { + mutableStateOf>>(AsyncData.Uninitialized) + } + val imageItems by remember { + derivedStateOf { + mediaItemsPostProcessor.process( + mediaItems = mediaItems, + predicate = { it is MediaItem.Image || it is MediaItem.Video }, + ) + } + } + val fileItems by remember { + derivedStateOf { + mediaItemsPostProcessor.process( + mediaItems = mediaItems, + predicate = { it is MediaItem.File }, + ) + } + } + + val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() + localMediaActions.Configure() + + MediaListEffect( + onItemsChange = { newItems -> + mediaItems = newItems + } + ) + + LaunchedEffect(Unit) { + timelineProvider.launchIn(this) + } + + fun handleEvents(event: MediaGalleryEvents) { + when (event) { + is MediaGalleryEvents.ChangeMode -> { + mode = event.mode + } + is MediaGalleryEvents.LoadMore -> coroutineScope.launch { + timelineProvider.invokeOnTimeline { + paginate(event.direction) + } + } + is MediaGalleryEvents.Delete -> coroutineScope.delete(event.eventId) + is MediaGalleryEvents.SaveOnDisk -> coroutineScope.saveOnDisk(event.mediaItem) + is MediaGalleryEvents.Share -> coroutineScope.share(event.mediaItem) + is MediaGalleryEvents.ViewInTimeline -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + navigator.onViewInTimelineClick(event.eventId) + } + is MediaGalleryEvents.OpenInfo -> coroutineScope.launch { + mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = event.mediaItem.eventId(), + canDelete = when (event.mediaItem.mediaInfo().senderId) { + null -> false + room.sessionId -> room.canRedactOwn().getOrElse { false } && event.mediaItem.eventId() != null + else -> room.canRedactOther().getOrElse { false } && event.mediaItem.eventId() != null + + }, + mediaInfo = event.mediaItem.mediaInfo(), + thumbnailSource = when (event.mediaItem) { + is MediaItem.Image -> event.mediaItem.thumbnailSource ?: event.mediaItem.mediaSource + is MediaItem.Video -> event.mediaItem.thumbnailSource ?: event.mediaItem.mediaSource + is MediaItem.File -> null + }, + ) + } + is MediaGalleryEvents.ConfirmDelete -> { + mediaBottomSheetState = MediaBottomSheetState.MediaDeleteConfirmationState( + eventId = event.eventId, + mediaInfo = event.mediaInfo, + thumbnailSource = event.thumbnailSource, + ) + } + MediaGalleryEvents.CloseBottomSheet -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + } + } + } + + return MediaGalleryState( + roomName = roomInfo?.name ?: room.displayName, + mode = mode, + imageItems = imageItems, + fileItems = fileItems, + mediaBottomSheetState = mediaBottomSheetState, + snackbarMessage = snackbarMessage, + eventSink = ::handleEvents + ) + } + + @Composable + private fun MediaListEffect(onItemsChange: (AsyncData>) -> Unit) { + val updatedOnItemsChange by rememberUpdatedState(onItemsChange) + + val timelineState by timelineProvider.timelineStateFlow.collectAsState() + + LaunchedEffect(timelineState) { + when (val asyncTimeline = timelineState) { + AsyncData.Uninitialized -> flowOf(AsyncData.Uninitialized) + is AsyncData.Failure -> flowOf(AsyncData.Failure(asyncTimeline.error)) + is AsyncData.Loading -> flowOf(AsyncData.Loading()) + is AsyncData.Success -> { + asyncTimeline.data.timelineItems + .onEach { items -> + timelineMediaItemsFactory.replaceWith( + timelineItems = items, + ) + } + .launchIn(this) + + asyncTimeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS) + .onEach { backwardPaginationStatus -> + if (backwardPaginationStatus.canPaginate) { + timelineMediaItemsFactory.onCanPaginate() + } + } + .launchIn(this) + + timelineMediaItemsFactory.timelineItems.map { timelineItems -> + AsyncData.Success(timelineItems) + } + } + } + .onEach { items -> + updatedOnItemsChange(items) + } + .launchIn(this) + } + } + + private fun CoroutineScope.delete(eventId: EventId) = launch { + timelineProvider.invokeOnTimeline { + redactEvent( + eventOrTransactionId = eventId.toEventOrTransactionId(), + reason = null, + ) + } + } + + private suspend fun downloadMedia(mediaItem: MediaItem.Event): Result { + return mediaLoader.downloadMediaFile( + source = mediaItem.mediaSource(), + mimeType = mediaItem.mediaInfo().mimeType, + filename = mediaItem.mediaInfo().filename + ) + .mapCatching { mediaFile -> + localMediaFactory.createFromMediaFile( + mediaFile = mediaFile, + mediaInfo = mediaItem.mediaInfo() + ) + } + } + + private fun CoroutineScope.saveOnDisk(mediaItem: MediaItem.Event) = launch { + downloadMedia(mediaItem) + .mapCatching { localMedia -> + localMediaActions.saveOnDisk(localMedia) + } + .onSuccess { + val snackbarMessage = SnackbarMessage(CommonStrings.common_file_saved_on_disk_android) + snackbarDispatcher.post(snackbarMessage) + } + .onFailure { + val snackbarMessage = SnackbarMessage(mediaActionsError(it)) + snackbarDispatcher.post(snackbarMessage) + } + } + + private fun CoroutineScope.share(mediaItem: MediaItem.Event) = launch { + downloadMedia(mediaItem) + .mapCatching { localMedia -> + localMediaActions.share(localMedia) + } + .onFailure { + val snackbarMessage = SnackbarMessage(mediaActionsError(it)) + snackbarDispatcher.post(snackbarMessage) + } + } + + private fun mediaActionsError(throwable: Throwable): Int { + return if (throwable is ActivityNotFoundException) { + R.string.error_no_compatible_app_found + } else { + CommonStrings.error_unknown + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt new file mode 100644 index 0000000000..36e0710a88 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.mediaviewer.impl.R +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import kotlinx.collections.immutable.ImmutableList + +data class MediaGalleryState( + val roomName: String, + val mode: MediaGalleryMode, + val imageItems: AsyncData>, + val fileItems: AsyncData>, + val mediaBottomSheetState: MediaBottomSheetState, + val snackbarMessage: SnackbarMessage?, + val eventSink: (MediaGalleryEvents) -> Unit, +) + +enum class MediaGalleryMode(val stringResource: Int) { + Images(R.string.screen_media_browser_list_mode_media), + Files(R.string.screen_media_browser_list_mode_files), +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt new file mode 100644 index 0000000000..d0a92f1b5d --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aFile +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aVideo +import io.element.android.libraries.mediaviewer.impl.gallery.ui.anImage +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList + +open class MediaGalleryStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aMediaGalleryState(), + aMediaGalleryState(imageItems = AsyncData.Loading()), + aMediaGalleryState(imageItems = AsyncData.Success(emptyList().toPersistentList())), + aMediaGalleryState( + imageItems = AsyncData.Success( + listOf( + aDate(), + anImage(), + aDate(), + anImage(), + aVideo(), + anImage(), + anImage(), + anImage(), + anImage(), + anImage(), + ).toImmutableList() + ) + ), + aMediaGalleryState(mode = MediaGalleryMode.Files), + aMediaGalleryState(mode = MediaGalleryMode.Files, fileItems = AsyncData.Loading()), + aMediaGalleryState(mode = MediaGalleryMode.Files, fileItems = AsyncData.Success(emptyList().toPersistentList())), + aMediaGalleryState(mode = MediaGalleryMode.Files, fileItems = AsyncData.Success(emptyList().toPersistentList())), + aMediaGalleryState( + mode = MediaGalleryMode.Files, + fileItems = AsyncData.Success( + listOf( + aDate(), + aFile(), + aDate(), + aFile(), + aFile(), + aFile(), + aFile(), + ).toImmutableList() + ) + ), + ) +} + +private fun aMediaGalleryState( + roomName: String = "Room name", + mode: MediaGalleryMode = MediaGalleryMode.Images, + imageItems: AsyncData> = AsyncData.Uninitialized, + fileItems: AsyncData> = AsyncData.Uninitialized, +) = MediaGalleryState( + roomName = roomName, + mode = mode, + imageItems = imageItems, + fileItems = fileItems, + mediaBottomSheetState = MediaBottomSheetState.Hidden, + snackbarMessage = null, + eventSink = {} +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt new file mode 100644 index 0000000000..9174cab8ca --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.features.networkmonitor.api.NetworkMonitor +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.coroutine.mapState +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.TimelineProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@SingleIn(RoomScope::class) +class MediaGalleryTimelineProvider @Inject constructor( + private val room: MatrixRoom, + private val networkMonitor: NetworkMonitor, + private val featureFlagService: FeatureFlagService, +) : TimelineProvider { + private val _timelineStateFlow: MutableStateFlow> = + MutableStateFlow(AsyncData.Uninitialized) + + override fun activeTimelineFlow(): StateFlow { + return _timelineStateFlow + .mapState { value -> + value.dataOrNull() + } + } + + val timelineStateFlow = _timelineStateFlow + + fun launchIn(scope: CoroutineScope) { + _timelineStateFlow.subscriptionCount + .map { count -> count > 0 } + .distinctUntilChanged() + .onEach { isActive -> + if (isActive) { + onActive() + } else { + onInactive() + } + } + .launchIn(scope) + } + + private suspend fun onActive() = coroutineScope { + combine( + featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaGallery), + networkMonitor.connectivity + ) { isEnabled, _ -> + // do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed + isEnabled + } + .onEach { isFeatureEnabled -> + if (isFeatureEnabled) { + loadTimelineIfNeeded() + } else { + resetTimeline() + } + } + .launchIn(this) + } + + private suspend fun onInactive() { + resetTimeline() + } + + private suspend fun resetTimeline() { + invokeOnTimeline { + close() + } + _timelineStateFlow.emit(AsyncData.Uninitialized) + } + + suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) { + when (val asyncTimeline = timelineStateFlow.value) { + is AsyncData.Success -> action(asyncTimeline.data) + else -> Unit + } + } + + private suspend fun loadTimelineIfNeeded() { + when (timelineStateFlow.value) { + is AsyncData.Uninitialized, + is AsyncData.Failure -> { + timelineStateFlow.emit(AsyncData.Loading()) + room.mediaTimeline() + .fold( + { timelineStateFlow.emit(AsyncData.Success(it)) }, + { timelineStateFlow.emit(AsyncData.Failure(it)) } + ) + } + else -> Unit + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt new file mode 100644 index 0000000000..ac97755301 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -0,0 +1,436 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SingleChoiceSegmentedButtonRow +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.PageTitle +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.SegmentedButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost +import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.mediaviewer.impl.R +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.details.MediaDeleteConfirmationBottomSheet +import io.element.android.libraries.mediaviewer.impl.details.MediaDetailsBottomSheet +import io.element.android.libraries.mediaviewer.impl.gallery.ui.DateItemView +import io.element.android.libraries.mediaviewer.impl.gallery.ui.FileItemView +import io.element.android.libraries.mediaviewer.impl.gallery.ui.ImageItemView +import io.element.android.libraries.mediaviewer.impl.gallery.ui.VideoItemView +import kotlinx.collections.immutable.ImmutableList +import kotlin.math.max + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MediaGalleryView( + state: MediaGalleryState, + onBackClick: () -> Unit, + onItemClick: (MediaItem.Event) -> Unit, + modifier: Modifier = Modifier, +) { + val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) + BackHandler { onBackClick() } + Scaffold( + modifier = modifier, + snackbarHost = { SnackbarHost(snackbarHostState) }, + topBar = { + TopAppBar( + title = { + Text( + text = state.roomName, + style = ElementTheme.typography.aliasScreenTitle, + ) + }, + navigationIcon = { + BackButton( + onClick = onBackClick, + ) + }, + ) + }, + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .consumeWindowInsets(paddingValues) + .fillMaxSize() + ) { + SingleChoiceSegmentedButtonRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + MediaGalleryMode.entries.forEach { mode -> + SegmentedButton( + index = mode.ordinal, + count = MediaGalleryMode.entries.size, + selected = state.mode == mode, + onClick = { state.eventSink(MediaGalleryEvents.ChangeMode(mode)) }, + text = stringResource(mode.stringResource), + ) + } + } + val pagerState = rememberPagerState(0, 0f) { + MediaGalleryMode.entries.size + } + LaunchedEffect(state.mode) { + pagerState.scrollToPage(state.mode.ordinal) + } + HorizontalPager( + state = pagerState, + userScrollEnabled = false, + modifier = Modifier, + ) { page -> + val mode = MediaGalleryMode.entries[page] + when (mode) { + MediaGalleryMode.Images -> MediaGalleryImages( + images = state.imageItems, + eventSink = state.eventSink, + onItemClick = onItemClick, + ) + MediaGalleryMode.Files -> MediaGalleryFiles( + files = state.fileItems, + eventSink = state.eventSink, + onItemClick = onItemClick, + ) + } + } + } + } + when (val bottomSheetState = state.mediaBottomSheetState) { + MediaBottomSheetState.Hidden -> Unit + is MediaBottomSheetState.MediaDetailsBottomSheetState -> { + MediaDetailsBottomSheet( + state = bottomSheetState, + onViewInTimeline = { eventId -> + state.eventSink(MediaGalleryEvents.ViewInTimeline(eventId)) + }, + onDelete = { eventId -> + state.eventSink( + MediaGalleryEvents.ConfirmDelete( + eventId = eventId, + mediaInfo = bottomSheetState.mediaInfo, + thumbnailSource = bottomSheetState.thumbnailSource, + ) + ) + }, + onDismiss = { + state.eventSink(MediaGalleryEvents.CloseBottomSheet) + }, + ) + } + is MediaBottomSheetState.MediaDeleteConfirmationState -> { + MediaDeleteConfirmationBottomSheet( + state = bottomSheetState, + onDelete = { + state.eventSink(MediaGalleryEvents.Delete(it)) + }, + onDismiss = { + state.eventSink(MediaGalleryEvents.CloseBottomSheet) + }, + ) + } + } +} + +@Composable +private fun MediaGalleryImages( + images: AsyncData>, + eventSink: (MediaGalleryEvents) -> Unit, + onItemClick: (MediaItem.Event) -> Unit, +) { + when (images) { + AsyncData.Uninitialized, + is AsyncData.Loading -> { + LoadingContent(MediaGalleryMode.Images) + } + is AsyncData.Success -> { + if (images.data.isEmpty()) { + EmptyContent() + } else { + MediaGalleryImageGrid( + images = images.data, + eventSink = eventSink, + onItemClick = onItemClick, + ) + } + } + is AsyncData.Failure -> { + ErrorContent( + error = images.error, + ) + } + } +} + +@Composable +private fun MediaGalleryFiles( + files: AsyncData>, + eventSink: (MediaGalleryEvents) -> Unit, + onItemClick: (MediaItem.Event) -> Unit, +) { + when (files) { + AsyncData.Uninitialized, + is AsyncData.Loading -> { + LoadingContent(MediaGalleryMode.Files) + } + is AsyncData.Success -> { + if (files.data.isEmpty()) { + EmptyContent() + } else { + MediaGalleryFilesList( + files = files.data, + eventSink = eventSink, + onItemClick = onItemClick, + ) + } + } + is AsyncData.Failure -> { + ErrorContent( + error = files.error, + ) + } + } +} + +@Composable +private fun MediaGalleryFilesList( + files: ImmutableList, + eventSink: (MediaGalleryEvents) -> Unit, + onItemClick: (MediaItem.Event) -> Unit, +) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + ) { + items(files) { item -> + when (item) { + is MediaItem.File -> FileItemView( + item, + onClick = { onItemClick(item) }, + onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, + onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, + onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, + ) + is MediaItem.DateSeparator -> DateItemView(item) + is MediaItem.Image, + is MediaItem.Video -> { + // Should not happen + } + is MediaItem.LoadingIndicator -> { + LoadingMoreIndicator(item.direction) + val latestEventSink by rememberUpdatedState(eventSink) + LaunchedEffect(item.timestamp) { + latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) + } + } + } + } + } +} + +@Composable +private fun MediaGalleryImageGrid( + images: ImmutableList, + eventSink: (MediaGalleryEvents) -> Unit, + onItemClick: (MediaItem.Event) -> Unit, +) { + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp.dp + val horizontalPadding = 16.dp + val itemSpacing = 4.dp + val availableWidth = screenWidth - horizontalPadding * 2 + val minCellWidth = 80.dp + // Calculate the number of columns + val columns = max(1, (availableWidth / (minCellWidth + itemSpacing)).toInt()) + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = horizontalPadding), + columns = GridCells.Fixed(columns), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + items( + images, + span = { item -> + when (item) { + is MediaItem.LoadingIndicator, + is MediaItem.DateSeparator -> GridItemSpan(columns) + is MediaItem.Image, + is MediaItem.Video, + is MediaItem.File -> GridItemSpan(1) + } + }, + key = { it.id() }, + contentType = { it::class.java }, + ) { item -> + when (item) { + is MediaItem.DateSeparator -> { + DateItemView(item) + } + is MediaItem.File -> { + // Should not happen + } + is MediaItem.Image -> { + ImageItemView( + item, + onClick = { onItemClick(item) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, + ) + } + is MediaItem.Video -> { + VideoItemView( + item, + onClick = { onItemClick(item) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, + ) + } + is MediaItem.LoadingIndicator -> { + LoadingMoreIndicator(item.direction) + val latestEventSink by rememberUpdatedState(eventSink) + LaunchedEffect(item.timestamp) { + latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) + } + } + } + } + } +} + +@Composable +private fun LoadingMoreIndicator( + direction: Timeline.PaginationDirection, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + when (direction) { + Timeline.PaginationDirection.FORWARDS -> { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .padding(top = 2.dp) + .height(1.dp) + ) + } + Timeline.PaginationDirection.BACKWARDS -> { + CircularProgressIndicator( + strokeWidth = 2.dp, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + } + } +} + +@Composable +private fun ErrorContent(error: Throwable) { + // TODO + Text("Error: $error") +} + +@Composable +private fun EmptyContent( +) { + Box( + modifier = Modifier.fillMaxSize(), + ) { + PageTitle( + modifier = Modifier + .fillMaxWidth() + .padding(top = 44.dp) + .padding(24.dp), + title = stringResource(R.string.screen_media_browser_empty_state_title), + iconStyle = BigIcon.Style.Default(CompoundIcons.Image()), + subtitle = stringResource(R.string.screen_media_browser_empty_state_subtitle), + ) + } +} + +@Composable +private fun LoadingContent( + mode: MediaGalleryMode, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 48.dp) + .padding(24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircularProgressIndicator() + val res = when (mode) { + MediaGalleryMode.Images -> R.string.screen_media_browser_list_loading_media + MediaGalleryMode.Files -> R.string.screen_media_browser_list_loading_files + } + Text( + text = stringResource(res), + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } +} + +@PreviewsDayNight +@Composable +internal fun MediaGalleryViewPreview( + @PreviewParameter(MediaGalleryStateProvider::class) state: MediaGalleryState +) = ElementPreview { + MediaGalleryView( + state = state, + onBackClick = {}, + onItemClick = {}, + ) +} + diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt new file mode 100644 index 0000000000..f43387fdb6 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.ui.media.MediaRequestData +import io.element.android.libraries.mediaviewer.api.MediaInfo + +sealed interface MediaItem { + data class DateSeparator( + val id: UniqueId, + val formattedDate: String, + ) : MediaItem + + data class LoadingIndicator( + val id: UniqueId, + val direction: Timeline.PaginationDirection, + val timestamp: Long, + ) : MediaItem + + sealed interface Event : MediaItem + + data class Image( + val id: UniqueId, + val eventId: EventId?, + val mediaInfo: MediaInfo, + val mediaSource: MediaSource, + val thumbnailSource: MediaSource?, + ) : Event { + val thumbnailMediaRequestData: MediaRequestData + get() = MediaRequestData(thumbnailSource ?: mediaSource, MediaRequestData.Kind.Thumbnail(100)) + } + + data class Video( + val id: UniqueId, + val eventId: EventId?, + val mediaInfo: MediaInfo, + val mediaSource: MediaSource, + val thumbnailSource: MediaSource?, + val duration: String?, + ) : Event { + val thumbnailMediaRequestData: MediaRequestData + get() = MediaRequestData(thumbnailSource ?: mediaSource, MediaRequestData.Kind.Thumbnail(100)) + } + + data class File( + val id: UniqueId, + val eventId: EventId?, + val mediaInfo: MediaInfo, + val mediaSource: MediaSource, + ) : Event +} + +fun MediaItem.id(): UniqueId { + return when (this) { + is MediaItem.DateSeparator -> id + is MediaItem.LoadingIndicator -> id + is MediaItem.Image -> id + is MediaItem.Video -> id + is MediaItem.File -> id + } +} + +fun MediaItem.Event.eventId(): EventId? { + return when (this) { + is MediaItem.Image -> eventId + is MediaItem.Video -> eventId + is MediaItem.File -> eventId + } +} + +fun MediaItem.Event.mediaInfo(): MediaInfo { + return when (this) { + is MediaItem.Image -> mediaInfo + is MediaItem.Video -> mediaInfo + is MediaItem.File -> mediaInfo + } +} + +fun MediaItem.Event.mediaSource(): MediaSource { + return when (this) { + is MediaItem.Image -> mediaSource + is MediaItem.Video -> mediaSource + is MediaItem.File -> mediaSource + } +} + +fun MediaItem.Event.thumbnailSource(): MediaSource? { + return when (this) { + is MediaItem.Image -> thumbnailSource + is MediaItem.Video -> thumbnailSource + is MediaItem.File -> null + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt new file mode 100644 index 0000000000..d406a34567 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.di.AppScope +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import javax.inject.Inject + +interface MediaItemsPostProcessor { + fun process( + mediaItems: AsyncData>, + predicate: (MediaItem.Event) -> Boolean, + ): AsyncData> +} + +@ContributesBinding(AppScope::class) +class DefaultMediaItemsPostProcessor @Inject constructor( +) : MediaItemsPostProcessor { + override fun process( + mediaItems: AsyncData>, + predicate: (MediaItem.Event) -> Boolean, + ): AsyncData> { + return when (mediaItems) { + is AsyncData.Uninitialized -> mediaItems + is AsyncData.Loading -> mediaItems + is AsyncData.Failure -> mediaItems + is AsyncData.Success -> AsyncData.Success( + process( + mediaItems = mediaItems.data, + predicate = predicate, + ) + ) + } + } + + private fun process( + mediaItems: List, + predicate: (MediaItem.Event) -> Boolean, + ) = buildList { + val eventList = mutableListOf() + for (item in mediaItems) { + when (item) { + is MediaItem.DateSeparator -> { + if (eventList.isNotEmpty()) { + // Date separator first + add(item) + // Then events + addAll(eventList) + eventList.clear() + } + } + is MediaItem.Event -> { + if (predicate(item)) { + eventList.add(item) + } + } + is MediaItem.LoadingIndicator -> { + add(item) + } + } + } + }.toImmutableList() +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt new file mode 100644 index 0000000000..1993381417 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.diff.DiffCacheUpdater +import io.element.android.libraries.androidutils.diff.MutableListDiffCache +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.mediaviewer.impl.gallery.diff.TimelineMediaItemsCacheInvalidator +import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject + +interface TimelineMediaItemsFactory { + val timelineItems: Flow> + + suspend fun replaceWith(timelineItems: List) + suspend fun onCanPaginate() +} + +@ContributesBinding(AppScope::class) +class DefaultTimelineMediaItemsFactory @Inject constructor( + private val dispatchers: CoroutineDispatchers, + private val virtualItemFactory: VirtualItemFactory, + private val eventItemFactory: EventItemFactory, + private val systemClock: SystemClock, +) : TimelineMediaItemsFactory { + private val _timelineItems = MutableSharedFlow>(replay = 1) + private val lock = Mutex() + private val diffCache = MutableListDiffCache() + private val diffCacheUpdater = DiffCacheUpdater( + diffCache = diffCache, + detectMoves = false, + cacheInvalidator = TimelineMediaItemsCacheInvalidator() + ) { old, new -> + if (old is MatrixTimelineItem.Event && new is MatrixTimelineItem.Event) { + old.uniqueId == new.uniqueId + } else { + false + } + } + + override val timelineItems: Flow> = _timelineItems.distinctUntilChanged() + + override suspend fun replaceWith( + timelineItems: List, + ) = withContext(dispatchers.computation) { + lock.withLock { + diffCacheUpdater.updateWith(timelineItems) + buildAndEmitTimelineItemStates(timelineItems) + } + } + + /** + * Update the timestamp of the loading indicator, so that it may trigger a new pagination request. + */ + override suspend fun onCanPaginate() { + lock.withLock { + val values = _timelineItems.replayCache.firstOrNull() ?: return@withLock + val lastItem = values.lastOrNull() + if (lastItem is MediaItem.LoadingIndicator) { + val newList = values.toMutableList().apply { + removeAt(size - 1) + val newTs = systemClock.epochMillis() + add(lastItem.copy(timestamp = newTs)) + } + _timelineItems.emit(newList.toPersistentList()) + } else { + Timber.w("onCanPaginate called but last item is not a loading indicator") + } + } + } + + private suspend fun buildAndEmitTimelineItemStates( + timelineItems: List, + ) { + val newTimelineItemStates = ArrayList() + for (index in diffCache.indices().reversed()) { + val cacheItem = diffCache.get(index) + if (cacheItem == null) { + buildAndCacheItem(timelineItems, index)?.also { timelineItemState -> + newTimelineItemStates.add(timelineItemState) + } + } else { + newTimelineItemStates.add(cacheItem) + } + } + _timelineItems.emit(newTimelineItemStates.toPersistentList()) + } + + private fun buildAndCacheItem( + timelineItems: List, + index: Int, + ): MediaItem? { + val timelineItem = + when (val currentTimelineItem = timelineItems[index]) { + is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem) + is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem) + MatrixTimelineItem.Other -> null + } + diffCache[index] = timelineItem + return timelineItem + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt new file mode 100644 index 0000000000..f8a952cbd4 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem +import javax.inject.Inject + +interface VirtualItemFactory { + fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? +} + +@ContributesBinding(AppScope::class) +class DefaultVirtualItemFactory @Inject constructor( + private val daySeparatorFormatter: DaySeparatorFormatter, +) : VirtualItemFactory { + override fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? { + return when (val virtual = timelineItem.virtual) { + is VirtualTimelineItem.DayDivider -> MediaItem.DateSeparator( + id = timelineItem.uniqueId, + formattedDate = daySeparatorFormatter.format(virtual.timestamp) + ) + VirtualTimelineItem.LastForwardIndicator -> null + is VirtualTimelineItem.LoadingIndicator -> MediaItem.LoadingIndicator( + id = timelineItem.uniqueId, + direction = virtual.direction, + timestamp = virtual.timestamp + ) + VirtualTimelineItem.ReadMarker -> null + VirtualTimelineItem.RoomBeginning -> null + VirtualTimelineItem.TypingNotification -> null + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt new file mode 100644 index 0000000000..b7e6d51913 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.diff + +import io.element.android.libraries.androidutils.diff.DefaultDiffCacheInvalidator +import io.element.android.libraries.androidutils.diff.DiffCacheInvalidator +import io.element.android.libraries.androidutils.diff.MutableDiffCache +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +/** + * [DiffCacheInvalidator] implementation for [MediaItem]. + * It uses [DefaultDiffCacheInvalidator] and invalidate the cache around the updated item so that those items are computed again. + * This is needed because a timeline item is computed based on the previous and next items. + */ +internal class TimelineMediaItemsCacheInvalidator : DiffCacheInvalidator { + private val delegate = DefaultDiffCacheInvalidator() + + override fun onChanged(position: Int, count: Int, cache: MutableDiffCache) { + delegate.onChanged(position, count, cache) + } + + override fun onMoved(fromPosition: Int, toPosition: Int, cache: MutableDiffCache) { + delegate.onMoved(fromPosition, toPosition, cache) + } + + override fun onInserted(position: Int, count: Int, cache: MutableDiffCache) { + cache.invalidateAround(position) + delegate.onInserted(position, count, cache) + } + + override fun onRemoved(position: Int, count: Int, cache: MutableDiffCache) { + cache.invalidateAround(position) + delegate.onRemoved(position, count, cache) + } +} + +/** + * Invalidate the cache around the given position. + * It invalidates the previous and next items. + */ +private fun MutableDiffCache<*>.invalidateAround(position: Int) { + if (position > 0) { + set(position - 1, null) + } + if (position < indices().last) { + set(position + 1, null) + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt new file mode 100644 index 0000000000..1476cef64a --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.root + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import com.bumble.appyx.navmodel.backstack.BackStack +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.BackstackWithOverlayBox +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.overlay.Overlay +import io.element.android.libraries.architecture.overlay.operation.hide +import io.element.android.libraries.architecture.overlay.operation.show +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint +import io.element.android.libraries.mediaviewer.impl.gallery.MediaGalleryNode +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import io.element.android.libraries.mediaviewer.impl.gallery.eventId +import io.element.android.libraries.mediaviewer.impl.gallery.mediaInfo +import io.element.android.libraries.mediaviewer.impl.gallery.mediaSource +import io.element.android.libraries.mediaviewer.impl.gallery.thumbnailSource +import kotlinx.parcelize.Parcelize + +@ContributesNode(RoomScope::class) +class MediaGalleryRootNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val mediaViewerEntryPoint: MediaViewerEntryPoint +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + overlay = Overlay( + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + + @Parcelize + data class MediaViewer( + val eventId: EventId?, + val mediaInfo: MediaInfo, + val mediaSource: MediaSource, + val thumbnailSource: MediaSource?, + ) : NavTarget + } + + private fun onDone() { + plugins().forEach { + it.onDone() + } + } + + private fun onViewInTimeline(eventId: EventId) { + plugins().forEach { + it.onViewInTimeline(eventId) + } + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Root -> { + val callback = object : MediaGalleryNode.Callback { + override fun onDone() { + this@MediaGalleryRootNode.onDone() + } + + override fun onViewInTimeline(eventId: EventId) { + this@MediaGalleryRootNode.onViewInTimeline(eventId) + } + + override fun onItemClick(item: MediaItem.Event) { + overlay.show( + NavTarget.MediaViewer( + eventId = item.eventId(), + mediaInfo = item.mediaInfo(), + mediaSource = item.mediaSource(), + thumbnailSource = item.thumbnailSource(), + ) + ) + } + } + createNode(buildContext = buildContext, plugins = listOf(callback)) + } + is NavTarget.MediaViewer -> { + val callback = object : MediaViewerEntryPoint.Callback { + override fun onDone() { + overlay.hide() + } + + override fun onViewInTimeline(eventId: EventId) { + this@MediaGalleryRootNode.onViewInTimeline(eventId) + } + } + mediaViewerEntryPoint.nodeBuilder(this, buildContext) + .params( + MediaViewerEntryPoint.Params( + eventId = navTarget.eventId, + mediaInfo = navTarget.mediaInfo, + mediaSource = navTarget.mediaSource, + thumbnailSource = navTarget.thumbnailSource, + canShowInfo = true, + canDownload = true, + canShare = true, + ) + ) + .callback(callback) + .build() + } + } + } + + @Composable + override fun View(modifier: Modifier) { + BackstackWithOverlayBox(modifier) + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt new file mode 100644 index 0000000000..7cf387a8cc --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +@Composable +fun DateItemView( + item: MediaItem.DateSeparator, + modifier: Modifier = Modifier, +) { + Text( + modifier = modifier + .fillMaxWidth() + .padding(12.dp), + text = item.formattedDate, + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textPrimary, + ) +} + +@PreviewsDayNight +@Composable +internal fun PreviewDateItemView( + @PreviewParameter(MediaItemDateSeparatorProvider::class) date: MediaItem.DateSeparator, +) = ElementPreview { + DateItemView(date) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt new file mode 100644 index 0000000000..99ff456296 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +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.libraries.core.extensions.withBrackets +import io.element.android.libraries.designsystem.preview.ElementPreview +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.IconButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +@Composable +fun FileItemView( + file: MediaItem.File, + onClick: () -> Unit, + onShareClick: () -> Unit, + onDownloadClick: () -> Unit, + onInfoClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(top = 20.dp, start = 16.dp, end = 16.dp), + ) { + FilenameRow( + file = file, + onClick = onClick, + ) + val caption = file.mediaInfo.caption + if (caption != null) { + Spacer(modifier = Modifier.height(16.dp)) + Caption(caption) + } + Spacer(modifier = Modifier.height(16.dp)) + ActionIconsRow( + onShareClick = onShareClick, + onDownloadClick = onDownloadClick, + onInfoClick = onInfoClick, + ) + HorizontalDivider() + } +} + +@Composable +private fun FilenameRow( + file: MediaItem.File, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background( + color = ElementTheme.colors.bgSubtleSecondary, + shape = RoundedCornerShape(12.dp), + ) + .clickable { onClick() } + .fillMaxWidth() + .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .background( + color = ElementTheme.colors.bgActionSecondaryRest, + shape = CircleShape, + ) + .size(32.dp) + .padding(6.dp), + imageVector = CompoundIcons.Attachment(), + contentDescription = null, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = file.mediaInfo.filename, + modifier = Modifier.weight(1f), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + val formattedSize = file.mediaInfo.formattedFileSize + if (formattedSize.isNotEmpty()) { + Text( + text = formattedSize.withBrackets(), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) + } + } +} + +@Composable +private fun Caption(caption: String) { + Text( + modifier = Modifier.fillMaxWidth(), + text = caption, + maxLines = 5, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) +} + +@Composable +private fun ActionIconsRow( + onShareClick: () -> Unit, + onDownloadClick: () -> Unit, + onInfoClick: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + IconButton( + onClick = onShareClick, + ) { + Icon( + imageVector = CompoundIcons.ShareAndroid(), + contentDescription = null, + ) + } + IconButton( + onClick = onDownloadClick, + ) { + Icon( + imageVector = CompoundIcons.Download(), + contentDescription = null, + ) + } + IconButton( + onClick = onInfoClick, + ) { + Icon( + imageVector = CompoundIcons.Info(), + contentDescription = null, + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun FileItemViewPreview( + @PreviewParameter(MediaItemFileProvider::class) file: MediaItem.File, +) = ElementPreview { + FileItemView( + file = file, + onClick = {}, + onShareClick = {}, + onDownloadClick = {}, + onInfoClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt new file mode 100644 index 0000000000..892b22a951 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalInspectionMode +import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ImageItemView( + image: MediaItem.Image, + onClick: () -> Unit, + onLongClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val bgColor = if (LocalInspectionMode.current) { + ElementTheme.colors.bgDecorative1 + } else { + Color.Transparent + } + Box( + modifier = modifier + .aspectRatio(1f) + .combinedClickable(onClick = onClick, onLongClick = onLongClick) + .background(bgColor), + ) { + var isLoaded by remember { mutableStateOf(false) } + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .then(if (isLoaded) Modifier.background(Color.White) else Modifier), + model = image.thumbnailMediaRequestData, + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + contentDescription = null, + onState = { isLoaded = it is AsyncImagePainter.State.Success }, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun ImageItemViewPreview() = ElementPreview { + ImageItemView( + image = anImage(), + onClick = {}, + onLongClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt new file mode 100644 index 0000000000..2d7c3d50ab --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +class MediaItemDateSeparatorProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aDate(), + aDate(formattedDate = "A long date that should be truncated"), + ) +} + +fun aDate( + id: UniqueId = UniqueId("dateId"), + formattedDate: String = "October 2024", +): MediaItem.DateSeparator { + return MediaItem.DateSeparator( + id = id, + formattedDate = formattedDate, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt new file mode 100644 index 0000000000..f5197e590d --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.core.preview.loremIpsum +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +class MediaItemFileProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aFile(), + aFile( + filename = "A long filename that should be truncated.jpg", + caption = "A caption", + ), + aFile( + caption = loremIpsum, + ), + ) +} + +fun aFile( + id: UniqueId = UniqueId("fileId"), + filename: String = "filename", + caption: String? = null, +): MediaItem.File { + return MediaItem.File( + id = id, + eventId = null, + mediaInfo = aPdfMediaInfo( + filename = filename, + caption = caption, + ), + mediaSource = MediaSource(""), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt new file mode 100644 index 0000000000..dc124cb5c7 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.anImageMediaInfo +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +fun anImage( + eventId: EventId? = null, + senderId: UserId? = null, +): MediaItem.Image { + return MediaItem.Image( + id = UniqueId("imageId"), + eventId = eventId, + mediaInfo = anImageMediaInfo( + senderId = senderId, + ), + mediaSource = MediaSource(""), + thumbnailSource = null, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt new file mode 100644 index 0000000000..1db99c7e31 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +class MediaItemVideoProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aVideo(), + aVideo( + duration = null, + ), + ) +} + +fun aVideo( + id: UniqueId = UniqueId("videoId"), + mediaSource: MediaSource = MediaSource(""), + duration: String? = "1:23", +): MediaItem.Video { + return MediaItem.Video( + id = id, + eventId = null, + mediaInfo = aVideoMediaInfo(), + mediaSource = mediaSource, + thumbnailSource = null, + duration = duration, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt new file mode 100644 index 0000000000..e6d67fb01a --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +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.mediaviewer.impl.gallery.MediaItem + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun VideoItemView( + video: MediaItem.Video, + onClick: () -> Unit, + onLongClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val bgColor = if (LocalInspectionMode.current) { + ElementTheme.colors.bgDecorative2 + } else { + Color.Transparent + } + Box( + modifier = modifier + .aspectRatio(1f) + .combinedClickable(onClick = onClick, onLongClick = onLongClick) + .background(bgColor), + ) { + var isLoaded by remember { mutableStateOf(false) } + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .then(if (isLoaded) Modifier.background(Color.White) else Modifier), + model = video.thumbnailMediaRequestData, + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + contentDescription = null, + onState = { isLoaded = it is AsyncImagePainter.State.Success }, + ) + VideoInfoRow( + video = video, + modifier = Modifier.align(Alignment.BottomStart) + ) + } +} + +@Composable +private fun VideoInfoRow( + video: MediaItem.Video, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = CompoundIcons.VideoCallSolid(), + contentDescription = null + ) + if (video.duration != null) { + Spacer(Modifier.weight(1f)) + Text( + text = video.duration, + style = ElementTheme.typography.fontBodySmMedium, + color = ElementTheme.colors.textPrimary, + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun VideoItemViewPreview( + @PreviewParameter(MediaItemVideoProvider::class) video: MediaItem.Video, +) = ElementPreview { + VideoItemView( + video = video, + onClick = {}, + onLongClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index 8b5163cf6c..62706f120e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.media.toFile import io.element.android.libraries.mediaviewer.api.MediaInfo @@ -41,7 +42,9 @@ class AndroidLocalMediaFactory @Inject constructor( name = mediaInfo.filename, caption = mediaInfo.caption, formattedFileSize = mediaInfo.formattedFileSize, + senderId = mediaInfo.senderId, senderName = mediaInfo.senderName, + senderAvatar = mediaInfo.senderAvatar, dateSent = mediaInfo.dateSent, ) @@ -56,7 +59,9 @@ class AndroidLocalMediaFactory @Inject constructor( name = name, caption = null, formattedFileSize = formattedFileSize, + senderId = null, senderName = null, + senderAvatar = null, dateSent = null, ) @@ -66,7 +71,9 @@ class AndroidLocalMediaFactory @Inject constructor( name: String?, caption: String?, formattedFileSize: String?, + senderId: UserId?, senderName: String?, + senderAvatar: String?, dateSent: String?, ): LocalMedia { val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream @@ -81,7 +88,9 @@ class AndroidLocalMediaFactory @Inject constructor( caption = caption, formattedFileSize = fileSize, fileExtension = fileExtension, + senderId = senderId, senderName = senderName, + senderAvatar = senderAvatar, dateSent = dateSent, ) ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt index ac2714584c..5b85cd2b9f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt @@ -7,10 +7,14 @@ package io.element.android.libraries.mediaviewer.impl.viewer +import io.element.android.libraries.matrix.api.core.EventId + sealed interface MediaViewerEvents { data object SaveOnDisk : MediaViewerEvents data object Share : MediaViewerEvents data object OpenWith : MediaViewerEvents data object RetryLoading : MediaViewerEvents data object ClearLoadingError : MediaViewerEvents + data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents + data class Delete(val eventId: EventId) : MediaViewerEvents } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt new file mode 100644 index 0000000000..07fa0ec15d --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.viewer + +import io.element.android.libraries.matrix.api.core.EventId + +interface MediaViewerNavigator { + fun onViewInTimelineClick(eventId: EventId) + fun onItemDeleted() +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt index 83c7c1aca7..9a9af5ee63 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt @@ -19,14 +19,16 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.compound.theme.ForcedDarkElementTheme import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint @ContributesNode(RoomScope::class) -open class MediaViewerNode @AssistedInject constructor( +class MediaViewerNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, presenterFactory: MediaViewerPresenter.Factory, -) : Node(buildContext, plugins = plugins) { +) : Node(buildContext, plugins = plugins), + MediaViewerNavigator { private val inputs = inputs() private fun onDone() { @@ -35,7 +37,20 @@ open class MediaViewerNode @AssistedInject constructor( } } - private val presenter = presenterFactory.create(inputs) + override fun onViewInTimelineClick(eventId: EventId) { + plugins().forEach { + it.onViewInTimeline(eventId) + } + } + + override fun onItemDeleted() { + onDone() + } + + private val presenter = presenterFactory.create( + inputs = inputs, + navigator = this, + ) @Composable override fun View(modifier: Modifier) { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index 068fb02b0f..a2201aec65 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -11,9 +11,11 @@ import android.content.ActivityNotFoundException import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -25,8 +27,13 @@ import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther +import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn +import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory @@ -38,6 +45,8 @@ import io.element.android.libraries.androidutils.R as UtilsR class MediaViewerPresenter @AssistedInject constructor( @Assisted private val inputs: MediaViewerEntryPoint.Params, + @Assisted private val navigator: MediaViewerNavigator, + private val room: MatrixRoom, private val localMediaFactory: LocalMediaFactory, private val mediaLoader: MatrixMediaLoader, private val localMediaActions: LocalMediaActions, @@ -45,7 +54,10 @@ class MediaViewerPresenter @AssistedInject constructor( ) : Presenter { @AssistedFactory interface Factory { - fun create(inputs: MediaViewerEntryPoint.Params): MediaViewerPresenter + fun create( + inputs: MediaViewerEntryPoint.Params, + navigator: MediaViewerNavigator, + ): MediaViewerPresenter } @Composable @@ -67,6 +79,15 @@ class MediaViewerPresenter @AssistedInject constructor( } } + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() + val canDelete by produceState(false, syncUpdateFlow.value) { + value = when (inputs.mediaInfo.senderId) { + null -> false + room.sessionId -> room.canRedactOwn().getOrElse { false } && inputs.eventId != null + else -> room.canRedactOther().getOrElse { false } && inputs.eventId != null + } + } + fun handleEvents(mediaViewerEvents: MediaViewerEvents) { when (mediaViewerEvents) { MediaViewerEvents.RetryLoading -> loadMediaTrigger++ @@ -74,16 +95,23 @@ class MediaViewerPresenter @AssistedInject constructor( MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value) MediaViewerEvents.Share -> coroutineScope.share(localMedia.value) MediaViewerEvents.OpenWith -> coroutineScope.open(localMedia.value) + is MediaViewerEvents.Delete -> coroutineScope.delete(mediaViewerEvents.eventId) + is MediaViewerEvents.ViewInTimeline -> { + navigator.onViewInTimelineClick(mediaViewerEvents.eventId) + } } } return MediaViewerState( + eventId = inputs.eventId, mediaInfo = inputs.mediaInfo, thumbnailSource = inputs.thumbnailSource, downloadedMedia = localMedia.value, snackbarMessage = snackbarMessage, + canShowInfo = inputs.canShowInfo, canDownload = inputs.canDownload, canShare = inputs.canShare, + canDelete = canDelete, eventSink = ::handleEvents ) } @@ -126,6 +154,17 @@ class MediaViewerPresenter @AssistedInject constructor( } } + private fun CoroutineScope.delete(eventId: EventId) = launch { + room.liveTimeline.redactEvent(eventId.toEventOrTransactionId(), null) + .onFailure { + val snackbarMessage = SnackbarMessage(CommonStrings.error_unknown) + snackbarDispatcher.post(snackbarMessage) + } + .onSuccess { + navigator.onItemDeleted() + } + } + private fun CoroutineScope.share(localMedia: AsyncData) = launch { if (localMedia is AsyncData.Success) { localMediaActions.share(localMedia.data) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt index 94d6653241..bdd59c5426 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt @@ -9,16 +9,20 @@ package io.element.android.libraries.mediaviewer.impl.viewer import io.element.android.libraries.architecture.AsyncData 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.media.MediaSource import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia data class MediaViewerState( + val eventId: EventId?, val mediaInfo: MediaInfo, val thumbnailSource: MediaSource?, val downloadedMedia: AsyncData, val snackbarMessage: SnackbarMessage?, + val canShowInfo: Boolean, val canDownload: Boolean, val canShare: Boolean, + val canDelete: Boolean, val eventSink: (MediaViewerEvents) -> Unit, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 6c7a9fb704..003e079b99 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -30,10 +30,10 @@ open class MediaViewerStateProvider : PreviewParameterProvider caption = "A caption", ).let { aMediaViewerState( - AsyncData.Success( + downloadedMedia = AsyncData.Success( LocalMedia(Uri.EMPTY, it) ), - it, + mediaInfo = it, ) }, aVideoMediaInfo( @@ -42,50 +42,51 @@ open class MediaViewerStateProvider : PreviewParameterProvider caption = "A caption", ).let { aMediaViewerState( - AsyncData.Success( + downloadedMedia = AsyncData.Success( LocalMedia(Uri.EMPTY, it) ), - it, + mediaInfo = it, ) }, aPdfMediaInfo().let { aMediaViewerState( - AsyncData.Success( + downloadedMedia = AsyncData.Success( LocalMedia(Uri.EMPTY, it) ), - it, + mediaInfo = it, ) }, aMediaViewerState( - AsyncData.Loading(), - anApkMediaInfo(), + downloadedMedia = AsyncData.Loading(), + mediaInfo = anApkMediaInfo(), ), anApkMediaInfo().let { aMediaViewerState( - AsyncData.Success( + downloadedMedia = AsyncData.Success( LocalMedia(Uri.EMPTY, it) ), - it, + mediaInfo = it, ) }, aMediaViewerState( - AsyncData.Loading(), - anAudioMediaInfo(), + downloadedMedia = AsyncData.Loading(), + mediaInfo = anAudioMediaInfo(), ), anAudioMediaInfo().let { aMediaViewerState( - AsyncData.Success( + downloadedMedia = AsyncData.Success( LocalMedia(Uri.EMPTY, it) ), - it, + mediaInfo = it, ) }, anImageMediaInfo().let { aMediaViewerState( - AsyncData.Success( + downloadedMedia = AsyncData.Success( LocalMedia(Uri.EMPTY, it) ), - it, + mediaInfo = it, + canShowInfo = false, canDownload = false, canShare = false, ) @@ -96,15 +97,19 @@ open class MediaViewerStateProvider : PreviewParameterProvider fun aMediaViewerState( downloadedMedia: AsyncData = AsyncData.Uninitialized, mediaInfo: MediaInfo = anImageMediaInfo(), + canShowInfo: Boolean = true, canDownload: Boolean = true, canShare: Boolean = true, eventSink: (MediaViewerEvents) -> Unit = {}, ) = MediaViewerState( + eventId = null, mediaInfo = mediaInfo, thumbnailSource = null, downloadedMedia = downloadedMedia, snackbarMessage = null, + canShowInfo = canShowInfo, canDownload = canDownload, canShare = canShare, + canDelete = true, eventSink = eventSink, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 3a468eb0f5..f5fae4d3bb 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -68,6 +68,9 @@ import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.impl.R +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.details.MediaDeleteConfirmationBottomSheet +import io.element.android.libraries.mediaviewer.impl.details.MediaDetailsBottomSheet import io.element.android.libraries.mediaviewer.impl.local.LocalMediaView import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState @@ -92,6 +95,7 @@ fun MediaViewerView( val defaultBottomPaddingInPixels = if (LocalInspectionMode.current) 303 else 0 var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) } BackHandler { onBackClick() } + var mediaBottomSheetState by remember { mutableStateOf(MediaBottomSheetState.Hidden) } Scaffold( modifier, containerColor = Color.Transparent, @@ -121,7 +125,16 @@ fun MediaViewerView( mimeType = state.mediaInfo.mimeType, senderName = state.mediaInfo.senderName, dateSent = state.mediaInfo.dateSent, + canShowInfo = state.canShowInfo, onBackClick = onBackClick, + onInfoClick = { + mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = state.eventId, + canDelete = state.canDelete, + mediaInfo = state.mediaInfo, + thumbnailSource = state.thumbnailSource, + ) + }, eventSink = state.eventSink ) MediaViewerBottomBar( @@ -133,6 +146,40 @@ fun MediaViewerView( } } } + when (val bottomSheetState = mediaBottomSheetState) { + MediaBottomSheetState.Hidden -> Unit + is MediaBottomSheetState.MediaDetailsBottomSheetState -> { + MediaDetailsBottomSheet( + state = bottomSheetState, + onViewInTimeline = { + mediaBottomSheetState = MediaBottomSheetState.Hidden + state.eventSink(MediaViewerEvents.ViewInTimeline(it)) + }, + onDelete = { eventId -> + mediaBottomSheetState = MediaBottomSheetState.MediaDeleteConfirmationState( + eventId = eventId, + mediaInfo = state.mediaInfo, + thumbnailSource = state.thumbnailSource, + ) + }, + onDismiss = { + mediaBottomSheetState = MediaBottomSheetState.Hidden + }, + ) + } + is MediaBottomSheetState.MediaDeleteConfirmationState -> { + MediaDeleteConfirmationBottomSheet( + state = bottomSheetState, + onDelete = { + mediaBottomSheetState = MediaBottomSheetState.Hidden + state.eventSink(MediaViewerEvents.Delete(it)) + }, + onDismiss = { + mediaBottomSheetState = MediaBottomSheetState.Hidden + }, + ) + } + } } @Composable @@ -283,7 +330,9 @@ private fun MediaViewerTopBar( mimeType: String, senderName: String?, dateSent: String?, + canShowInfo: Boolean, onBackClick: () -> Unit, + onInfoClick: () -> Unit, eventSink: (MediaViewerEvents) -> Unit, ) { TopAppBar( @@ -354,7 +403,17 @@ private fun MediaViewerTopBar( ) } } - // TODO Add action to open infos. + if (canShowInfo) { + IconButton( + onClick = onInfoClick, + enabled = actionsEnabled, + ) { + Icon( + imageVector = CompoundIcons.Info(), + contentDescription = null, + ) + } + } } ) } diff --git a/libraries/mediaviewer/impl/src/main/res/values/localazy.xml b/libraries/mediaviewer/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..992b8edd88 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values/localazy.xml @@ -0,0 +1,16 @@ + + + "This file will be removed from the room and members won’t have access to it." + "Delete file?" + "Images and videos uploaded to this room will be shown here." + "No media uploaded yet" + "Loading files…" + "Loading media…" + "Files" + "Media" + "Media and files" + "File format" + "File name" + "Uploaded by" + "Uploaded on" + diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt new file mode 100644 index 0000000000..6462204721 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem + +class FakeEventItemFactory : EventItemFactory { + override fun create(currentTimelineItem: MatrixTimelineItem.Event): MediaItem.Event? { + return null + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt new file mode 100644 index 0000000000..6633fcbce1 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeMediaGalleryNavigator( + private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() } +) : MediaGalleryNavigator { + override fun onViewInTimelineClick(eventId: EventId) { + onViewInTimelineClickLambda(eventId) + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt new file mode 100644 index 0000000000..637c60d57d --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.ImmutableList + +class FakeMediaItemsPostProcessor : MediaItemsPostProcessor { + override fun process(mediaItems: AsyncData>, predicate: (MediaItem.Event) -> Boolean): AsyncData> { + return mediaItems + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt new file mode 100644 index 0000000000..618ba855ad --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.tests.testutils.lambda.lambdaError +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +class FakeTimelineMediaItemsFactory( + private val replaceWithLambda: (List) -> Unit = { lambdaError() }, + private val onCanPaginateLambda: () -> Unit = { lambdaError() } +) : TimelineMediaItemsFactory { + override val timelineItems: Flow> + get() = flowOf(emptyList().toImmutableList()) + + override suspend fun replaceWith(timelineItems: List) { + replaceWithLambda(timelineItems) + } + + override suspend fun onCanPaginate() { + onCanPaginateLambda() + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt new file mode 100644 index 0000000000..40e2780a41 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem + +class FakeVirtualItemFactory : VirtualItemFactory { + override fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? { + return null + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt new file mode 100644 index 0000000000..2d6a07e477 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -0,0 +1,261 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import android.net.Uri +import com.google.common.truth.Truth.assertThat +import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_ROOM_NAME +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.media.FakeMatrixMediaLoader +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.timeline.FakeTimeline +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.gallery.ui.anImage +import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions +import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory +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.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class MediaGalleryPresenterTest { + @get:Rule + val warmUpRule = WarmUpRule() + + private val mockMediaUri: Uri = mockk("localMediaUri") + private val localMediaFactory = FakeLocalMediaFactory(mockMediaUri) + + @Test + fun `present - initial state`() = runTest { + val onViewInTimelineClickLambda = lambdaRecorder { } + val navigator = FakeMediaGalleryNavigator( + onViewInTimelineClickLambda = onViewInTimelineClickLambda, + ) + val presenter = createMediaGalleryPresenter( + navigator = navigator, + room = FakeMatrixRoom( + displayName = A_ROOM_NAME, + mediaTimelineResult = { Result.success(FakeTimeline()) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + assertThat(initialState.roomName).isEqualTo(A_ROOM_NAME) + assertThat(initialState.imageItems.dataOrNull()).isEmpty() + assertThat(initialState.fileItems.dataOrNull()).isEmpty() + assertThat(initialState.snackbarMessage).isNull() + } + } + + @Test + fun `present - change mode`() = runTest { + val onViewInTimelineClickLambda = lambdaRecorder { } + val navigator = FakeMediaGalleryNavigator( + onViewInTimelineClickLambda = onViewInTimelineClickLambda, + ) + val presenter = createMediaGalleryPresenter( + navigator = navigator, + room = FakeMatrixRoom( + displayName = A_ROOM_NAME, + mediaTimelineResult = { Result.success(FakeTimeline()) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) + initialState.eventSink(MediaGalleryEvents.ChangeMode(MediaGalleryMode.Files)) + val state = awaitItem() + assertThat(state.mode).isEqualTo(MediaGalleryMode.Files) + state.eventSink(MediaGalleryEvents.ChangeMode(MediaGalleryMode.Images)) + val imageModeState = awaitItem() + assertThat(imageModeState.mode).isEqualTo(MediaGalleryMode.Images) + } + } + + @Test + fun `present - bottom sheet state - own message and can delete own`() = runTest { + `present - bottom sheet state - own message`(canDeleteOwn = true) + } + + @Test + fun `present - bottom sheet state - own message and cannot delete own`() = runTest { + `present - bottom sheet state - own message`(canDeleteOwn = false) + } + + private suspend fun `present - bottom sheet state - own message`(canDeleteOwn: Boolean) { + val presenter = createMediaGalleryPresenter( + room = FakeMatrixRoom( + sessionId = A_USER_ID, + displayName = A_ROOM_NAME, + mediaTimelineResult = { Result.success(FakeTimeline()) }, + canRedactOwnResult = { Result.success(canDeleteOwn) } + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + val item = anImage( + eventId = AN_EVENT_ID, + senderId = A_USER_ID, + ) + initialState.eventSink(MediaGalleryEvents.OpenInfo(item)) + val state = awaitItem() + assertThat(state.mediaBottomSheetState).isEqualTo( + MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = AN_EVENT_ID, + canDelete = canDeleteOwn, + mediaInfo = item.mediaInfo, + thumbnailSource = item.mediaSource, + ) + ) + // Close the bottom sheet + state.eventSink(MediaGalleryEvents.CloseBottomSheet) + val closedState = awaitItem() + assertThat(closedState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + } + } + + @Test + fun `present - bottom sheet state - other message and can delete other`() = runTest { + `present - bottom sheet state - other message`(canDeleteOther = true) + } + + @Test + fun `present - bottom sheet state - other message and cannot delete other`() = runTest { + `present - bottom sheet state - other message`(canDeleteOther = false) + } + + private suspend fun `present - bottom sheet state - other message`(canDeleteOther: Boolean) { + val presenter = createMediaGalleryPresenter( + room = FakeMatrixRoom( + sessionId = A_USER_ID, + displayName = A_ROOM_NAME, + mediaTimelineResult = { Result.success(FakeTimeline()) }, + canRedactOtherResult = { Result.success(canDeleteOther) } + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + val item = anImage( + eventId = AN_EVENT_ID, + senderId = A_USER_ID_2, + ) + initialState.eventSink(MediaGalleryEvents.OpenInfo(item)) + val state = awaitItem() + assertThat(state.mediaBottomSheetState).isEqualTo( + MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = AN_EVENT_ID, + canDelete = canDeleteOther, + mediaInfo = item.mediaInfo, + thumbnailSource = item.mediaSource, + ) + ) + // Close the bottom sheet + state.eventSink(MediaGalleryEvents.CloseBottomSheet) + val closedState = awaitItem() + assertThat(closedState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + } + } + + @Test + fun `present - delete bottom sheet`() = runTest { + val presenter = createMediaGalleryPresenter( + room = FakeMatrixRoom( + displayName = A_ROOM_NAME, + mediaTimelineResult = { Result.success(FakeTimeline()) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + // Delete bottom sheet + val item = anImage() + initialState.eventSink(MediaGalleryEvents.ConfirmDelete(AN_EVENT_ID, item.mediaInfo, item.thumbnailSource)) + val deleteState = awaitItem() + assertThat(deleteState.mediaBottomSheetState).isEqualTo( + MediaBottomSheetState.MediaDeleteConfirmationState( + eventId = AN_EVENT_ID, + mediaInfo = item.mediaInfo, + thumbnailSource = item.thumbnailSource, + ) + ) + // Close the bottom sheet + deleteState.eventSink(MediaGalleryEvents.CloseBottomSheet) + val deleteClosedState = awaitItem() + assertThat(deleteClosedState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + } + } + + @Test + fun `present - view in timeline invokes the navigator`() = runTest { + val onViewInTimelineClickLambda = lambdaRecorder { } + val navigator = FakeMediaGalleryNavigator( + onViewInTimelineClickLambda = onViewInTimelineClickLambda, + ) + val presenter = createMediaGalleryPresenter( + room = FakeMatrixRoom( + mediaTimelineResult = { Result.success(FakeTimeline()) }, + ), + navigator = navigator, + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + initialState.eventSink(MediaGalleryEvents.ViewInTimeline(AN_EVENT_ID)) + onViewInTimelineClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) + } + } + + private fun createMediaGalleryPresenter( + matrixMediaLoader: FakeMatrixMediaLoader = FakeMatrixMediaLoader(), + localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(), + snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), + navigator: MediaGalleryNavigator = FakeMediaGalleryNavigator(), + room: MatrixRoom = FakeMatrixRoom( + liveTimeline = FakeTimeline(), + ), + ): MediaGalleryPresenter { + return MediaGalleryPresenter( + navigator = navigator, + room = room, + timelineProvider = MediaGalleryTimelineProvider( + room = room, + networkMonitor = FakeNetworkMonitor(), + featureFlagService = FakeFeatureFlagService(), + ), + timelineMediaItemsFactory = FakeTimelineMediaItemsFactory( + replaceWithLambda = lambdaRecorder, Unit> { _ -> }, + onCanPaginateLambda = lambdaRecorder { }, + ), + localMediaFactory = localMediaFactory, + mediaLoader = matrixMediaLoader, + localMediaActions = localMediaActions, + snackbarDispatcher = snackbarDispatcher, + mediaItemsPostProcessor = FakeMediaItemsPostProcessor(), + ) + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index c341f6e751..bd9fe7d884 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -38,7 +38,9 @@ class AndroidLocalMediaFactoryTest { mimeType = MimeTypes.Jpeg, formattedFileSize = "4MB", fileExtension = "jpg", + senderId = null, senderName = A_USER_NAME, + senderAvatar = null, dateSent = "12:34" ) ) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt new file mode 100644 index 0000000000..c07c53f8ae --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.viewer + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeMediaViewerNavigator( + private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() }, + private val onItemDeletedLambda: () -> Unit = { lambdaError() }, +) : MediaViewerNavigator { + override fun onViewInTimelineClick(eventId: EventId) { + onViewInTimelineClickLambda(eventId) + } + + override fun onItemDeleted() { + onItemDeletedLambda() + } +} 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 cbe334216c..aaae1d8b79 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 @@ -16,20 +16,34 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.room.MatrixRoom +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.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID_2 +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader import io.element.android.libraries.matrix.test.media.aMediaSource +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.anApkMediaInfo import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory 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.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -private val TESTED_MEDIA_INFO = anApkMediaInfo() +private val TESTED_MEDIA_INFO = anApkMediaInfo( + senderId = A_USER_ID, +) class MediaViewerPresenterTest { @get:Rule @@ -38,11 +52,133 @@ class MediaViewerPresenterTest { private val mockMediaUri: Uri = mockk("localMediaUri") private val localMediaFactory = FakeLocalMediaFactory(mockMediaUri) + @Test + fun `present - initial state null Event`() = runTest { + val presenter = createMediaViewerPresenter( + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.snackbarMessage).isNull() + assertThat(initialState.canShowInfo).isTrue() + assertThat(initialState.canDownload).isTrue() + assertThat(initialState.canShare).isTrue() + assertThat(initialState.canDelete).isFalse() + } + } + + @Test + fun `present - initial state cannot show info`() = runTest { + val presenter = createMediaViewerPresenter( + canShowInfo = false, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.snackbarMessage).isNull() + assertThat(initialState.canShowInfo).isFalse() + assertThat(initialState.canDownload).isTrue() + assertThat(initialState.canShare).isTrue() + assertThat(initialState.canDelete).isFalse() + } + } + + @Test + fun `present - initial state cannot share`() = runTest { + val presenter = createMediaViewerPresenter( + canShare = false, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.snackbarMessage).isNull() + assertThat(initialState.canShowInfo).isTrue() + assertThat(initialState.canDownload).isTrue() + assertThat(initialState.canShare).isFalse() + assertThat(initialState.canDelete).isFalse() + } + } + + @Test + fun `present - initial state cannot download`() = runTest { + val presenter = createMediaViewerPresenter( + canDownload = false, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.snackbarMessage).isNull() + assertThat(initialState.canShowInfo).isTrue() + assertThat(initialState.canDownload).isFalse() + assertThat(initialState.canShare).isTrue() + assertThat(initialState.canDelete).isFalse() + } + } + + @Test + fun `present - initial state Event`() = runTest { + val presenter = createMediaViewerPresenter( + eventId = AN_EVENT_ID, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.snackbarMessage).isNull() + assertThat(initialState.canShowInfo).isTrue() + assertThat(initialState.canDownload).isTrue() + assertThat(initialState.canShare).isTrue() + assertThat(initialState.canDelete).isTrue() + } + } + + @Test + fun `present - initial state Event from other`() = runTest { + val presenter = createMediaViewerPresenter( + eventId = AN_EVENT_ID, + room = FakeMatrixRoom( + sessionId = A_SESSION_ID_2, + canRedactOtherResult = { Result.success(false) }, + ) + ) + presenter.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.snackbarMessage).isNull() + assertThat(initialState.canShowInfo).isTrue() + assertThat(initialState.canDownload).isTrue() + assertThat(initialState.canShare).isTrue() + assertThat(initialState.canDelete).isFalse() + } + } + @Test fun `present - download media success scenario`() = runTest { - val matrixMediaLoader = FakeMatrixMediaLoader() - val mediaActions = FakeLocalMediaActions() - val presenter = createMediaViewerPresenter(matrixMediaLoader, mediaActions) + val presenter = createMediaViewerPresenter( + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -60,10 +196,15 @@ class MediaViewerPresenterTest { @Test fun `present - check all actions`() = runTest { - val matrixMediaLoader = FakeMatrixMediaLoader() val mediaActions = FakeLocalMediaActions() val snackbarDispatcher = SnackbarDispatcher() - val presenter = createMediaViewerPresenter(matrixMediaLoader, mediaActions, snackbarDispatcher) + val presenter = createMediaViewerPresenter( + localMediaActions = mediaActions, + snackbarDispatcher = snackbarDispatcher, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -108,8 +249,12 @@ class MediaViewerPresenterTest { @Test fun `present - download media failure then retry with success scenario`() = runTest { val matrixMediaLoader = FakeMatrixMediaLoader() - val mediaActions = FakeLocalMediaActions() - val presenter = createMediaViewerPresenter(matrixMediaLoader, mediaActions) + val presenter = createMediaViewerPresenter( + matrixMediaLoader = matrixMediaLoader, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -134,18 +279,87 @@ class MediaViewerPresenterTest { } } + @Test + fun `present - delete media success scenario`() = runTest { + val redactEventLambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val timeline = FakeTimeline().apply { + this.redactEventLambda = redactEventLambda + } + val onItemDeletedLambda = lambdaRecorder { } + val navigator = FakeMediaViewerNavigator( + onItemDeletedLambda = onItemDeletedLambda, + ) + + val presenter = createMediaViewerPresenter( + room = FakeMatrixRoom( + liveTimeline = timeline, + canRedactOwnResult = { Result.success(true) }, + ), + mediaViewerNavigator = navigator, + ) + presenter.test { + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isEqualTo(AsyncData.Uninitialized) + assertThat(initialState.mediaInfo).isEqualTo(TESTED_MEDIA_INFO) + val loadingState = awaitItem() + assertThat(loadingState.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java) + val successState = awaitItem() + assertThat(successState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + successState.eventSink(MediaViewerEvents.Delete(AN_EVENT_ID)) + redactEventLambda.assertions().isCalledOnce().with( + value(AN_EVENT_ID.toEventOrTransactionId()), value(null) + ) + onItemDeletedLambda.assertions().isCalledOnce() + } + } + + @Test + fun `present - view in timeline invokes the navigator`() = runTest { + val onViewInTimelineClickLambda = lambdaRecorder { } + val navigator = FakeMediaViewerNavigator( + onViewInTimelineClickLambda = onViewInTimelineClickLambda, + ) + val presenter = createMediaViewerPresenter( + mediaViewerNavigator = navigator, + room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + ) + ) + presenter.test { + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isEqualTo(AsyncData.Uninitialized) + assertThat(initialState.mediaInfo).isEqualTo(TESTED_MEDIA_INFO) + val loadingState = awaitItem() + assertThat(loadingState.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java) + val successState = awaitItem() + assertThat(successState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) + successState.eventSink(MediaViewerEvents.ViewInTimeline(AN_EVENT_ID)) + onViewInTimelineClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) + } + } + private fun createMediaViewerPresenter( - matrixMediaLoader: FakeMatrixMediaLoader, - localMediaActions: FakeLocalMediaActions, + eventId: EventId? = null, + matrixMediaLoader: FakeMatrixMediaLoader = FakeMatrixMediaLoader(), + localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(), snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), + canShowInfo: Boolean = true, canShare: Boolean = true, canDownload: Boolean = true, + mediaViewerNavigator: MediaViewerNavigator = FakeMediaViewerNavigator(), + room: MatrixRoom = FakeMatrixRoom( + liveTimeline = FakeTimeline(), + ), ): MediaViewerPresenter { return MediaViewerPresenter( inputs = MediaViewerEntryPoint.Params( + eventId = eventId, mediaInfo = TESTED_MEDIA_INFO, mediaSource = aMediaSource(), thumbnailSource = null, + canShowInfo = canShowInfo, canShare = canShare, canDownload = canDownload, ), @@ -153,6 +367,9 @@ class MediaViewerPresenterTest { mediaLoader = matrixMediaLoader, localMediaActions = localMediaActions, snackbarDispatcher = snackbarDispatcher, + navigator = mediaViewerNavigator, + room = room, ) } } + diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index a0f36c6f0f..c41435afc0 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -37,7 +37,9 @@ class FakeLocalMediaFactory( mimeType = mimeType ?: fallbackMimeType, formattedFileSize = formattedFileSize ?: fallbackFileSize, fileExtension = fileExtensionExtractor.extractFromName(safeName), + senderId = null, senderName = null, + senderAvatar = null, dateSent = null ) return aLocalMedia(uri, mediaInfo) diff --git a/tools/localazy/config.json b/tools/localazy/config.json index a365379344..fe2f7d3e03 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -92,6 +92,13 @@ "error_no_compatible_app_found" ] }, + { + "name" : ":libraries:mediaviewer:impl", + "includeRegex" : [ + "screen\\.media_details\\..*", + "screen_media_browser_.*" + ] + }, { "name" : ":libraries:eventformatter:impl", "includeRegex" : [ From 653c1e8214e9e2699380f3ba089a7d4fbc6ec4a1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2024 17:16:29 +0100 Subject: [PATCH 037/203] Sync strings. --- .../src/main/res/values-cs/translations.xml | 9 ++++++++ .../src/main/res/values-de/translations.xml | 8 +++++++ .../src/main/res/values-el/translations.xml | 8 +++++++ .../src/main/res/values-et/translations.xml | 8 +++++++ .../src/main/res/values-fr/translations.xml | 8 +++++++ .../src/main/res/values-hu/translations.xml | 8 +++++++ .../src/main/res/values-it/translations.xml | 8 +++++++ .../src/main/res/values-ru/translations.xml | 9 ++++++++ .../src/main/res/values-sk/translations.xml | 9 ++++++++ .../src/main/res/values-cs/translations.xml | 10 +++++++++ .../src/main/res/values-de/translations.xml | 14 +++++++++++++ .../src/main/res/values-et/translations.xml | 16 ++++++++++++++ .../src/main/res/values-fr/translations.xml | 16 ++++++++++++++ .../src/main/res/values-hu/translations.xml | 16 ++++++++++++++ .../src/main/res/values-it/translations.xml | 8 +++++++ .../src/main/res/values-ru/translations.xml | 16 ++++++++++++++ .../impl/src/main/res/values/localazy.xml | 2 ++ .../src/main/res/values-cs/translations.xml | 16 -------------- .../src/main/res/values-de/translations.xml | 19 ----------------- .../src/main/res/values-el/translations.xml | 8 ------- .../src/main/res/values-et/translations.xml | 15 ------------- .../src/main/res/values-fr/translations.xml | 21 ------------------- .../src/main/res/values-hu/translations.xml | 21 ------------------- .../src/main/res/values-it/translations.xml | 13 ------------ .../src/main/res/values-ru/translations.xml | 16 -------------- .../src/main/res/values-sk/translations.xml | 9 -------- .../src/main/res/values/localazy.xml | 15 ++----------- 27 files changed, 175 insertions(+), 151 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-de/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-et/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-it/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml diff --git a/features/knockrequests/impl/src/main/res/values-cs/translations.xml b/features/knockrequests/impl/src/main/res/values-cs/translations.xml index b02fdf8595..b8c4f6f125 100644 --- a/features/knockrequests/impl/src/main/res/values-cs/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-cs/translations.xml @@ -14,4 +14,13 @@ "Když někdo požádá o vstup do místnosti, uvidíte jeho žádost zde." "Žádná čekající žádost o vstup" "Žádosti o vstup" + + "%1$s +%2$d další chce vstoupit do této místnosti" + "%1$s +%2$d další chtějí vstoupit do této místnosti" + "%1$s +%2$d dalších chce vstoupit do této místnosti" + + "Zobrazit vše" + "Přijmout" + "%1$s chce vstoupit do této místnosti" + "Zobrazit" diff --git a/features/knockrequests/impl/src/main/res/values-de/translations.xml b/features/knockrequests/impl/src/main/res/values-de/translations.xml index 693b1c8882..70e43ba076 100644 --- a/features/knockrequests/impl/src/main/res/values-de/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-de/translations.xml @@ -14,4 +14,12 @@ "Falls jemand um Aufnahme in den Raum bittet, können Sie dessen Anfrage hier sehen." "Keine ausstehende Beitrittsanfrage" "Beitrittsanfragen" + + "%1$s+ %2$d andere wollen diesem Chatroom beitreten" + "%1$s+ %2$d andere wollen diesem Chatroom beitreten" + + "Alles ansehen" + "Akzeptieren" + "%1$s möchte diesem Chatroom beitreten" + "Ansicht" diff --git a/features/knockrequests/impl/src/main/res/values-el/translations.xml b/features/knockrequests/impl/src/main/res/values-el/translations.xml index 794312dd93..a59b6757db 100644 --- a/features/knockrequests/impl/src/main/res/values-el/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-el/translations.xml @@ -14,4 +14,12 @@ "Όταν κάποιος θα ζητήσει να συμμετάσχει στο δωμάτιο, θα μπορείς να δεις το αίτημά του εδώ." "Δεν υπάρχει εκκρεμές αίτημα συμμετοχής" "Αιτήματα συμμετοχής" + + "Οι χρήστες %1$s +%2$d ακόμη θέλουν να συμμετάσχουν σε αυτό το δωμάτιο" + "Οι χρήστες %1$s +%2$d ακόμη θέλουν να συμμετάσχουν σε αυτό το δωμάτιο" + + "Προβολή όλων" + "Αποδοχή" + "Ο χρήστης %1$s θέλει να μπει σε αυτό το δωμάτιο" + "Προβολή" diff --git a/features/knockrequests/impl/src/main/res/values-et/translations.xml b/features/knockrequests/impl/src/main/res/values-et/translations.xml index bd647dcb09..79d9c56964 100644 --- a/features/knockrequests/impl/src/main/res/values-et/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-et/translations.xml @@ -14,4 +14,12 @@ "Kui keegi soovib jututoaga liituda, siis need päringud on kuvatud siin." "Pole ühtegi liitumispalvet" "Liitumispalved" + + "%1$s + veel %2$d kasutaja soovivad selle jututoaga liituda" + "%1$s + veel %2$d kasutajat soovivad selle jututoaga liituda" + + "Vaata kõiki" + "Nõustu" + "%1$s soovib selle jututoaga liituda" + "Vaata" diff --git a/features/knockrequests/impl/src/main/res/values-fr/translations.xml b/features/knockrequests/impl/src/main/res/values-fr/translations.xml index 6632df1dd3..39bc75f5c1 100644 --- a/features/knockrequests/impl/src/main/res/values-fr/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-fr/translations.xml @@ -14,4 +14,12 @@ "Lorsque quelqu’un demandera à rejoindre le salon, vous pourrez voir sa demande ici." "Personne ne demande à rejoindre le salon" "Demandes en attente" + + "%1$s et %2$d autre personne souhaitent rejoindre ce salon" + "%1$s et %2$d autres personnes souhaitent rejoindre ce salon" + + "Tout afficher" + "Accepter" + "%1$s souhaite rejoindre ce salon" + "Voir" diff --git a/features/knockrequests/impl/src/main/res/values-hu/translations.xml b/features/knockrequests/impl/src/main/res/values-hu/translations.xml index 2093d0103d..ba1aa620c6 100644 --- a/features/knockrequests/impl/src/main/res/values-hu/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-hu/translations.xml @@ -14,4 +14,12 @@ "Ha valaki csatlakozni kíván a szobához, itt láthatja a kérését." "Nincs függőben lévő csatlakozási kérelem" "Csatlakozási kérelmek" + + "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" + "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" + + "Összes megtekintése" + "Elfogadás" + "%1$s szeretne csatlakozni ehhez a szobához" + "Megtekintés" diff --git a/features/knockrequests/impl/src/main/res/values-it/translations.xml b/features/knockrequests/impl/src/main/res/values-it/translations.xml index a3e05bb8ef..ebdba8074a 100644 --- a/features/knockrequests/impl/src/main/res/values-it/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-it/translations.xml @@ -14,4 +14,12 @@ "Quando qualcuno ti chiederà di entrare nella stanza, potrai vedere la sua richiesta qui." "Nessuna richiesta di accesso in sospeso" "Richieste di accesso" + + "%1$s +%2$d vogliono entrare in questa stanza" + "%1$s +%2$d vogliono entrare in questa stanza" + + "Visualizza tutte" + "Accetta" + "%1$s vuole entrare in questa stanza" + "Visualizza" diff --git a/features/knockrequests/impl/src/main/res/values-ru/translations.xml b/features/knockrequests/impl/src/main/res/values-ru/translations.xml index 2542195139..1023af2d28 100644 --- a/features/knockrequests/impl/src/main/res/values-ru/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-ru/translations.xml @@ -14,4 +14,13 @@ "Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате." "Нет ожидающих запросов на присоединение" "Запросы на присоединение" + + "%1$s +%2$d хочет присоединиться к этой комнате" + "%1$s +%2$d хотят присоединиться к этой комнате" + "%1$s +%2$d хотят присоединиться к этой комнате" + + "Показать все" + "Принять" + "%1$s хочет присоединиться к этой комнате" + "Просмотр" diff --git a/features/knockrequests/impl/src/main/res/values-sk/translations.xml b/features/knockrequests/impl/src/main/res/values-sk/translations.xml index 4dff32780a..1504ef8631 100644 --- a/features/knockrequests/impl/src/main/res/values-sk/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-sk/translations.xml @@ -5,4 +5,13 @@ "Keď niekto požiada, aby sa pripojil k miestnosti, jeho žiadosť si môžete pozrieť tu." "Žiadna čakajúca žiadosť o pripojenie" "Žiadosti o pripojenie" + + "%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti" + "%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti" + "%1$s +%2$d ďalších chce vstúpiť do tejto miestnosti" + + "Zobraziť všetko" + "Prijať" + "%1$s chce vstúpiť do tejto miestnosti" + "Zobraziť" diff --git a/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..79f3b646ee --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,10 @@ + + + "Obrázky a videa nahraná do této místnosti budou zobrazeny zde." + "Zatím nebyla nahrána žádná média" + "Načítání souborů…" + "Načítání médií…" + "Soubory" + "Média" + "Média a soubory" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..07438166f6 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,14 @@ + + + "In diesen Chatroom hochgeladene Bilder und Videos werden hier angezeigt." + "Noch keine Medien hochgeladen" + "Dateien werden geladen…" + "Medien werden geladen…" + "Dateien" + "Medien" + "Medien und Dateien" + "Dateiformat" + "Dateiname" + "Hochgeladen von" + "Hochgeladen am" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..396138c100 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,16 @@ + + + "Järgnevaga eemaldame selle faili jututoast ka tema liikmed enam ei pääse failile ligi." + "Kas kustutame faili?" + "Antud jututuppa üleslaaditud pildid ja videod kuvatakse siin." + "Mitte keegi pole veel meediat üles laadinud" + "Laadime faile…" + "Laadime meediat…" + "Failid" + "Meedia" + "Meedia ja failid" + "Failivorming" + "Failinimi" + "Üleslaadija" + "Üleslaaditud" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..bd961fb941 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,16 @@ + + + "Ce fichier sera supprimé du salon et les membres n’y auront plus accès." + "Supprimer le fichier ?" + "Les images et vidéos envoyées dans ce salon seront affichées ici." + "Aucun média n’a encore été envoyé dans ce salon" + "Chargement des fichiers…" + "Chargement des médias…" + "Fichiers" + "Média" + "Médias et fichiers" + "Format du fichier" + "Nom du fichier" + "Envoyé par" + "Envoyé le" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..1fcc528dc5 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,16 @@ + + + "Ez a fájl el lesz távolítva a szobából, és a tagok nem férhetnek hozzá." + "Törli a fájlt?" + "Az ebbe a szobába feltöltött képek és videók itt jelennek meg." + "Még nincs feltöltött média" + "Fájlok betöltése…" + "Média betöltése…" + "Fájlok" + "Média" + "Média és fájlok" + "Fájlformátum" + "Fájlnév" + "Feltöltötte:" + "Feltöltve:" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..45d160f3d2 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,8 @@ + + + "Le immagini e i video caricati in questa stanza verranno mostrati qui." + "Nessun file multimediale ancora caricato" + "File" + "Contenuti multimediali" + "File e contenuti multimediali" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..713b748617 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,16 @@ + + + "Этот файл будет удален из комнаты и участники не будут иметь к нему доступ." + "Удалить файл?" + "Здесь будут показаны изображения и видео, загруженные в данную комнату." + "Пока что нет загруженных медиафайлов" + "Загрузка файлов…" + "Загрузка медиа…" + "Файлы" + "Медиа" + "Медиа и файлы" + "Формат файла" + "Имя файла" + "Загружено" + "Загружено на" + diff --git a/libraries/mediaviewer/impl/src/main/res/values/localazy.xml b/libraries/mediaviewer/impl/src/main/res/values/localazy.xml index 992b8edd88..b35a4819f1 100644 --- a/libraries/mediaviewer/impl/src/main/res/values/localazy.xml +++ b/libraries/mediaviewer/impl/src/main/res/values/localazy.xml @@ -11,6 +11,8 @@ "Media and files" "File format" "File name" + "This file will be removed from the room and members won’t have access to it." + "Delete file?" "Uploaded by" "Uploaded on" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index f1b88a1c76..9479eb43c4 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -303,13 +303,6 @@ Důvod: %1$s." "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" "Zatřeste zařízením pro nahlášení chyby" - "Obrázky a videa nahraná do této místnosti budou zobrazeny zde." - "Zatím nebyla nahrána žádná média" - "Načítání souborů…" - "Načítání médií…" - "Soubory" - "Média" - "Média a soubory" "Výběr média se nezdařil, zkuste to prosím znovu." "Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace." "Nahrání média se nezdařilo, zkuste to prosím znovu." @@ -334,19 +327,10 @@ Důvod: %1$s." "Vaše zpráva nebyla odeslána, protože jste neověřili jedno nebo více zařízení" "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nepodařilo se načíst údaje o uživateli" - - "%1$s +%2$d další chce vstoupit do této místnosti" - "%1$s +%2$d další chtějí vstoupit do této místnosti" - "%1$s +%2$d dalších chce vstoupit do této místnosti" - - "Zobrazit vše" "%1$s z %2$s" "%1$s Připnuté zprávy" "Načítání zprávy…" "Zobrazit vše" - "Přijmout" - "%1$s chce vstoupit do této místnosti" - "Zobrazit" "Chat" "Žádost o vstup odeslána" "Sdílet polohu" diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 4f2ca9bf97..0d7acd7e90 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -299,17 +299,6 @@ Grund: %1$s." "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" "Schüttel heftig zum Melden von Fehlern" - "In diesen Chatroom hochgeladene Bilder und Videos werden hier angezeigt." - "Noch keine Medien hochgeladen" - "Dateien werden geladen…" - "Medien werden geladen…" - "Dateien" - "Medien" - "Medien und Dateien" - "Dateiformat" - "Dateiname" - "Hochgeladen von" - "Hochgeladen am" "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." @@ -333,18 +322,10 @@ Grund: %1$s." "Ihre Nachricht wurde nicht geschickt, da Sie eines oder mehrere Ihrer Geräte nicht verifiziert haben." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Benutzerdetails konnten nicht abgerufen werden" - - "%1$s+ %2$d andere wollen diesem Chatroom beitreten" - "%1$s+ %2$d andere wollen diesem Chatroom beitreten" - - "Alles ansehen" "%1$s von %2$s" "%1$s fixierte Nachrichten" "Nachricht wird geladen…" "Alle anzeigen" - "Akzeptieren" - "%1$s möchte diesem Chatroom beitreten" - "Ansicht" "Chat" "Beitrittsanfrage gesendet" "Standort teilen" diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index 89e96e3f3c..c05000f3a5 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -322,18 +322,10 @@ "Το μήνυμά σου δεν στάλθηκε επειδή δεν έχεις επαληθεύσει τουλάχιστον μία από τις συσκευές σου" "Αποτυχία μεταφόρτωσης μέσου, δοκίμασε ξανά." "Δεν ήταν δυνατή η ανάκτηση στοιχείων χρήστη" - - "Οι χρήστες %1$s +%2$d ακόμη θέλουν να συμμετάσχουν σε αυτό το δωμάτιο" - "Οι χρήστες %1$s +%2$d ακόμη θέλουν να συμμετάσχουν σε αυτό το δωμάτιο" - - "Προβολή όλων" "%1$s από %2$s" "%1$s Καρφιτσωμένα μηνύματα" "Φόρτωση μηνύματος…" "Προβολή Όλων" - "Αποδοχή" - "Ο χρήστης %1$s θέλει να μπει σε αυτό το δωμάτιο" - "Προβολή" "Συνομιλία" "Το αίτημα συμμετοχής στάλθηκε" "Κοινή χρήση τοποθεσίας" diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index b09e10b8b3..0d7f51baef 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -299,13 +299,6 @@ Põhjus: %1$s." "Hei, suhtle minuga %1$s võrgus: %2$s" "%1$s Android" "Veast teatamiseks raputa nutiseadet ägedalt" - "Antud jututuppa üleslaaditud pildid ja videod kuvatakse siin." - "Mitte keegi pole veel meediat üles laadinud" - "Laadime faile…" - "Laadime meediat…" - "Failid" - "Meedia" - "Meedia ja failid" "Meediafaili valimine ei õnnestunud. Palun proovi uuesti." "Selgitused ja alapealkirjad ei pruugi olla nähtavad vanemate rakenduste kasutajatele." "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." @@ -329,18 +322,10 @@ Põhjus: %1$s." "Kuna sul on üks või enam verifitseerimata seadet, siis sinu sõnum jäi saatmata" "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." "Kasutaja andmete laadimine ei õnnestunud" - - "%1$s + veel %2$d kasutaja soovivad selle jututoaga liituda" - "%1$s + veel %2$d kasutajat soovivad selle jututoaga liituda" - - "Vaata kõiki" "%1$s / %2$s" "%1$s esiletõstetud sõnumit" "Laadime sõnumit…" "Näita kõiki" - "Nõustu" - "%1$s soovib selle jututoaga liituda" - "Vaata" "Vestlus" "Liitumispäring on saadetud" "Jaga asukohta" diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index ac4bff851e..ecf513c269 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -299,19 +299,6 @@ Raison : %1$s." "Salut, parle-moi sur %1$s : %2$s" "%1$s Android" "Rageshake pour signaler un problème" - "Ce fichier sera supprimé du salon et les membres n’y auront plus accès." - "Supprimer le fichier ?" - "Les images et vidéos envoyées dans ce salon seront affichées ici." - "Aucun média n’a encore été envoyé dans ce salon" - "Chargement des fichiers…" - "Chargement des médias…" - "Fichiers" - "Média" - "Médias et fichiers" - "Format du fichier" - "Nom du fichier" - "Envoyé par" - "Envoyé le" "Échec de la sélection du média, veuillez réessayer." "Les légendes peuvent ne pas être visibles pour les utilisateurs d’anciennes applications." "Échec du traitement des médias à télécharger, veuillez réessayer." @@ -335,18 +322,10 @@ Raison : %1$s." "Votre message n’a pas été envoyé car vous n’avez pas vérifié tous vos appareils" "Échec du traitement des médias à télécharger, veuillez réessayer." "Impossible de récupérer les détails de l’utilisateur" - - "%1$s et %2$d autre personne souhaitent rejoindre ce salon" - "%1$s et %2$d autres personnes souhaitent rejoindre ce salon" - - "Tout afficher" "%1$s sur %2$s" "%1$s Messages épinglés" "Chargement du message…" "Voir tout" - "Accepter" - "%1$s souhaite rejoindre ce salon" - "Voir" "Discussion" "Demande d’adhésion envoyée" "Partage de position" diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index b30c6f6896..0f1bb7507f 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -299,19 +299,6 @@ Ok: %1$s." "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" - "Ez a fájl el lesz távolítva a szobából, és a tagok nem férhetnek hozzá." - "Törli a fájlt?" - "Az ebbe a szobába feltöltött képek és videók itt jelennek meg." - "Még nincs feltöltött média" - "Fájlok betöltése…" - "Média betöltése…" - "Fájlok" - "Média" - "Média és fájlok" - "Fájlformátum" - "Fájlnév" - "Feltöltötte:" - "Feltöltve:" "Nem sikerült kiválasztani a médiát, próbálja újra." "Előfordulhat, hogy a feliratok nem láthatók a régebbi alkalmazásokat használók számára." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." @@ -335,18 +322,10 @@ Ok: %1$s." "Az üzenet nem lett elküldve, mert egy vagy több eszközét nem ellenőrizte" "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." "Nem sikerült letölteni a felhasználói adatokat" - - "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" - "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" - - "Összes megtekintése" "%1$s / %2$s" "%1$s kitűzött üzenet" "Üzenet betöltése…" "Összes megtekintése" - "Elfogadás" - "%1$s szeretne csatlakozni ehhez a szobához" - "Megtekintés" "Csevegés" "Csatlakozási kérés elküldve" "Hely megosztása" diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index 69bfd8cc95..68547a7f7e 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -299,11 +299,6 @@ Motivo:. %1$s" "Ehi, parliamo su %1$s: %2$s" "%1$s Android" "Scuoti per segnalare un problema" - "Le immagini e i video caricati in questa stanza verranno mostrati qui." - "Nessun file multimediale ancora caricato" - "File" - "Contenuti multimediali" - "File e contenuti multimediali" "Selezione del file multimediale fallita, riprova." "Le didascalie potrebbero non essere visibili agli utenti di app meno recenti." "Elaborazione del file multimediale da caricare fallita, riprova." @@ -327,18 +322,10 @@ Motivo:. %1$s" "Il tuo messaggio non è stato inviato perché non hai verificato uno o più dispositivi." "Elaborazione del file multimediale da caricare fallita, riprova." "Impossibile recuperare i dettagli dell\'utente" - - "%1$s +%2$d vogliono entrare in questa stanza" - "%1$s +%2$d vogliono entrare in questa stanza" - - "Visualizza tutte" "%1$s di %2$s" "%1$s Messaggi fissati" "Caricamento messaggio…" "Mostra tutti" - "Accetta" - "%1$s vuole entrare in questa stanza" - "Visualizza" "Conversazione" "Richiesta di accesso inviata" "Condividi posizione" 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 7bb6675bd5..b2a05f452c 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -303,13 +303,6 @@ "Привет, поговори со мной по %1$s: %2$s" "%1$s Android" "Встряхните устройство, чтобы сообщить об ошибке" - "Здесь будут показаны изображения и видео, загруженные в данную комнату." - "Пока что нет загруженных медиафайлов" - "Загрузка файлов…" - "Загрузка медиа…" - "Файлы" - "Медиа" - "Медиа и файлы" "Не удалось выбрать носитель, попробуйте еще раз." "Подпись может быть не видна пользователям старых приложений." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." @@ -334,19 +327,10 @@ "Ваше сообщение не было отправлено, поскольку вы не подтвердили одно или несколько своих устройств." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось получить данные о пользователе" - - "%1$s +%2$d хочет присоединиться к этой комнате" - "%1$s +%2$d хотят присоединиться к этой комнате" - "%1$s +%2$d хотят присоединиться к этой комнате" - - "Показать все" "%1$s из %2$s" "%1$s Закрепленные сообщения" "Загрузка сообщения…" "Посмотреть все" - "Принять" - "%1$s хочет присоединиться к этой комнате" - "Просмотр" "Чат" "Запрос на присоединение отправлен" "Поделиться местоположением" diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index c43567ea44..1c26d0eaae 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -323,19 +323,10 @@ Dôvod: %1$s." "Vaša správa nebola odoslaná, pretože ste neoverili jedno alebo viac svojich zariadení" "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa získať údaje o používateľovi" - - "%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti" - "%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti" - "%1$s +%2$d ďalších chce vstúpiť do tejto miestnosti" - - "Zobraziť všetko" "%1$s z %2$s" "%1$s Pripnutých správ" "Načítava sa správa…" "Zobraziť všetko" - "Prijať" - "%1$s chce vstúpiť do tejto miestnosti" - "Zobraziť" "Konverzácia" "Žiadosť o vstup odoslaná" "Zdieľať polohu" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 27be174f00..b571a23230 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -148,6 +148,7 @@ "Device ID" "Direct chat" "Do not show this again" + "Downloading" "(edited)" "Editing" "Editing caption" @@ -299,19 +300,6 @@ Reason: %1$s." "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "This file will be removed from the room and members won’t have access to it." - "Delete file?" - "Images and videos uploaded to this room will be shown here." - "No media uploaded yet" - "Loading files…" - "Loading media…" - "Files" - "Media" - "Media and files" - "File format" - "File name" - "Uploaded by" - "Uploaded on" "Failed selecting media, please try again." "Captions might not be visible to people using older apps." "Failed processing media to upload, please try again." @@ -355,6 +343,7 @@ Reason: %1$s." "en" "en" "Historical messages are not available on this device" + "You don\'t have access to this message" "Unable to decrypt message" "This message was blocked either because you did not verify your device or because the sender needs to verify your identity." From d81859705d5ff5b6b0eb6a7c181383de5611d7ce Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2024 17:33:18 +0100 Subject: [PATCH 038/203] Fix preview. --- .../impl/gallery/MediaGalleryStateProvider.kt | 41 +++++++++++-------- .../impl/gallery/ui/MediaItemImageProvider.kt | 3 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index d0a92f1b5d..d357ae7a0c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate import io.element.android.libraries.mediaviewer.impl.gallery.ui.aFile @@ -27,16 +28,19 @@ open class MediaGalleryStateProvider : PreviewParameterProvider Date: Mon, 9 Dec 2024 16:45:31 +0000 Subject: [PATCH 039/203] Update screenshots --- .../images/features.roomdetails.impl_RoomDetailsDark_0_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_10_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_11_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_12_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_13_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_1_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_2_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_3_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_4_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_5_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_6_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_7_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_8_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_9_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_0_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_10_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_11_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_12_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_13_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_1_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_2_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_3_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_4_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_5_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_6_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_7_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_8_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_9_en.png | 4 ++-- ...es.designsystem.components.avatar_Avatar_Avatars_84_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_85_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_86_en.png | 3 +++ ...pl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png | 3 +++ ....details_MediaDeleteConfirmationBottomSheet_Night_0_en.png | 3 +++ ...iaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png | 3 +++ ...viewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png | 3 +++ ...ries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en.png | 3 +++ ...ries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en.png | 3 +++ ...es.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en.png | 3 +++ ...es.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en.png | 3 +++ ...ries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png | 3 +++ ...ries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png | 3 +++ ...ries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png | 3 +++ ...es.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png | 3 +++ ...es.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png | 3 +++ ...es.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png | 3 +++ ...libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png | 4 ++-- 79 files changed, 199 insertions(+), 76 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_84_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_85_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_86_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png index 76446e6180..12b3132118 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94f040a3d18493f80b5f90eb48e68c664de5ddee0ae4575905ce35709d31abe9 -size 40969 +oid sha256:c7828106cd3724769c5bbeaad50c3417264abcb6af40ffd90aee283e8b29e579 +size 41831 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png index 52912aa0fd..58d7c45b3d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96cf72bdceae29a86593ed3bd02d5edbe1f5422e5be0798f536b49805088b0b8 -size 45109 +oid sha256:f9c24abc59ed8ef26f647b5d3855b768af6043bdbb035f0d2756a7b783f64561 +size 39848 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png index 0ae9c7dc0e..e2e7745abc 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c719ba2c0782ecf8ebf37c07dbc79d37b1d993e4987388ceafbefb31b03d100 -size 44064 +oid sha256:b87d165479dd2a0d6497fdac37bb43de760fd0eade06ad23b53baff667b8af03 +size 38783 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png index fba01a25c6..09080780f9 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a675afee3fcef0f8468ec93e33e1e86398bda517f4f54615aaf527d549387431 -size 47217 +oid sha256:2e7726872c78a2bcddfefa689699d7bf69a09b55c023bbc7008575fbec5b7779 +size 41986 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png index b3273a0efd..62a3a99fba 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ced35352da8f7b6c9d4a5647cf1ff29f194d4f68ca9eec9c268ab889271e4776 -size 45507 +oid sha256:6235b9e9b6ffb9e4d813cfa49c9819a3cbc112d6dc5d25d7901a257e4353e609 +size 40241 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png index 4a0208786f..4f70fd407b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3444cc70e1f1b212d89ba404199a439a498281aa9faff9a9bb2469b727498224 -size 37486 +oid sha256:75bcd07324f5acb45552d8d5bbe369cece798d109f3c096859c6a88fccc8e2e1 +size 39797 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png index 16b2995961..5591d4b251 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afcf1a235cd16b501ec02f7da90cf4800df41ab07383b7e6ed502f4e9249855e -size 38354 +oid sha256:f69da1fef91846329a0835dfc2be82b1f49892193dadf139f6ac262355aff86e +size 39910 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png index cedbb0f72d..406387611a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5338fba98c85142b4300467a564e8920627ba83ec91dffb7e2370d07447b8d78 -size 38479 +oid sha256:2ddb13a08e77486addc983662966128492074b5a2ddc4afb193ba459c8366952 +size 40544 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png index f7c16b996f..ac33cc9543 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c7887b5f1cc07ef30ef347c149af51edbc1c4539615c04fef57737839677423 -size 44293 +oid sha256:003386cc6af1fb6c3d52724e234627021a131f6eaf4c5261f58bfbba9d87bb54 +size 40981 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png index 1774b7ba41..15c85d775e 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11c9af054eb293134755003d8864305d9f2ef9a7597df795e4354e4f7c420166 -size 42209 +oid sha256:9f50132f14d4f26077cbb44e616b4b76070fd6df20e14af740e2743d1fc874b8 +size 40020 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png index 8b0e9ee674..55ca265ccf 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3bd2e90f06f259b158b6438ff4cd045733362ad215b933ddbb855043a5b8fa2 -size 38463 +oid sha256:76f09daad900e1c3eac91d07ec81c6a714f80ae9a734e3a3921dc5b259bab278 +size 40502 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png index 52789eb061..4bc9febcfd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae95b22ef977a95f9de6dba9e8ed2b33ebca808db4437512be39f020fd8831da -size 46411 +oid sha256:0b2d15ec833e6e20fe302d377c11f686275d5d115dbba070d623c46f66f95f95 +size 41111 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png index aa849d237d..4aaa0ce01a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2724c07c5ce097c2c470ecb4168fe2d101fb56a2b37a88065aa9210b14b871be -size 45403 +oid sha256:0f8a39cfe4c761462b84425e1008a5c36c8f93a6d00e7e6f2ece108575bf6b89 +size 40124 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png index c4aca87a18..3a7de20375 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df340c45b1d0a07f53adcc2872aef1a691c4fe4d0280e961524281a3dc1e427b -size 45412 +oid sha256:959b422fefa73279a7a3ef2193c7cd5c597fbedbaddea7de245cc86b820c2514 +size 40158 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png index a6694758f1..8e8c5d5fcd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bee1a47e22df24ba29b46dac1c8c2da8c38b3d438f8ef5b72dc3c39b0900338a -size 41908 +oid sha256:da5e1b0b8dc2663baf0e491878868f43c078d6be0d81d4776dacbd28f34d794c +size 42866 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png index c09b745e24..11c6879648 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:655995891afd39283b5271280d594d6b2ca0e3ff004e81ebbc4e351be0cd185e -size 46007 +oid sha256:0837ae930f448d0cf0f0614c437ead15398cafa5a96aa50e0967cf297d3ff355 +size 40662 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png index c3ddd6dd92..2485aea5d3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff42edd6e8f1165bfe8ad24ad2e1a37a34138b30193283ceb070e09273c37247 -size 44976 +oid sha256:72b557a35f41804729912f653ff64a70c9173aeff63471367bf4f5e88bd8e7b8 +size 39664 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png index ea86ba1b2e..c6b95729a7 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a21c4945fa617d0bdd5549c98a0b18067f302cb71e7e012728a61120a6ba7269 -size 47772 +oid sha256:684bae4170ee939c3317880889fa28e4c467ddf704a13280ca9a1e33d9ac7776 +size 42526 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png index b4d73c57b7..72c9a2497c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc76558ad62b1d9ec77afd066b2c64edc7241b93aaa431d8545041f7024315b1 -size 46443 +oid sha256:2c96854d4054cec6a4e3f2f642def4abc500f9a22dfe1aadd24cf45eb326a815 +size 41109 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png index fec1bbe806..34156265b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab627c807db2e5bcf339f01fd0f11f5e79529b32165d96f2a192faa863c38dd4 -size 38380 +oid sha256:f47468b9b9dc5c7c91b2d0b1446fd3f6e45ea9ee5b760029421a28e52410db77 +size 40935 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png index 7f4fb4df94..17e997b919 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af7f4944f2e1bd57e1a02716ff02a67beeb8a05847a0d06387fa0a8ca5ec0481 -size 39221 +oid sha256:0ff7f7f25df81af089c0c22bb7937f01ea1d05e7acc09de1fd8c355f03c329c7 +size 41035 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png index fe253a9a6e..cdf150fbc0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:647a6e7c0fdbb3d89aa411c14a2f41b127dd597fa157f4ef37a909132368c47e -size 39114 +oid sha256:b6259c338f58959fc732676553418f8b6dfd5e50cd3b55ceac75b5d95a9feda5 +size 41323 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png index d0bb2ef17c..5c7a431cbd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0ea6b1bf786b06fbe4ad211f9dbda7094f30f5089f7bb186d4024f064612785 -size 45210 +oid sha256:2fde798527f42790e6fe230dc896481223db0481605f551e5f7e840a3a78566b +size 42028 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png index 045e6fbe0a..686029b8da 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cf9b0b10c112a964bbc85e6c14b634d96460884992c53e8ae9408ec5a94455a -size 43073 +oid sha256:1d133a0d749da6a68605594ce69494fb8bb8094023a80e617872a40789bd4213 +size 40891 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png index 8a5a3a51ae..48df048115 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aa79cc35f4f4e9f6221b1f6cf119906bef17cc62268fae01ff1ec713931b7b9 -size 39619 +oid sha256:fbecca870bf0bedc0db8195b222b75408e8a2846424cf9de9fcf86e7342734d9 +size 41750 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png index 8656744f17..12816f97f3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:526a16419357ee26e460511190d95c4f9adf8686d8a688704634025298b5153e -size 47451 +oid sha256:3c1002160e799e3fb8c122f030f76dc29b0a766e533a9fe2151c9244777293f8 +size 42181 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png index 34883fce29..3e61505a0e 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe2e1f53003df2f9fd33e90861221a4adec4e4104ddf1162502b70a895b798eb -size 46410 +oid sha256:139702b064abb7b4b2d862e6f05730e2d3cc631cde28bde6c2a0770c5e08dbd1 +size 41072 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png index 5fa82cc6ce..d4bea56772 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44e97087d7fefeb63beb81ef0e52ea6616820bf325217f75aa0a11806f6c4313 -size 46366 +oid sha256:a9c44b7df1330d879fb78c3cde3e4664a470fec1508183f274cb5c572d457993 +size 41033 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_84_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_84_en.png new file mode 100644 index 0000000000..d78e8bad2a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_84_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:938fb1c1ade57ac6421387d9ea5142448842a32d7ce446d4743d1aa15b2944d9 +size 15284 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_85_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_85_en.png new file mode 100644 index 0000000000..deb69c6aa1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_85_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7619c6bc9d0cb6ea7660992ed16ce24eafe10052f9a8a6e7708f7bc5c079fdc +size 14541 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_86_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_86_en.png new file mode 100644 index 0000000000..bd51d8c202 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_86_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bef7c3d043454a4faf858456c7fcc98d1a17a0846a891af3332e76c4e10b553 +size 17102 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png new file mode 100644 index 0000000000..a9e55101ce --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3331fd0021785394b1f2b274433da2e1b2c01c001af57840fb69436c84a2345 +size 31436 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png new file mode 100644 index 0000000000..11f9963897 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eb047b7c69436b6c131ffdc1bea2883240fee64d57163d9113cf6fa184e8081 +size 30042 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png new file mode 100644 index 0000000000..55e944524e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f3557280a4010e7ffdb6f22c11561573780c0b24c27e264073d3a3899169014 +size 29659 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png new file mode 100644 index 0000000000..33c24c3341 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63efb9744640b71cfde672821c7c1ea8b33f9c3c2e2ad4d5f41149b9749b31c2 +size 28016 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en.png new file mode 100644 index 0000000000..fd7b7e6443 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:467d501edd69dc1cc2ee57b2558ed6215f0464495d09a421692c2e30d4dc0fb3 +size 6473 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en.png new file mode 100644 index 0000000000..f835ed50a3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4a741963a984c6f491290fda0f399a73a92099cbef9a6c79676a1f89bbc53b1 +size 9050 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en.png new file mode 100644 index 0000000000..a0e5cbaec3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45592dde62aa3f5272bb63df450b5eb76f634caadbabc0ac416c27882edb2ecc +size 6340 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en.png new file mode 100644 index 0000000000..8430ef926c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5298e5212daba4deda2bb931f9de1af660af559dc77f092622ded925125233e +size 8898 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png new file mode 100644 index 0000000000..2d5f0a7613 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:421bd0bd141a6f6b38ce938c68cb21672b1d3c9cbb83e8cd44fe47bbc2488c3f +size 11168 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png new file mode 100644 index 0000000000..fc5c101c2e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c94c521f50f3b8d2fc5e03365e54e8582ee48d31a47146abfb34cc41972fd38b +size 15539 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png new file mode 100644 index 0000000000..e28f8c7a7c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a8d98c3b6bd629ca6318fb7ac8d989960bf2bb952a9719a305568864d3bbb77 +size 38554 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png new file mode 100644 index 0000000000..e73f79b22d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00f111de4ffa63cfb0cc047ea017a2e740f52a9d0786fadf379937c12ae0e199 +size 10715 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png new file mode 100644 index 0000000000..5663da541d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2637ec2e1303341c473cb9380c18fb7c7c1558c1dbd39dcff8d71744e4c3f5c9 +size 14901 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png new file mode 100644 index 0000000000..84aae36b0a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57b058e64c8361a70492056a09d13e87c4e1b61fbfe785198282d035d27326ac +size 37041 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en.png new file mode 100644 index 0000000000..a027c89303 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15d3fa6a95cda6bca06ad79d3f4862db05e38111cdcac47c1cdd3aa204bc1f97 +size 4210 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en.png new file mode 100644 index 0000000000..503f2bb229 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abaae9e0c6bf9d7dec701e9a51592e89408668e0a2b8325731efdfdc73978acd +size 3667 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png new file mode 100644 index 0000000000..f5caf4274b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70f93330adb987d6f98d654670ab0898957d765ad3d92a47c9a1c781b24f9059 +size 5317 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png new file mode 100644 index 0000000000..1821a79941 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ffac5911f928411fa0ac94e9ac59f6b8bb8bce1016e06f348d946f9d10053e5 +size 4539 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png new file mode 100644 index 0000000000..43d55840d9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0ee87589ec0e4f7cf67775dfa69cd289aeb27f22087bd91d54102923a28557d +size 4775 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png new file mode 100644 index 0000000000..f743d18269 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6504e09eb09a9e28bc70594e669ec87abd290b4fe20e2ee9b3588c2116a049cd +size 3994 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png new file mode 100644 index 0000000000..14e6352729 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d55f260237060e5ac280d6c87f5eefbdaa9dc6710572cab38fbd41dea77090 +size 15465 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png new file mode 100644 index 0000000000..14e6352729 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d55f260237060e5ac280d6c87f5eefbdaa9dc6710572cab38fbd41dea77090 +size 15465 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png new file mode 100644 index 0000000000..14e65ac8bd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08b47d8b631032d7cb33cdd150eab67e4dc9e6498813e0f5107007c30143d249 +size 26058 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png new file mode 100644 index 0000000000..9a840d69b9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6c04486280ce42c65486d4c532c20521f0d03419f1ab042dc61c0b5ff1060c2 +size 18242 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png new file mode 100644 index 0000000000..5544b7f86f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88c68143c2bc7b098664d881bb6c0f2c35ac10ec690c0c7eb7ae6d9366144e83 +size 15136 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png new file mode 100644 index 0000000000..5544b7f86f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88c68143c2bc7b098664d881bb6c0f2c35ac10ec690c0c7eb7ae6d9366144e83 +size 15136 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png new file mode 100644 index 0000000000..18144ddccb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b7055ce0a8214e7c445c2d1b7d30ad5ffc63617c9f9f837661dfbd057198681 +size 26092 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png new file mode 100644 index 0000000000..18144ddccb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b7055ce0a8214e7c445c2d1b7d30ad5ffc63617c9f9f837661dfbd057198681 +size 26092 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png new file mode 100644 index 0000000000..5b1aa7f25a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:789e94ac051194e1a43d2b211301537a17596fd30b7a670a7d39107dcfe6471d +size 40514 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png new file mode 100644 index 0000000000..a8e5914937 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e379008ac3a5346b727bd9355a4a59b75a1a2eca8855ad4ef5b6d7c239da129b +size 15076 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png new file mode 100644 index 0000000000..a8e5914937 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e379008ac3a5346b727bd9355a4a59b75a1a2eca8855ad4ef5b6d7c239da129b +size 15076 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png new file mode 100644 index 0000000000..47b5c76148 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a99c2ae96a72551e0da2e6e3cce08a76fffc2f99763c81d5c3fe65a9604bbdc7 +size 25564 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png new file mode 100644 index 0000000000..7a9b4af687 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b332b50ff448deea9bb3fc0db047b99743bb16c730a9ef3dbb203b7ca8982dc +size 17067 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png new file mode 100644 index 0000000000..30b2d63dd7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d414cb2e4268815943ce6d78fe8d85f576e055bd8ff4f652b7e15a664e3d8cb3 +size 14597 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png new file mode 100644 index 0000000000..30b2d63dd7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d414cb2e4268815943ce6d78fe8d85f576e055bd8ff4f652b7e15a664e3d8cb3 +size 14597 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png new file mode 100644 index 0000000000..aca9e44958 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f07b50b154c426638c38897fca296f5d52fb0054ad459eadf8fbe344c9b0526 +size 25475 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png new file mode 100644 index 0000000000..aca9e44958 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f07b50b154c426638c38897fca296f5d52fb0054ad459eadf8fbe344c9b0526 +size 25475 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png new file mode 100644 index 0000000000..5e1abf250a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44a96a7ad9425d869b06d3339b8356fbb9d213f1ce1a987b57ddc8117885daea +size 38480 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png index 93393fba05..7214efd2c9 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8672f053256e9ab2984aa06a22e4c27f2dd3f3373f3d87c12bc3269297713fd -size 389602 +oid sha256:043da4a779363f5162b1fbb6b1159ab3ae3f6a1635473146a5b73242525b5e53 +size 390373 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png index 4439239760..1ad4e9e788 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33f460febda376d35c6eceb643dca374e9dbc76d30f21ccab99085a628e90e79 -size 389629 +oid sha256:a2c00187eb25b297f7debb7424969e5535e48366d139b7f073b6a7628f155d60 +size 390395 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png index 061278ca3f..9511942ab8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b5c823df53d7a6fb2df235c2016b0e71bbf26aa2f7ec1c175e3360424b69c37 -size 94822 +oid sha256:94520145bde5de6a0d7820dd44d7f0243d9fb06eca062b19b72cb2457abdfb7d +size 95438 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png index 5d77b5705d..475bdd1991 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b5506ab9651470ae3fe08d89a1ee2783d333a0fc1a657e45d50c156d664e84c -size 396617 +oid sha256:82710704b6daff1c1e12a4a3c782f68744c0d6b3ee30b3fa1e38739176c6eaef +size 397724 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png index dcbe73cf41..685149c94d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4a44ab880d3d9660c6671505d0575f6a89e21c4e4a16d5838b6826fe770473d -size 22041 +oid sha256:1547883dfc7ae3d742d2da2f6e5c6c2d6182d6039b3ce6075dbbe1d24f4ba341 +size 23370 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png index c8856654b4..57c533a361 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c7122bd07a25e38d8056ca2f9d297e2073c418d359b4d286a5ba3441bce1e4b -size 5712 +oid sha256:66add7cb3b696075b7e931b06d1d8472cfddc2fc1902081864c3d88754f7404a +size 6702 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png index fbda655334..2142adf1d5 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f67a1db4ce45d9b5d2cf8f9f3bf0e5e6de439d1d9631a407826f984afdaf90c7 -size 14756 +oid sha256:a86ec4a40a63f646e62b1c6a3e481f0b68be442923e39b6750836ae4e5ac3045 +size 15577 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png index 4ff875eb5c..8f3f77a739 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b9d8de8cddf75a662c72d519eef8ae6409e109d944826147f71ddc97f2d5a40 -size 14954 +oid sha256:0abf1fdae34ea898f817f9667a02adb551d5e3bb528f73b4c2c01826f4ca375a +size 15881 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png index 1d9b20bcf3..54fda061bc 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e202c4748e6c0afcfdcfb77c3e3f14a234d0622e579ea926311e669486ba0e8 -size 13576 +oid sha256:c786dc4f2ec41fc334cd67febc105670c999da57cec1ea263daff2358ff5c766 +size 14406 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png index 9698527a7d..8e2a70f3ce 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:646a010fc57669e8bd7a97cab1f5bfdd94c78a05cd02c0150a6b41ef82e8f466 -size 13687 +oid sha256:3f74e1b75f3e5a03df1b6b0e4509a4bf5da033c4f68cf88705ed136b471ae38c +size 14691 From 73143d88c08f0cdd8eccba1834e73fde195fd52c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2024 17:52:59 +0100 Subject: [PATCH 040/203] Fix preview --- .../impl/details/MediaDeleteConfirmationBottomSheet.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt index 9be9d84b6f..58b4bfaceb 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -107,15 +106,9 @@ private fun MediaRow( .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { - val bgColor = if (LocalInspectionMode.current) { - ElementTheme.colors.bgDecorative1 - } else { - Color.Transparent - } Box( modifier = Modifier - .size(40.dp) - .background(bgColor), + .size(40.dp), ) { if (state.thumbnailSource == null) { BigIcon( From c2ad4a929b272de531383177bb903f506e68243c Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 9 Dec 2024 17:04:41 +0000 Subject: [PATCH 041/203] Update screenshots --- ...pl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png | 4 ++-- ....details_MediaDeleteConfirmationBottomSheet_Night_0_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png index a9e55101ce..137a7d8afe 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3331fd0021785394b1f2b274433da2e1b2c01c001af57840fb69436c84a2345 -size 31436 +oid sha256:8070f1089c8d151b74558046ca70d3c92525d80109dcc082ac05be5678b7b6e0 +size 31230 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png index 11f9963897..7a2ce52d92 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8eb047b7c69436b6c131ffdc1bea2883240fee64d57163d9113cf6fa184e8081 -size 30042 +oid sha256:2dc3ac99e6894376a01f3f9cdbe8efcfd43233ada646944634489620535d326e +size 29603 From 6ad89fd5126f55b172120ed47cfad7f716192887 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 19:58:54 +0100 Subject: [PATCH 042/203] fix(dm) : remove duplicate LaunchedEffect --- .../impl/members/details/RoomMemberDetailsNode.kt | 8 -------- .../features/userprofile/impl/root/UserProfileNode.kt | 8 -------- 2 files changed, 16 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 06748b0999..60aa00150b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -8,7 +8,6 @@ package io.element.android.features.roomdetails.impl.members.details import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.lifecycle.subscribe @@ -21,7 +20,6 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.features.userprofile.shared.UserProfileNodeHelper import io.element.android.features.userprofile.shared.UserProfileView -import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope @@ -73,12 +71,6 @@ class RoomMemberDetailsNode @AssistedInject constructor( val state = presenter.present() - LaunchedEffect(state.startDmActionState) { - val result = state.startDmActionState - if (result is AsyncAction.Success) { - onStartDM(result.data) - } - } UserProfileView( state = state, modifier = modifier, diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt index e3ce329b09..da86349adb 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt @@ -8,7 +8,6 @@ package io.element.android.features.userprofile.impl.root import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.lifecycle.subscribe @@ -21,7 +20,6 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.features.userprofile.shared.UserProfileNodeHelper import io.element.android.features.userprofile.shared.UserProfileView -import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope @@ -69,12 +67,6 @@ class UserProfileNode @AssistedInject constructor( val state = presenter.present() - LaunchedEffect(state.startDmActionState) { - val result = state.startDmActionState - if (result is AsyncAction.Success) { - onStartDM(result.data) - } - } UserProfileView( state = state, modifier = modifier, From 4e3cab09d301ad7f4874af914084742576abe8ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:11:49 +0000 Subject: [PATCH 043/203] Update dagger to v2.53.1 --- 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 748a62bd88..7a3033f370 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ telephoto = "0.14.0" dependencyAnalysis = "2.5.0" # DI -dagger = "2.53" +dagger = "2.53.1" anvil = "0.4.0" # Auto service From ae0593d555d51fc8932144df491ab090cdcebeb2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 08:07:12 +0100 Subject: [PATCH 044/203] Rename preview. --- .../libraries/mediaviewer/impl/gallery/ui/DateItemView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt index 7cf387a8cc..f0c44a382c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt @@ -38,7 +38,7 @@ fun DateItemView( @PreviewsDayNight @Composable -internal fun PreviewDateItemView( +internal fun DateItemViewPreview( @PreviewParameter(MediaItemDateSeparatorProvider::class) date: MediaItem.DateSeparator, ) = ElementPreview { DateItemView(date) From 220704077c493b320cbd75774ec379e1fb712152 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 08:46:07 +0100 Subject: [PATCH 045/203] remove blank line. --- .../element/android/libraries/core/extensions/BasicExtensions.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index f4dd61bf6e..12aa5c4bfe 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -68,4 +68,3 @@ fun String.replacePrefix(oldPrefix: String, newPrefix: String): String { fun String.withBrackets(prefix: String = "(", suffix: String = ")"): String { return "$prefix$this$suffix" } - From 197fda5d0edfa1b0ecc2947af3d9947a58e83d27 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 08:51:43 +0100 Subject: [PATCH 046/203] Rename onDone to onBackClick. --- .../features/roomdetails/impl/RoomDetailsFlowNode.kt | 2 +- .../libraries/mediaviewer/api/MediaGalleryEntryPoint.kt | 2 +- .../mediaviewer/impl/gallery/MediaGalleryNode.kt | 8 ++++---- .../mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index cf1443fc8b..f89953263c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -243,7 +243,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( } is NavTarget.MediaGallery -> { val callback = object : MediaGalleryEntryPoint.Callback { - override fun onDone() { + override fun onBackClick() { backstack.pop() } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt index e8b438b642..a26bb18915 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt @@ -22,7 +22,7 @@ interface MediaGalleryEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onDone() + fun onBackClick() fun onViewInTimeline(eventId: EventId) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt index 4ec91570ef..ccea1a130e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt @@ -31,14 +31,14 @@ class MediaGalleryNode @AssistedInject constructor( ) interface Callback : Plugin { - fun onDone() + fun onBackClick() fun onItemClick(item: MediaItem.Event) fun onViewInTimeline(eventId: EventId) } - private fun onDone() { + private fun onBackClick() { plugins().forEach { - it.onDone() + it.onBackClick() } } @@ -59,7 +59,7 @@ class MediaGalleryNode @AssistedInject constructor( val state = presenter.present() MediaGalleryView( state = state, - onBackClick = ::onDone, + onBackClick = ::onBackClick, onItemClick = ::onItemClick, modifier = modifier, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt index 1476cef64a..4f5272b01b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt @@ -67,9 +67,9 @@ class MediaGalleryRootNode @AssistedInject constructor( ) : NavTarget } - private fun onDone() { + private fun onBackClick() { plugins().forEach { - it.onDone() + it.onBackClick() } } @@ -83,8 +83,8 @@ class MediaGalleryRootNode @AssistedInject constructor( return when (navTarget) { NavTarget.Root -> { val callback = object : MediaGalleryNode.Callback { - override fun onDone() { - this@MediaGalleryRootNode.onDone() + override fun onBackClick() { + this@MediaGalleryRootNode.onBackClick() } override fun onViewInTimeline(eventId: EventId) { From 323ae0ad2ee1e7f3ee64bb58c743aab8db8f0340 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 08:57:36 +0100 Subject: [PATCH 047/203] Rename imageItems to imageAndVideoItems. --- .../impl/gallery/MediaGalleryPresenter.kt | 4 ++-- .../impl/gallery/MediaGalleryState.kt | 2 +- .../impl/gallery/MediaGalleryStateProvider.kt | 10 +++++----- .../impl/gallery/MediaGalleryView.kt | 20 +++++++++---------- .../impl/gallery/MediaGalleryPresenterTest.kt | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 5bd170f672..dc973be624 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -77,7 +77,7 @@ class MediaGalleryPresenter @AssistedInject constructor( var mediaItems by remember { mutableStateOf>>(AsyncData.Uninitialized) } - val imageItems by remember { + val imageAndVideoItems by remember { derivedStateOf { mediaItemsPostProcessor.process( mediaItems = mediaItems, @@ -157,7 +157,7 @@ class MediaGalleryPresenter @AssistedInject constructor( return MediaGalleryState( roomName = roomInfo?.name ?: room.displayName, mode = mode, - imageItems = imageItems, + imageAndVideoItems = imageAndVideoItems, fileItems = fileItems, mediaBottomSheetState = mediaBottomSheetState, snackbarMessage = snackbarMessage, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt index 36e0710a88..bcec4a37ca 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt @@ -16,7 +16,7 @@ import kotlinx.collections.immutable.ImmutableList data class MediaGalleryState( val roomName: String, val mode: MediaGalleryMode, - val imageItems: AsyncData>, + val imageAndVideoItems: AsyncData>, val fileItems: AsyncData>, val mediaBottomSheetState: MediaBottomSheetState, val snackbarMessage: SnackbarMessage?, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index d357ae7a0c..0a48618643 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -23,10 +23,10 @@ open class MediaGalleryStateProvider : PreviewParameterProvider get() = sequenceOf( aMediaGalleryState(), - aMediaGalleryState(imageItems = AsyncData.Loading()), - aMediaGalleryState(imageItems = AsyncData.Success(emptyList().toPersistentList())), + aMediaGalleryState(imageAndVideoItems = AsyncData.Loading()), + aMediaGalleryState(imageAndVideoItems = AsyncData.Success(emptyList().toPersistentList())), aMediaGalleryState( - imageItems = AsyncData.Success( + imageAndVideoItems = AsyncData.Success( listOf( aDate(id = UniqueId("0")), anImage(id = UniqueId("1")), @@ -71,12 +71,12 @@ open class MediaGalleryStateProvider : PreviewParameterProvider> = AsyncData.Uninitialized, + imageAndVideoItems: AsyncData> = AsyncData.Uninitialized, fileItems: AsyncData> = AsyncData.Uninitialized, ) = MediaGalleryState( roomName = roomName, mode = mode, - imageItems = imageItems, + imageAndVideoItems = imageAndVideoItems, fileItems = fileItems, mediaBottomSheetState = MediaBottomSheetState.Hidden, snackbarMessage = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index ac97755301..3d4082015b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -129,7 +129,7 @@ fun MediaGalleryView( val mode = MediaGalleryMode.entries[page] when (mode) { MediaGalleryMode.Images -> MediaGalleryImages( - images = state.imageItems, + imagesAndVideos = state.imageAndVideoItems, eventSink = state.eventSink, onItemClick = onItemClick, ) @@ -180,21 +180,21 @@ fun MediaGalleryView( @Composable private fun MediaGalleryImages( - images: AsyncData>, + imagesAndVideos: AsyncData>, eventSink: (MediaGalleryEvents) -> Unit, onItemClick: (MediaItem.Event) -> Unit, ) { - when (images) { + when (imagesAndVideos) { AsyncData.Uninitialized, is AsyncData.Loading -> { LoadingContent(MediaGalleryMode.Images) } is AsyncData.Success -> { - if (images.data.isEmpty()) { + if (imagesAndVideos.data.isEmpty()) { EmptyContent() } else { MediaGalleryImageGrid( - images = images.data, + imagesAndVideos = imagesAndVideos.data, eventSink = eventSink, onItemClick = onItemClick, ) @@ -202,7 +202,7 @@ private fun MediaGalleryImages( } is AsyncData.Failure -> { ErrorContent( - error = images.error, + error = imagesAndVideos.error, ) } } @@ -275,7 +275,7 @@ private fun MediaGalleryFilesList( @Composable private fun MediaGalleryImageGrid( - images: ImmutableList, + imagesAndVideos: ImmutableList, eventSink: (MediaGalleryEvents) -> Unit, onItemClick: (MediaItem.Event) -> Unit, ) { @@ -296,7 +296,7 @@ private fun MediaGalleryImageGrid( verticalArrangement = Arrangement.spacedBy(4.dp), ) { items( - images, + imagesAndVideos, span = { item -> when (item) { is MediaItem.LoadingIndicator, @@ -318,7 +318,7 @@ private fun MediaGalleryImageGrid( } is MediaItem.Image -> { ImageItemView( - item, + image = item, onClick = { onItemClick(item) }, onLongClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) @@ -327,7 +327,7 @@ private fun MediaGalleryImageGrid( } is MediaItem.Video -> { VideoItemView( - item, + video = item, onClick = { onItemClick(item) }, onLongClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index 2d6a07e477..c1a71a1152 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -61,7 +61,7 @@ class MediaGalleryPresenterTest { assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) assertThat(initialState.roomName).isEqualTo(A_ROOM_NAME) - assertThat(initialState.imageItems.dataOrNull()).isEmpty() + assertThat(initialState.imageAndVideoItems.dataOrNull()).isEmpty() assertThat(initialState.fileItems.dataOrNull()).isEmpty() assertThat(initialState.snackbarMessage).isNull() } From e55e97795053c76565fcb5ee1784189ea3885dff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:01:42 +0100 Subject: [PATCH 048/203] Remove TimelineMediaItemsCacheInvalidator, it is not needed. --- .../impl/gallery/TimelineMediaItemsFactory.kt | 4 +- .../TimelineMediaItemsCacheInvalidator.kt | 53 ------------------- 2 files changed, 2 insertions(+), 55 deletions(-) delete mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt index 1993381417..d8531cf230 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt @@ -8,12 +8,12 @@ package io.element.android.libraries.mediaviewer.impl.gallery import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.diff.DefaultDiffCacheInvalidator import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem -import io.element.android.libraries.mediaviewer.impl.gallery.diff.TimelineMediaItemsCacheInvalidator import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList @@ -46,7 +46,7 @@ class DefaultTimelineMediaItemsFactory @Inject constructor( private val diffCacheUpdater = DiffCacheUpdater( diffCache = diffCache, detectMoves = false, - cacheInvalidator = TimelineMediaItemsCacheInvalidator() + cacheInvalidator = DefaultDiffCacheInvalidator() ) { old, new -> if (old is MatrixTimelineItem.Event && new is MatrixTimelineItem.Event) { old.uniqueId == new.uniqueId diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt deleted file mode 100644 index b7e6d51913..0000000000 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/diff/TimelineMediaItemsCacheInvalidator.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery.diff - -import io.element.android.libraries.androidutils.diff.DefaultDiffCacheInvalidator -import io.element.android.libraries.androidutils.diff.DiffCacheInvalidator -import io.element.android.libraries.androidutils.diff.MutableDiffCache -import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem - -/** - * [DiffCacheInvalidator] implementation for [MediaItem]. - * It uses [DefaultDiffCacheInvalidator] and invalidate the cache around the updated item so that those items are computed again. - * This is needed because a timeline item is computed based on the previous and next items. - */ -internal class TimelineMediaItemsCacheInvalidator : DiffCacheInvalidator { - private val delegate = DefaultDiffCacheInvalidator() - - override fun onChanged(position: Int, count: Int, cache: MutableDiffCache) { - delegate.onChanged(position, count, cache) - } - - override fun onMoved(fromPosition: Int, toPosition: Int, cache: MutableDiffCache) { - delegate.onMoved(fromPosition, toPosition, cache) - } - - override fun onInserted(position: Int, count: Int, cache: MutableDiffCache) { - cache.invalidateAround(position) - delegate.onInserted(position, count, cache) - } - - override fun onRemoved(position: Int, count: Int, cache: MutableDiffCache) { - cache.invalidateAround(position) - delegate.onRemoved(position, count, cache) - } -} - -/** - * Invalidate the cache around the given position. - * It invalidates the previous and next items. - */ -private fun MutableDiffCache<*>.invalidateAround(position: Int) { - if (position > 0) { - set(position - 1, null) - } - if (position < indices().last) { - set(position + 1, null) - } -} From a40aff661883c08f547cdb5b88e795848310c8e7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:04:14 +0100 Subject: [PATCH 049/203] Change scope to RoomScope --- .../libraries/mediaviewer/impl/gallery/EventItemFactory.kt | 4 ++-- .../mediaviewer/impl/gallery/MediaItemsPostProcessor.kt | 4 ++-- .../mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt | 4 ++-- .../libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index d4065f76c2..711ac8c66c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -10,7 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.dateformatter.api.toHumanReadableDuration -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent @@ -49,7 +49,7 @@ interface EventItemFactory { fun create(currentTimelineItem: MatrixTimelineItem.Event): MediaItem.Event? } -@ContributesBinding(AppScope::class) +@ContributesBinding(RoomScope::class) class DefaultEventItemFactory @Inject constructor( private val fileSizeFormatter: FileSizeFormatter, private val fileExtensionExtractor: FileExtensionExtractor, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index d406a34567..88473a8fe2 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -9,7 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.RoomScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -21,7 +21,7 @@ interface MediaItemsPostProcessor { ): AsyncData> } -@ContributesBinding(AppScope::class) +@ContributesBinding(RoomScope::class) class DefaultMediaItemsPostProcessor @Inject constructor( ) : MediaItemsPostProcessor { override fun process( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt index d8531cf230..aca0e6e477 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt @@ -12,7 +12,7 @@ import io.element.android.libraries.androidutils.diff.DefaultDiffCacheInvalidato import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.collections.immutable.ImmutableList @@ -33,7 +33,7 @@ interface TimelineMediaItemsFactory { suspend fun onCanPaginate() } -@ContributesBinding(AppScope::class) +@ContributesBinding(RoomScope::class) class DefaultTimelineMediaItemsFactory @Inject constructor( private val dispatchers: CoroutineDispatchers, private val virtualItemFactory: VirtualItemFactory, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt index f8a952cbd4..9f541b6e16 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt @@ -9,7 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import javax.inject.Inject @@ -18,7 +18,7 @@ interface VirtualItemFactory { fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? } -@ContributesBinding(AppScope::class) +@ContributesBinding(RoomScope::class) class DefaultVirtualItemFactory @Inject constructor( private val daySeparatorFormatter: DaySeparatorFormatter, ) : VirtualItemFactory { From e20800db759e43c08acccf64a9e1510d225169d8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 08:19:08 +0000 Subject: [PATCH 050/203] Update dependency io.sentry:sentry-android to v7.18.1 (#3972) 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 748a62bd88..04a393c042 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -195,7 +195,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics posthog = "com.posthog:posthog-android:3.9.2" -sentry = "io.sentry:sentry-android:7.18.0" +sentry = "io.sentry:sentry-android:7.18.1" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" From 85df35e36316806e40b24b852a0fbb858e1049be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 08:20:26 +0000 Subject: [PATCH 051/203] Update dependency com.posthog:posthog-android to v3.9.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 04a393c042..2a443fdd0c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -194,7 +194,7 @@ opusencoder = "io.element.android:opusencoder:1.1.0" zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics -posthog = "com.posthog:posthog-android:3.9.2" +posthog = "com.posthog:posthog-android:3.9.3" sentry = "io.sentry:sentry-android:7.18.1" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" From a89a9c35d8a7716399e310719f4dc331c3a6d70e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:24:31 +0100 Subject: [PATCH 052/203] Remove not needed MediaGalleryTimelineProvider --- libraries/mediaviewer/impl/build.gradle.kts | 2 - .../impl/gallery/MediaGalleryPresenter.kt | 59 +++++---- .../gallery/MediaGalleryTimelineProvider.kt | 113 ------------------ .../impl/gallery/MediaGalleryPresenterTest.kt | 7 -- 4 files changed, 35 insertions(+), 146 deletions(-) delete mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index deeffe9e8b..d77df8ab36 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -33,7 +33,6 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.flick) - implementation(projects.features.networkmonitor.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.core) @@ -52,7 +51,6 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) - testImplementation(projects.features.networkmonitor.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaviewer.test) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index dc973be624..7a70a77e0b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.content.ActivityNotFoundException import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf @@ -50,7 +51,6 @@ import kotlinx.coroutines.launch class MediaGalleryPresenter @AssistedInject constructor( @Assisted private val navigator: MediaGalleryNavigator, private val room: MatrixRoom, - private val timelineProvider: MediaGalleryTimelineProvider, private val timelineMediaItemsFactory: TimelineMediaItemsFactory, private val localMediaFactory: LocalMediaFactory, private val mediaLoader: MatrixMediaLoader, @@ -97,27 +97,36 @@ class MediaGalleryPresenter @AssistedInject constructor( val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() localMediaActions.Configure() + var timeline by remember { mutableStateOf>(AsyncData.Uninitialized) } + LaunchedEffect(Unit) { + room.mediaTimeline() + .fold( + { timeline = AsyncData.Success(it) }, + { timeline = AsyncData.Failure(it) }, + ) + } + DisposableEffect(Unit) { + onDispose { + timeline.dataOrNull()?.close() + } + } + MediaListEffect( + timeline = timeline, onItemsChange = { newItems -> mediaItems = newItems } ) - LaunchedEffect(Unit) { - timelineProvider.launchIn(this) - } - fun handleEvents(event: MediaGalleryEvents) { when (event) { is MediaGalleryEvents.ChangeMode -> { mode = event.mode } is MediaGalleryEvents.LoadMore -> coroutineScope.launch { - timelineProvider.invokeOnTimeline { - paginate(event.direction) - } + timeline.dataOrNull()?.paginate(event.direction) } - is MediaGalleryEvents.Delete -> coroutineScope.delete(event.eventId) + is MediaGalleryEvents.Delete -> coroutineScope.delete(timeline, event.eventId) is MediaGalleryEvents.SaveOnDisk -> coroutineScope.saveOnDisk(event.mediaItem) is MediaGalleryEvents.Share -> coroutineScope.share(event.mediaItem) is MediaGalleryEvents.ViewInTimeline -> { @@ -166,18 +175,19 @@ class MediaGalleryPresenter @AssistedInject constructor( } @Composable - private fun MediaListEffect(onItemsChange: (AsyncData>) -> Unit) { + private fun MediaListEffect( + timeline: AsyncData, + onItemsChange: (AsyncData>) -> Unit, + ) { val updatedOnItemsChange by rememberUpdatedState(onItemsChange) - val timelineState by timelineProvider.timelineStateFlow.collectAsState() - - LaunchedEffect(timelineState) { - when (val asyncTimeline = timelineState) { + LaunchedEffect(timeline) { + when (timeline) { AsyncData.Uninitialized -> flowOf(AsyncData.Uninitialized) - is AsyncData.Failure -> flowOf(AsyncData.Failure(asyncTimeline.error)) + is AsyncData.Failure -> flowOf(AsyncData.Failure(timeline.error)) is AsyncData.Loading -> flowOf(AsyncData.Loading()) is AsyncData.Success -> { - asyncTimeline.data.timelineItems + timeline.data.timelineItems .onEach { items -> timelineMediaItemsFactory.replaceWith( timelineItems = items, @@ -185,7 +195,7 @@ class MediaGalleryPresenter @AssistedInject constructor( } .launchIn(this) - asyncTimeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS) + timeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS) .onEach { backwardPaginationStatus -> if (backwardPaginationStatus.canPaginate) { timelineMediaItemsFactory.onCanPaginate() @@ -205,13 +215,14 @@ class MediaGalleryPresenter @AssistedInject constructor( } } - private fun CoroutineScope.delete(eventId: EventId) = launch { - timelineProvider.invokeOnTimeline { - redactEvent( - eventOrTransactionId = eventId.toEventOrTransactionId(), - reason = null, - ) - } + private fun CoroutineScope.delete( + timeline: AsyncData, + eventId: EventId, + ) = launch { + timeline.dataOrNull()?.redactEvent( + eventOrTransactionId = eventId.toEventOrTransactionId(), + reason = null, + ) } private suspend fun downloadMedia(mediaItem: MediaItem.Event): Result { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt deleted file mode 100644 index 9174cab8ca..0000000000 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery - -import io.element.android.features.networkmonitor.api.NetworkMonitor -import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.core.coroutine.mapState -import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.di.SingleIn -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.TimelineProvider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import javax.inject.Inject - -@SingleIn(RoomScope::class) -class MediaGalleryTimelineProvider @Inject constructor( - private val room: MatrixRoom, - private val networkMonitor: NetworkMonitor, - private val featureFlagService: FeatureFlagService, -) : TimelineProvider { - private val _timelineStateFlow: MutableStateFlow> = - MutableStateFlow(AsyncData.Uninitialized) - - override fun activeTimelineFlow(): StateFlow { - return _timelineStateFlow - .mapState { value -> - value.dataOrNull() - } - } - - val timelineStateFlow = _timelineStateFlow - - fun launchIn(scope: CoroutineScope) { - _timelineStateFlow.subscriptionCount - .map { count -> count > 0 } - .distinctUntilChanged() - .onEach { isActive -> - if (isActive) { - onActive() - } else { - onInactive() - } - } - .launchIn(scope) - } - - private suspend fun onActive() = coroutineScope { - combine( - featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaGallery), - networkMonitor.connectivity - ) { isEnabled, _ -> - // do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed - isEnabled - } - .onEach { isFeatureEnabled -> - if (isFeatureEnabled) { - loadTimelineIfNeeded() - } else { - resetTimeline() - } - } - .launchIn(this) - } - - private suspend fun onInactive() { - resetTimeline() - } - - private suspend fun resetTimeline() { - invokeOnTimeline { - close() - } - _timelineStateFlow.emit(AsyncData.Uninitialized) - } - - suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) { - when (val asyncTimeline = timelineStateFlow.value) { - is AsyncData.Success -> action(asyncTimeline.data) - else -> Unit - } - } - - private suspend fun loadTimelineIfNeeded() { - when (timelineStateFlow.value) { - is AsyncData.Uninitialized, - is AsyncData.Failure -> { - timelineStateFlow.emit(AsyncData.Loading()) - room.mediaTimeline() - .fold( - { timelineStateFlow.emit(AsyncData.Success(it)) }, - { timelineStateFlow.emit(AsyncData.Failure(it)) } - ) - } - else -> Unit - } - } -} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index c1a71a1152..3acc293f35 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -9,9 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.net.Uri import com.google.common.truth.Truth.assertThat -import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem @@ -242,11 +240,6 @@ class MediaGalleryPresenterTest { return MediaGalleryPresenter( navigator = navigator, room = room, - timelineProvider = MediaGalleryTimelineProvider( - room = room, - networkMonitor = FakeNetworkMonitor(), - featureFlagService = FakeFeatureFlagService(), - ), timelineMediaItemsFactory = FakeTimelineMediaItemsFactory( replaceWithLambda = lambdaRecorder, Unit> { _ -> }, onCanPaginateLambda = lambdaRecorder { }, From 6386645e0bbf5ded205e8222fd18c848741763bd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:24:45 +0100 Subject: [PATCH 053/203] Remove extra space. --- .../android/libraries/matrix/test/room/FakeMatrixRoom.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index b357c51fee..41d913b89c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -204,7 +204,7 @@ class FakeMatrixRoom( pinnedEventsTimelineResult() } - override suspend fun mediaTimeline(): Result = simulateLongTask { + override suspend fun mediaTimeline(): Result = simulateLongTask { mediaTimelineResult() } From cce1c9974a0e3e380547d5556a126ee2e8628a29 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:28:46 +0100 Subject: [PATCH 054/203] Format file. --- .../impl/viewer/MediaViewerPresenterTest.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 aaae1d8b79..2b9e56820d 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 @@ -308,9 +308,12 @@ class MediaViewerPresenterTest { val successState = awaitItem() assertThat(successState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) successState.eventSink(MediaViewerEvents.Delete(AN_EVENT_ID)) - redactEventLambda.assertions().isCalledOnce().with( - value(AN_EVENT_ID.toEventOrTransactionId()), value(null) - ) + redactEventLambda.assertions() + .isCalledOnce() + .with( + value(AN_EVENT_ID.toEventOrTransactionId()), + value(null), + ) onItemDeletedLambda.assertions().isCalledOnce() } } @@ -372,4 +375,3 @@ class MediaViewerPresenterTest { ) } } - From b78039a17fe6bcb025aa42b8cb4336a183b7365e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:47:53 +0100 Subject: [PATCH 055/203] Add test. --- .../roomdetails/impl/RoomDetailsViewTest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index abbca71b53..16b29bfb43 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -79,6 +79,17 @@ class RoomDetailsViewTest { } } + @Config(qualifiers = "h1024dp") + @Test + fun `click on media gallery invokes expected callback`() { + ensureCalledOnce { callback -> + rule.setRoomDetailView( + openMediaGallery = callback, + ) + rule.clickOn(R.string.screen_room_details_media_gallery_title) + } + } + @Config(qualifiers = "h1024dp") @Test fun `click on notification invokes expected callback`() { @@ -282,6 +293,7 @@ private fun AndroidComposeTestRule.setRoomD invitePeople: () -> Unit = EnsureNeverCalled(), openAvatarPreview: (name: String, url: String) -> Unit = EnsureNeverCalledWithTwoParams(), openPollHistory: () -> Unit = EnsureNeverCalled(), + openMediaGallery: () -> Unit = EnsureNeverCalled(), openAdminSettings: () -> Unit = EnsureNeverCalled(), onJoinCallClick: () -> Unit = EnsureNeverCalled(), onPinnedMessagesClick: () -> Unit = EnsureNeverCalled(), @@ -298,6 +310,7 @@ private fun AndroidComposeTestRule.setRoomD invitePeople = invitePeople, openAvatarPreview = openAvatarPreview, openPollHistory = openPollHistory, + openMediaGallery = openMediaGallery, openAdminSettings = openAdminSettings, onJoinCallClick = onJoinCallClick, onPinnedMessagesClick = onPinnedMessagesClick, From 6dbfa94a71c8445c1370b24d1f47dda61a167602 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 09:56:08 +0100 Subject: [PATCH 056/203] Fix test. --- .../mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index bd9fe7d884..f60c43572e 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MediaFile +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.media.FakeMediaFile import io.element.android.libraries.mediaviewer.api.MediaInfo @@ -27,6 +28,7 @@ class AndroidLocalMediaFactoryTest { fun `test AndroidLocalMediaFactory`() { val sut = createAndroidLocalMediaFactory() val result = sut.createFromMediaFile(aMediaFile(), anImageMediaInfo( + senderId = A_USER_ID, senderName = A_USER_NAME, dateSent = "12:34", )) @@ -38,7 +40,7 @@ class AndroidLocalMediaFactoryTest { mimeType = MimeTypes.Jpeg, formattedFileSize = "4MB", fileExtension = "jpg", - senderId = null, + senderId = A_USER_ID, senderName = A_USER_NAME, senderAvatar = null, dateSent = "12:34" From ff86d0679f14199836558475577f4dcfbaf10f39 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 10:17:57 +0100 Subject: [PATCH 057/203] Fix test. --- .../android/features/roomdetails/impl/RoomDetailsViewTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index 16b29bfb43..11858929e3 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -252,7 +252,7 @@ class RoomDetailsViewTest { eventsRecorder.assertSingle(RoomDetailsEvent.SetFavorite(true)) } - @Config(qualifiers = "h1024dp") + @Config(qualifiers = "h1500dp") @Test fun `click on leave emit expected Event`() { val eventsRecorder = EventsRecorder() From 0f43e7c1b86c54757feda6b90ff2993455471942 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 10:42:23 +0100 Subject: [PATCH 058/203] More formatting --- .../mediaviewer/impl/gallery/MediaGalleryPresenter.kt | 1 - .../libraries/mediaviewer/impl/gallery/MediaGalleryView.kt | 4 +--- .../mediaviewer/impl/gallery/MediaItemsPostProcessor.kt | 3 +-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 7a70a77e0b..f5737a27b3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -140,7 +140,6 @@ class MediaGalleryPresenter @AssistedInject constructor( null -> false room.sessionId -> room.canRedactOwn().getOrElse { false } && event.mediaItem.eventId() != null else -> room.canRedactOther().getOrElse { false } && event.mediaItem.eventId() != null - }, mediaInfo = event.mediaItem.mediaInfo(), thumbnailSource = when (event.mediaItem) { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 3d4082015b..88b0c5a769 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -381,8 +381,7 @@ private fun ErrorContent(error: Throwable) { } @Composable -private fun EmptyContent( -) { +private fun EmptyContent() { Box( modifier = Modifier.fillMaxSize(), ) { @@ -433,4 +432,3 @@ internal fun MediaGalleryViewPreview( onItemClick = {}, ) } - diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index 88473a8fe2..85e972eff2 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -22,8 +22,7 @@ interface MediaItemsPostProcessor { } @ContributesBinding(RoomScope::class) -class DefaultMediaItemsPostProcessor @Inject constructor( -) : MediaItemsPostProcessor { +class DefaultMediaItemsPostProcessor @Inject constructor() : MediaItemsPostProcessor { override fun process( mediaItems: AsyncData>, predicate: (MediaItem.Event) -> Boolean, From d00841471e78b11bdd1462cbcb3d5d2418ee44df Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 11:34:57 +0100 Subject: [PATCH 059/203] Improve avatar rendering --- .../impl/banner/KnockRequestsBannerView.kt | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 402b8ba182..751e962bf7 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -10,7 +10,6 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -22,18 +21,20 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.knockrequests.impl.KnockRequest @@ -183,23 +184,38 @@ private fun KnockRequestAvatarListView( modifier: Modifier = Modifier, ) { val avatarSize = AvatarSize.KnockRequestBanner.dp - Row( + Box( modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(-avatarSize / 2), ) { knockRequests .take(MAX_AVATAR_COUNT) - .forEachIndexed { index, knockRequest -> - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .size(size = avatarSize) - .clip(CircleShape) - .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) - .zIndex(-index.toFloat()), - ) { + .reversed() + .let { smallReversedList -> + val lastItemIndex = smallReversedList.size - 1 + smallReversedList.forEachIndexed { index, knockRequest -> Avatar( - modifier = Modifier.padding(2.dp), + modifier = Modifier + .padding(start = avatarSize / 2 * (lastItemIndex - index)) + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithContent { + // Draw content and clear the pixels for the avatar on the left. + drawContent() + if (index < lastItemIndex) { + drawCircle( + color = Color.Black, + center = Offset( + x = 0f, + y = size.height / 2, + ), + radius = avatarSize.toPx() / 2, + blendMode = BlendMode.Clear, + ) + } + } + .size(size = avatarSize) + .padding(2.dp), avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), ) } From 50e8ceaaea36e5c1109d21c540068ef3e5120253 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 11:36:55 +0100 Subject: [PATCH 060/203] Fix alignment of avatar according to Figma https://www.figma.com/design/7TqjqdMBaPpm3IavKsMYu6/Ask-to-join-(Knocking)?node-id=4856-42262 --- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 751e962bf7..f11dc7da3d 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset @@ -101,7 +102,10 @@ private fun KnockRequestsBannerContent( .padding(all = 16.dp) ) { Row { - KnockRequestAvatarView(state.knockRequests) + KnockRequestAvatarView( + state.knockRequests, + modifier = Modifier.align(Alignment.CenterVertically), + ) Spacer(modifier = Modifier.width(10.dp)) Column(modifier = Modifier.weight(1f)) { Text( From 45924cc02c370e0d9b6dd7fa8c0b21b6aae32d98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 11:54:50 +0100 Subject: [PATCH 061/203] Fix alignment for edge cases. --- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index f11dc7da3d..9cf7144f5e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset @@ -104,7 +103,7 @@ private fun KnockRequestsBannerContent( Row { KnockRequestAvatarView( state.knockRequests, - modifier = Modifier.align(Alignment.CenterVertically), + modifier = Modifier.padding(top = 2.dp), ) Spacer(modifier = Modifier.width(10.dp)) Column(modifier = Modifier.weight(1f)) { From 4064cad05582e12658651741bd01711079407bb3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 11:58:31 +0100 Subject: [PATCH 062/203] Add edge case for the preview. --- .../impl/banner/KnockRequestsBannerStateProvider.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt index 87295d94ee..c0f4cc0961 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -49,6 +49,13 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider Date: Tue, 10 Dec 2024 11:09:06 +0000 Subject: [PATCH 063/203] Update screenshots --- ...krequests.impl.banner_KnockRequestsBannerView_Day_0_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_1_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_2_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_3_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_4_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_5_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_6_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_7_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_0_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_1_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_2_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_3_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_4_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_5_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_6_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_7_en.png | 3 +++ 16 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png index 3b63b61f2d..8d01d01fbd 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 -size 29335 +oid sha256:77db58461ef35ea3d1ab1dda6aff454e376b15d7037e83fbbd965037afa45572 +size 29317 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png index 1708abde01..4441fd8ef3 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0f508d82473c7b1dc9da20d25808843c3e1bfcac632ac2bf33151cc7626bf35 -size 34821 +oid sha256:585ea7b1f230ac6a12f8098200b04f433cbb30b39b8be7953dc6e278ffe8179e +size 34799 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png index d5a84e03df..75cafbe376 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5262c97044361ab44066a8876b8417853c8ef5c1449390242c0248c06a0fc568 -size 17855 +oid sha256:05770e4e11bbc7019ebc113d31eb8b76bbbddc8b1ca6acbb01c0764089047376 +size 17835 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png index c148a2032f..9dda5c0f62 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f247767c5f965cf75f1c7556f49bd5e3c7207b2effd9cd3d19a913581556842 -size 18744 +oid sha256:e21673def1a4f03dfbc6c451299ffa993acf82b63d8de13400519a0e54625fa9 +size 18736 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png index b606b20db0..55bf403a0d 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78f259c410ad179c3f93b18fc7c10829a355e5a0f56b0dd4c1f4b4efc72910f0 -size 27309 +oid sha256:a10ccd7db4660bdff998622f5390a33adcaa39472c7d60ebae7c0a5b30a810d4 +size 27294 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png index 3b63b61f2d..8d01d01fbd 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 -size 29335 +oid sha256:77db58461ef35ea3d1ab1dda6aff454e376b15d7037e83fbbd965037afa45572 +size 29317 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png index 3b63b61f2d..8d01d01fbd 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 -size 29335 +oid sha256:77db58461ef35ea3d1ab1dda6aff454e376b15d7037e83fbbd965037afa45572 +size 29317 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png new file mode 100644 index 0000000000..ab52d1d019 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43e80c40ea0b0f6c02d03cf37537fa9e1b8ac71ec5f1d279b51477538a743063 +size 39209 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png index 7b685bcbcb..fd40cbce60 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 -size 27357 +oid sha256:3f2c03edb4eaec4917b95955ac0b51cbdce25ae9710757cad68e3ed863707f7a +size 27374 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png index 6d822fab84..8c1e0742e5 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77bc7bd3f4dab9e05ce7f68ddc50d06da03d414c33ce8e361bb83ccec38ad3c8 -size 32231 +oid sha256:3811a02bebe20381a5996bcb61176d258fbb1ca4302cf5f9742470c3f152c780 +size 32258 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png index 9c7829d9fe..ca5bf4cb5f 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cff0d41eb0ab572946867cb9ea57fcab1fb72b155273fc68b12855c100cdb0c0 -size 15974 +oid sha256:9aaf05a0f912cedf6897fac21e679429b86b0ed1d3e8b6a07cfe7ec6f60cec13 +size 15885 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png index 34c0b0c9ff..c34f208e49 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fcbb3f101b864cd0a5eeade5d43a76e42573705a90604706be696a810e7096c -size 17021 +oid sha256:903a894c80772fb4966542a6832d9d311c715495edd64bb5ca048f8218aea66a +size 16944 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png index 06ad640021..38ace64640 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f89ebacc5f45844b3606a214a4c0df8ca150003de50bd7a04680d85ddea2ee15 -size 25224 +oid sha256:17d3263b1c47a75083bd597f73baf8cf5faa74274810e85cf2ee086812ca0e60 +size 25248 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png index 7b685bcbcb..fd40cbce60 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 -size 27357 +oid sha256:3f2c03edb4eaec4917b95955ac0b51cbdce25ae9710757cad68e3ed863707f7a +size 27374 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png index 7b685bcbcb..fd40cbce60 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 -size 27357 +oid sha256:3f2c03edb4eaec4917b95955ac0b51cbdce25ae9710757cad68e3ed863707f7a +size 27374 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png new file mode 100644 index 0000000000..81355d2bfc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbed125bd5c334bb5d7fce60df615d188c713989bcd6450f59d3f37973ab2bc7 +size 36735 From 893c152272fc32200bf0e461569db452c3ae7daa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 12:28:42 +0100 Subject: [PATCH 064/203] Add missing previews --- .../MediaDeleteConfirmationBottomSheet.kt | 9 +----- .../impl/details/MediaDetailsBottomSheet.kt | 10 +----- .../mediaviewer/impl/details/Preview.kt | 32 +++++++++++++++++++ .../impl/gallery/MediaGalleryStateProvider.kt | 7 +++- 4 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt index 58b4bfaceb..b8e0075504 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt @@ -38,7 +38,6 @@ import io.element.android.libraries.designsystem.theme.components.ModalBottomShe import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.ui.media.MediaRequestData -import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.impl.R import io.element.android.libraries.ui.strings.CommonStrings @@ -155,13 +154,7 @@ private fun MediaRow( @Composable internal fun MediaDeleteConfirmationBottomSheetPreview() = ElementPreview { MediaDeleteConfirmationBottomSheet( - state = MediaBottomSheetState.MediaDeleteConfirmationState( - eventId = EventId("\$eventId"), - mediaInfo = anImageMediaInfo( - senderName = "Alice", - ), - thumbnailSource = null, - ), + state = aMediaDeleteConfirmationState(), onDelete = {}, onDismiss = {}, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt index 9e2109c978..a11abe945b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt @@ -40,7 +40,6 @@ import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.mediaviewer.api.MediaInfo -import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.impl.R import io.element.android.libraries.ui.strings.CommonStrings @@ -195,14 +194,7 @@ private fun SectionText( @Composable internal fun MediaDetailsBottomSheetPreview() = ElementPreview { MediaDetailsBottomSheet( - state = MediaBottomSheetState.MediaDetailsBottomSheetState( - eventId = EventId("\$eventId"), - canDelete = true, - mediaInfo = anImageMediaInfo( - senderName = "Alice", - ), - thumbnailSource = null, - ), + state = aMediaDetailsBottomSheetState(), onViewInTimeline = {}, onDelete = {}, onDismiss = {}, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt new file mode 100644 index 0000000000..880fcb2b91 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.details + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.mediaviewer.api.anImageMediaInfo + +fun aMediaDetailsBottomSheetState(): MediaBottomSheetState.MediaDetailsBottomSheetState { + return MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = EventId("\$eventId"), + canDelete = true, + mediaInfo = anImageMediaInfo( + senderName = "Alice", + ), + thumbnailSource = null, + ) +} + +fun aMediaDeleteConfirmationState(): MediaBottomSheetState.MediaDeleteConfirmationState { + return MediaBottomSheetState.MediaDeleteConfirmationState( + eventId = EventId("\$eventId"), + mediaInfo = anImageMediaInfo( + senderName = "Alice", + ), + thumbnailSource = null, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 0a48618643..7e551b02f4 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -11,6 +11,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.details.aMediaDeleteConfirmationState +import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate import io.element.android.libraries.mediaviewer.impl.gallery.ui.aFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aVideo @@ -65,6 +67,8 @@ open class MediaGalleryStateProvider : PreviewParameterProvider> = AsyncData.Uninitialized, fileItems: AsyncData> = AsyncData.Uninitialized, + mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden, ) = MediaGalleryState( roomName = roomName, mode = mode, imageAndVideoItems = imageAndVideoItems, fileItems = fileItems, - mediaBottomSheetState = MediaBottomSheetState.Hidden, + mediaBottomSheetState = mediaBottomSheetState, snackbarMessage = null, eventSink = {} ) From 6fbd34307654659ee0c4e9b719cad406ce43b78e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 12:58:24 +0100 Subject: [PATCH 065/203] Introduce GroupedMediaItems for code clarity --- .../impl/gallery/MediaGalleryPresenter.kt | 15 +-- .../impl/gallery/MediaGalleryState.kt | 8 +- .../impl/gallery/MediaGalleryStateProvider.kt | 100 ++++++++++-------- .../impl/gallery/MediaGalleryView.kt | 92 ++++++++-------- .../impl/gallery/MediaItemsPostProcessor.kt | 64 ++++++----- .../gallery/FakeMediaItemsPostProcessor.kt | 10 +- .../impl/gallery/MediaGalleryPresenterTest.kt | 21 ++-- 7 files changed, 170 insertions(+), 140 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index f5737a27b3..68f2aa98f0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -77,23 +77,13 @@ class MediaGalleryPresenter @AssistedInject constructor( var mediaItems by remember { mutableStateOf>>(AsyncData.Uninitialized) } - val imageAndVideoItems by remember { + val groupedMediaItems by remember { derivedStateOf { mediaItemsPostProcessor.process( mediaItems = mediaItems, - predicate = { it is MediaItem.Image || it is MediaItem.Video }, ) } } - val fileItems by remember { - derivedStateOf { - mediaItemsPostProcessor.process( - mediaItems = mediaItems, - predicate = { it is MediaItem.File }, - ) - } - } - val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() localMediaActions.Configure() @@ -165,8 +155,7 @@ class MediaGalleryPresenter @AssistedInject constructor( return MediaGalleryState( roomName = roomInfo?.name ?: room.displayName, mode = mode, - imageAndVideoItems = imageAndVideoItems, - fileItems = fileItems, + groupedMediaItems = groupedMediaItems, mediaBottomSheetState = mediaBottomSheetState, snackbarMessage = snackbarMessage, eventSink = ::handleEvents diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt index bcec4a37ca..51ae794175 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt @@ -16,13 +16,17 @@ import kotlinx.collections.immutable.ImmutableList data class MediaGalleryState( val roomName: String, val mode: MediaGalleryMode, - val imageAndVideoItems: AsyncData>, - val fileItems: AsyncData>, + val groupedMediaItems: AsyncData, val mediaBottomSheetState: MediaBottomSheetState, val snackbarMessage: SnackbarMessage?, val eventSink: (MediaGalleryEvents) -> Unit, ) +data class GroupedMediaItems( + val imageAndVideoItems: ImmutableList, + val fileItems: ImmutableList, +) + enum class MediaGalleryMode(val stringResource: Int) { Images(R.string.screen_media_browser_list_mode_media), Files(R.string.screen_media_browser_list_mode_files), diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 7e551b02f4..55fffa2a9c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -11,79 +11,91 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState -import io.element.android.libraries.mediaviewer.impl.details.aMediaDeleteConfirmationState import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate import io.element.android.libraries.mediaviewer.impl.gallery.ui.aFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aVideo import io.element.android.libraries.mediaviewer.impl.gallery.ui.anImage -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.collections.immutable.toPersistentList open class MediaGalleryStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aMediaGalleryState(), - aMediaGalleryState(imageAndVideoItems = AsyncData.Loading()), - aMediaGalleryState(imageAndVideoItems = AsyncData.Success(emptyList().toPersistentList())), + aMediaGalleryState(groupedMediaItems = AsyncData.Loading()), + aMediaGalleryState(groupedMediaItems = AsyncData.Success(aGroupedMediaItems())), aMediaGalleryState( - imageAndVideoItems = AsyncData.Success( - listOf( - aDate(id = UniqueId("0")), - anImage(id = UniqueId("1")), - aDate( - id = UniqueId("2"), - formattedDate = "September 2004", - ), - anImage(id = UniqueId("3")), - aVideo(id = UniqueId("4")), - anImage(id = UniqueId("5")), - anImage(id = UniqueId("6")), - anImage(id = UniqueId("7")), - anImage(id = UniqueId("8")), - anImage(id = UniqueId("9")), - ).toImmutableList() - ) + groupedMediaItems = AsyncData.Success( + aGroupedMediaItems( + imageAndVideoItems = listOf( + aDate(id = UniqueId("0")), + anImage(id = UniqueId("1")), + aDate( + id = UniqueId("2"), + formattedDate = "September 2004", + ), + anImage(id = UniqueId("3")), + aVideo(id = UniqueId("4")), + anImage(id = UniqueId("5")), + anImage(id = UniqueId("6")), + anImage(id = UniqueId("7")), + anImage(id = UniqueId("8")), + anImage(id = UniqueId("9")), + ).toImmutableList() + ) + ), ), aMediaGalleryState(mode = MediaGalleryMode.Files), - aMediaGalleryState(mode = MediaGalleryMode.Files, fileItems = AsyncData.Loading()), - aMediaGalleryState(mode = MediaGalleryMode.Files, fileItems = AsyncData.Success(emptyList().toPersistentList())), - aMediaGalleryState(mode = MediaGalleryMode.Files, fileItems = AsyncData.Success(emptyList().toPersistentList())), + aMediaGalleryState(mode = MediaGalleryMode.Files, groupedMediaItems = AsyncData.Loading()), + aMediaGalleryState(mode = MediaGalleryMode.Files, groupedMediaItems = AsyncData.Success(aGroupedMediaItems())), aMediaGalleryState( mode = MediaGalleryMode.Files, - fileItems = AsyncData.Success( - listOf( - aDate(id = UniqueId("0")), - aFile(id = UniqueId("1")), - aDate( - id = UniqueId("2"), - formattedDate = "September 2004", - ), - aFile(id = UniqueId("3")), - aFile(id = UniqueId("4")), - aFile(id = UniqueId("5")), - aFile(id = UniqueId("6")), - ).toImmutableList() - ) + groupedMediaItems = AsyncData.Success( + aGroupedMediaItems( + fileItems = listOf( + aDate(id = UniqueId("0")), + aFile(id = UniqueId("1")), + aDate( + id = UniqueId("2"), + formattedDate = "September 2004", + ), + aFile(id = UniqueId("3")), + aFile(id = UniqueId("4")), + aFile(id = UniqueId("5")), + aFile(id = UniqueId("6")), + ).toImmutableList() + ) + ), ), aMediaGalleryState(mediaBottomSheetState = aMediaDetailsBottomSheetState()), - aMediaGalleryState(mediaBottomSheetState = aMediaDeleteConfirmationState()), + aMediaGalleryState( + groupedMediaItems = AsyncData.Failure(Exception("Failed to load media")), + ), + aMediaGalleryState( + mode = MediaGalleryMode.Files, + groupedMediaItems = AsyncData.Failure(Exception("Failed to load media")), + ), ) } private fun aMediaGalleryState( roomName: String = "Room name", mode: MediaGalleryMode = MediaGalleryMode.Images, - imageAndVideoItems: AsyncData> = AsyncData.Uninitialized, - fileItems: AsyncData> = AsyncData.Uninitialized, + groupedMediaItems: AsyncData = AsyncData.Uninitialized, mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden, ) = MediaGalleryState( roomName = roomName, mode = mode, - imageAndVideoItems = imageAndVideoItems, - fileItems = fileItems, + groupedMediaItems = groupedMediaItems, mediaBottomSheetState = mediaBottomSheetState, snackbarMessage = null, eventSink = {} ) + +private fun aGroupedMediaItems( + imageAndVideoItems: List = emptyList(), + fileItems: List = emptyList(), +) = GroupedMediaItems( + imageAndVideoItems = imageAndVideoItems.toImmutableList(), + fileItems = fileItems.toImmutableList(), +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 88b0c5a769..9e3d89d44e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -127,18 +127,11 @@ fun MediaGalleryView( modifier = Modifier, ) { page -> val mode = MediaGalleryMode.entries[page] - when (mode) { - MediaGalleryMode.Images -> MediaGalleryImages( - imagesAndVideos = state.imageAndVideoItems, - eventSink = state.eventSink, - onItemClick = onItemClick, - ) - MediaGalleryMode.Files -> MediaGalleryFiles( - files = state.fileItems, - eventSink = state.eventSink, - onItemClick = onItemClick, - ) - } + MediaGalleryPage( + mode = mode, + state = state, + onItemClick = onItemClick, + ) } } } @@ -179,62 +172,69 @@ fun MediaGalleryView( } @Composable -private fun MediaGalleryImages( - imagesAndVideos: AsyncData>, - eventSink: (MediaGalleryEvents) -> Unit, +private fun MediaGalleryPage( + mode: MediaGalleryMode, + state: MediaGalleryState, onItemClick: (MediaItem.Event) -> Unit, ) { - when (imagesAndVideos) { + when (val groupedMediaItems = state.groupedMediaItems) { AsyncData.Uninitialized, is AsyncData.Loading -> { - LoadingContent(MediaGalleryMode.Images) + LoadingContent(mode) } is AsyncData.Success -> { - if (imagesAndVideos.data.isEmpty()) { - EmptyContent() - } else { - MediaGalleryImageGrid( - imagesAndVideos = imagesAndVideos.data, - eventSink = eventSink, + when (mode) { + MediaGalleryMode.Images -> MediaGalleryImages( + imagesAndVideos = groupedMediaItems.data.imageAndVideoItems, + eventSink = state.eventSink, + onItemClick = onItemClick, + ) + MediaGalleryMode.Files -> MediaGalleryFiles( + files = groupedMediaItems.data.fileItems, + eventSink = state.eventSink, onItemClick = onItemClick, ) } } is AsyncData.Failure -> { ErrorContent( - error = imagesAndVideos.error, + error = groupedMediaItems.error, ) } } } +@Composable +private fun MediaGalleryImages( + imagesAndVideos: ImmutableList, + eventSink: (MediaGalleryEvents) -> Unit, + onItemClick: (MediaItem.Event) -> Unit, +) { + if (imagesAndVideos.isEmpty()) { + EmptyContent() + } else { + MediaGalleryImageGrid( + imagesAndVideos = imagesAndVideos, + eventSink = eventSink, + onItemClick = onItemClick, + ) + } +} + @Composable private fun MediaGalleryFiles( - files: AsyncData>, + files: ImmutableList, eventSink: (MediaGalleryEvents) -> Unit, onItemClick: (MediaItem.Event) -> Unit, ) { - when (files) { - AsyncData.Uninitialized, - is AsyncData.Loading -> { - LoadingContent(MediaGalleryMode.Files) - } - is AsyncData.Success -> { - if (files.data.isEmpty()) { - EmptyContent() - } else { - MediaGalleryFilesList( - files = files.data, - eventSink = eventSink, - onItemClick = onItemClick, - ) - } - } - is AsyncData.Failure -> { - ErrorContent( - error = files.error, - ) - } + if (files.isEmpty()) { + EmptyContent() + } else { + MediaGalleryFilesList( + files = files, + eventSink = eventSink, + onItemClick = onItemClick, + ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index 85e972eff2..ff983ac97f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -17,54 +17,68 @@ import javax.inject.Inject interface MediaItemsPostProcessor { fun process( mediaItems: AsyncData>, - predicate: (MediaItem.Event) -> Boolean, - ): AsyncData> + ): AsyncData } @ContributesBinding(RoomScope::class) class DefaultMediaItemsPostProcessor @Inject constructor() : MediaItemsPostProcessor { override fun process( mediaItems: AsyncData>, - predicate: (MediaItem.Event) -> Boolean, - ): AsyncData> { + ): AsyncData { return when (mediaItems) { - is AsyncData.Uninitialized -> mediaItems - is AsyncData.Loading -> mediaItems - is AsyncData.Failure -> mediaItems + is AsyncData.Uninitialized -> AsyncData.Uninitialized + is AsyncData.Loading -> AsyncData.Loading() + is AsyncData.Failure -> AsyncData.Failure(mediaItems.error) is AsyncData.Success -> AsyncData.Success( - process( - mediaItems = mediaItems.data, - predicate = predicate, - ) + mediaItems.data.process() ) } } - private fun process( - mediaItems: List, - predicate: (MediaItem.Event) -> Boolean, - ) = buildList { - val eventList = mutableListOf() - for (item in mediaItems) { + private fun List.process(): GroupedMediaItems { + val imageAndVideoItems = mutableListOf() + val fileItems = mutableListOf() + + val imageAndVideoItemsSubList = mutableListOf() + val fileItemsSublist = mutableListOf() + forEach { item -> when (item) { is MediaItem.DateSeparator -> { - if (eventList.isNotEmpty()) { + if (imageAndVideoItemsSubList.isNotEmpty()) { // Date separator first - add(item) + imageAndVideoItems.add(item) // Then events - addAll(eventList) - eventList.clear() + imageAndVideoItems.addAll(imageAndVideoItemsSubList) + imageAndVideoItemsSubList.clear() + } + if (fileItemsSublist.isNotEmpty()) { + // Date separator first + fileItems.add(item) + // Then events + fileItems.addAll(fileItemsSublist) + fileItemsSublist.clear() } } is MediaItem.Event -> { - if (predicate(item)) { - eventList.add(item) + when (item) { + is MediaItem.Image, + is MediaItem.Video -> { + imageAndVideoItemsSubList.add(item) + } + is MediaItem.File -> { + fileItemsSublist.add(item) + } } } is MediaItem.LoadingIndicator -> { - add(item) + imageAndVideoItems.add(item) + fileItems.add(item) } } } - }.toImmutableList() + return GroupedMediaItems( + imageAndVideoItems = imageAndVideoItems.toImmutableList(), + fileItems = fileItems.toImmutableList(), + ) + } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt index 637c60d57d..c94fbbbb2c 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt @@ -9,9 +9,15 @@ package io.element.android.libraries.mediaviewer.impl.gallery import io.element.android.libraries.architecture.AsyncData import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf class FakeMediaItemsPostProcessor : MediaItemsPostProcessor { - override fun process(mediaItems: AsyncData>, predicate: (MediaItem.Event) -> Boolean): AsyncData> { - return mediaItems + override fun process(mediaItems: AsyncData>): AsyncData { + return AsyncData.Success( + GroupedMediaItems( + imageAndVideoItems = persistentListOf(), + fileItems = persistentListOf() + ) + ) } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index 3acc293f35..8867b8ebc3 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -29,6 +29,7 @@ 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.mockk.mockk +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -54,13 +55,17 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(2) + skipItems(1) val initialState = awaitItem() assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) assertThat(initialState.roomName).isEqualTo(A_ROOM_NAME) - assertThat(initialState.imageAndVideoItems.dataOrNull()).isEmpty() - assertThat(initialState.fileItems.dataOrNull()).isEmpty() + assertThat(initialState.groupedMediaItems.dataOrNull()).isEqualTo( + GroupedMediaItems( + imageAndVideoItems = persistentListOf(), + fileItems = persistentListOf(), + ) + ) assertThat(initialState.snackbarMessage).isNull() } } @@ -79,7 +84,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(2) + skipItems(1) val initialState = awaitItem() assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) initialState.eventSink(MediaGalleryEvents.ChangeMode(MediaGalleryMode.Files)) @@ -111,7 +116,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(2) + skipItems(1) val initialState = awaitItem() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) val item = anImage( @@ -155,7 +160,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(2) + skipItems(1) val initialState = awaitItem() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) val item = anImage( @@ -188,7 +193,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(2) + skipItems(1) val initialState = awaitItem() // Delete bottom sheet val item = anImage() @@ -221,7 +226,7 @@ class MediaGalleryPresenterTest { navigator = navigator, ) presenter.test { - skipItems(2) + skipItems(1) val initialState = awaitItem() initialState.eventSink(MediaGalleryEvents.ViewInTimeline(AN_EVENT_ID)) onViewInTimelineClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) From f314c250391ca8c82f7f5a88dcbc67edfe806c57 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 13:04:53 +0100 Subject: [PATCH 066/203] Iterate on Error rendering. --- .../mediaviewer/impl/gallery/MediaGalleryView.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 9e3d89d44e..e590d79742 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -41,6 +41,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle +import io.element.android.libraries.designsystem.components.async.AsyncFailure import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -376,8 +377,11 @@ private fun LoadingMoreIndicator( @Composable private fun ErrorContent(error: Throwable) { - // TODO - Text("Error: $error") + AsyncFailure( + throwable = error, + onRetry = null, + modifier = Modifier.fillMaxSize(), + ) } @Composable From db0bca97219adec7d193739d8eb0953eb936afdd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 13:10:25 +0100 Subject: [PATCH 067/203] Improve preview. --- .../impl/gallery/MediaGalleryStateProvider.kt | 33 ++++++++++--------- .../impl/gallery/MediaGalleryView.kt | 4 +-- .../impl/gallery/ui/ImageItemView.kt | 2 +- .../impl/gallery/ui/MediaItemFileProvider.kt | 8 ++--- .../impl/gallery/ui/MediaItemImageProvider.kt | 2 +- .../ui/MediaItemLoadingIndicatorProvider.kt | 22 +++++++++++++ .../impl/gallery/ui/MediaItemVideoProvider.kt | 6 ++-- .../impl/gallery/MediaGalleryPresenterTest.kt | 8 ++--- 8 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemLoadingIndicatorProvider.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 55fffa2a9c..81b34020f5 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -13,9 +13,10 @@ import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate -import io.element.android.libraries.mediaviewer.impl.gallery.ui.aFile -import io.element.android.libraries.mediaviewer.impl.gallery.ui.aVideo -import io.element.android.libraries.mediaviewer.impl.gallery.ui.anImage +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator import kotlinx.collections.immutable.toImmutableList open class MediaGalleryStateProvider : PreviewParameterProvider { @@ -29,18 +30,19 @@ open class MediaGalleryStateProvider : PreviewParameterProvider GridItemSpan(columns) - is MediaItem.Image, - is MediaItem.Video, - is MediaItem.File -> GridItemSpan(1) + is MediaItem.Event -> GridItemSpan(1) } }, key = { it.id() }, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt index 892b22a951..f92a29c1ae 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt @@ -67,7 +67,7 @@ fun ImageItemView( @Composable internal fun ImageItemViewPreview() = ElementPreview { ImageItemView( - image = anImage(), + image = aMediaItemImage(), onClick = {}, onLongClick = {}, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt index f5197e590d..f5374cbbc2 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt @@ -17,18 +17,18 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem class MediaItemFileProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aFile(), - aFile( + aMediaItemFile(), + aMediaItemFile( filename = "A long filename that should be truncated.jpg", caption = "A caption", ), - aFile( + aMediaItemFile( caption = loremIpsum, ), ) } -fun aFile( +fun aMediaItemFile( id: UniqueId = UniqueId("fileId"), filename: String = "filename", caption: String? = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt index a87983831c..a422fc715b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemImageProvider.kt @@ -14,7 +14,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem -fun anImage( +fun aMediaItemImage( id: UniqueId = UniqueId("imageId"), eventId: EventId? = null, senderId: UserId? = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemLoadingIndicatorProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemLoadingIndicatorProvider.kt new file mode 100644 index 0000000000..d9323e5979 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemLoadingIndicatorProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +fun aMediaItemLoadingIndicator( + id: UniqueId = UniqueId("loadingId"), +): MediaItem.LoadingIndicator { + return MediaItem.LoadingIndicator( + id = id, + direction = Timeline.PaginationDirection.BACKWARDS, + timestamp = 123, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt index 1db99c7e31..1cc223b347 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt @@ -16,14 +16,14 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem class MediaItemVideoProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aVideo(), - aVideo( + aMediaItemVideo(), + aMediaItemVideo( duration = null, ), ) } -fun aVideo( +fun aMediaItemVideo( id: UniqueId = UniqueId("videoId"), mediaSource: MediaSource = MediaSource(""), duration: String? = "1:23", diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index 8867b8ebc3..a5fe335fd3 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState -import io.element.android.libraries.mediaviewer.impl.gallery.ui.anImage +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.tests.testutils.WarmUpRule @@ -119,7 +119,7 @@ class MediaGalleryPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) - val item = anImage( + val item = aMediaItemImage( eventId = AN_EVENT_ID, senderId = A_USER_ID, ) @@ -163,7 +163,7 @@ class MediaGalleryPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) - val item = anImage( + val item = aMediaItemImage( eventId = AN_EVENT_ID, senderId = A_USER_ID_2, ) @@ -196,7 +196,7 @@ class MediaGalleryPresenterTest { skipItems(1) val initialState = awaitItem() // Delete bottom sheet - val item = anImage() + val item = aMediaItemImage() initialState.eventSink(MediaGalleryEvents.ConfirmDelete(AN_EVENT_ID, item.mediaInfo, item.thumbnailSource)) val deleteState = awaitItem() assertThat(deleteState.mediaBottomSheetState).isEqualTo( From 36eceb75e822c97b2297ce27342862fc9709d700 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 13:34:44 +0100 Subject: [PATCH 068/203] Move bottom state to the View state --- .../impl/viewer/MediaViewerEvents.kt | 3 ++ .../impl/viewer/MediaViewerPresenter.kt | 43 +++++++++++++------ .../impl/viewer/MediaViewerState.kt | 3 +- .../impl/viewer/MediaViewerStateProvider.kt | 12 +++++- .../impl/viewer/MediaViewerView.kt | 22 +++------- .../impl/viewer/MediaViewerPresenterTest.kt | 13 +++--- 6 files changed, 58 insertions(+), 38 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt index 5b85cd2b9f..6d9a31a816 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt @@ -16,5 +16,8 @@ sealed interface MediaViewerEvents { data object RetryLoading : MediaViewerEvents data object ClearLoadingError : MediaViewerEvents data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents + data object OpenInfo : MediaViewerEvents + data class ConfirmDelete(val eventId: EventId) : MediaViewerEvents + data object CloseBottomSheet : MediaViewerEvents data class Delete(val eventId: EventId) : MediaViewerEvents } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index a2201aec65..a480d1ba7c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -11,11 +11,9 @@ import android.content.ActivityNotFoundException import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -37,6 +35,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTran import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.CoroutineScope @@ -78,15 +77,7 @@ class MediaViewerPresenter @AssistedInject constructor( mediaFile.value?.close() } } - - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canDelete by produceState(false, syncUpdateFlow.value) { - value = when (inputs.mediaInfo.senderId) { - null -> false - room.sessionId -> room.canRedactOwn().getOrElse { false } && inputs.eventId != null - else -> room.canRedactOther().getOrElse { false } && inputs.eventId != null - } - } + var mediaBottomSheetState by remember { mutableStateOf(MediaBottomSheetState.Hidden) } fun handleEvents(mediaViewerEvents: MediaViewerEvents) { when (mediaViewerEvents) { @@ -95,10 +86,36 @@ class MediaViewerPresenter @AssistedInject constructor( MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value) MediaViewerEvents.Share -> coroutineScope.share(localMedia.value) MediaViewerEvents.OpenWith -> coroutineScope.open(localMedia.value) - is MediaViewerEvents.Delete -> coroutineScope.delete(mediaViewerEvents.eventId) + is MediaViewerEvents.Delete -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + coroutineScope.delete(mediaViewerEvents.eventId) + } is MediaViewerEvents.ViewInTimeline -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden navigator.onViewInTimelineClick(mediaViewerEvents.eventId) } + MediaViewerEvents.OpenInfo -> coroutineScope.launch { + mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState( + eventId = inputs.eventId, + canDelete = when (inputs.mediaInfo.senderId) { + null -> false + room.sessionId -> room.canRedactOwn().getOrElse { false } && inputs.eventId != null + else -> room.canRedactOther().getOrElse { false } && inputs.eventId != null + }, + mediaInfo = inputs.mediaInfo, + thumbnailSource = inputs.thumbnailSource, + ) + } + is MediaViewerEvents.ConfirmDelete -> { + mediaBottomSheetState = MediaBottomSheetState.MediaDeleteConfirmationState( + eventId = mediaViewerEvents.eventId, + mediaInfo = inputs.mediaInfo, + thumbnailSource = inputs.thumbnailSource ?: inputs.mediaSource, + ) + } + MediaViewerEvents.CloseBottomSheet -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + } } } @@ -111,7 +128,7 @@ class MediaViewerPresenter @AssistedInject constructor( canShowInfo = inputs.canShowInfo, canDownload = inputs.canDownload, canShare = inputs.canShare, - canDelete = canDelete, + mediaBottomSheetState = mediaBottomSheetState, eventSink = ::handleEvents ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt index bdd59c5426..6ae8554b06 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt @@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState data class MediaViewerState( val eventId: EventId?, @@ -23,6 +24,6 @@ data class MediaViewerState( val canShowInfo: Boolean, val canDownload: Boolean, val canShare: Boolean, - val canDelete: Boolean, + val mediaBottomSheetState: MediaBottomSheetState, val eventSink: (MediaViewerEvents) -> Unit, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 003e079b99..8bed5dfc49 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -17,6 +17,9 @@ import io.element.android.libraries.mediaviewer.api.anApkMediaInfo import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState +import io.element.android.libraries.mediaviewer.impl.details.aMediaDeleteConfirmationState +import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState open class MediaViewerStateProvider : PreviewParameterProvider { override val values: Sequence @@ -91,6 +94,12 @@ open class MediaViewerStateProvider : PreviewParameterProvider canShare = false, ) }, + aMediaViewerState( + mediaBottomSheetState = aMediaDetailsBottomSheetState(), + ), + aMediaViewerState( + mediaBottomSheetState = aMediaDeleteConfirmationState(), + ), ) } @@ -100,6 +109,7 @@ fun aMediaViewerState( canShowInfo: Boolean = true, canDownload: Boolean = true, canShare: Boolean = true, + mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden, eventSink: (MediaViewerEvents) -> Unit = {}, ) = MediaViewerState( eventId = null, @@ -110,6 +120,6 @@ fun aMediaViewerState( canShowInfo = canShowInfo, canDownload = canDownload, canShare = canShare, - canDelete = true, + mediaBottomSheetState = mediaBottomSheetState, eventSink = eventSink, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index f5fae4d3bb..6ca9ebec9c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -95,7 +95,6 @@ fun MediaViewerView( val defaultBottomPaddingInPixels = if (LocalInspectionMode.current) 303 else 0 var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) } BackHandler { onBackClick() } - var mediaBottomSheetState by remember { mutableStateOf(MediaBottomSheetState.Hidden) } Scaffold( modifier, containerColor = Color.Transparent, @@ -128,12 +127,7 @@ fun MediaViewerView( canShowInfo = state.canShowInfo, onBackClick = onBackClick, onInfoClick = { - mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState( - eventId = state.eventId, - canDelete = state.canDelete, - mediaInfo = state.mediaInfo, - thumbnailSource = state.thumbnailSource, - ) + state.eventSink(MediaViewerEvents.OpenInfo) }, eventSink = state.eventSink ) @@ -146,24 +140,19 @@ fun MediaViewerView( } } } - when (val bottomSheetState = mediaBottomSheetState) { + when (val bottomSheetState = state.mediaBottomSheetState) { MediaBottomSheetState.Hidden -> Unit is MediaBottomSheetState.MediaDetailsBottomSheetState -> { MediaDetailsBottomSheet( state = bottomSheetState, onViewInTimeline = { - mediaBottomSheetState = MediaBottomSheetState.Hidden state.eventSink(MediaViewerEvents.ViewInTimeline(it)) }, onDelete = { eventId -> - mediaBottomSheetState = MediaBottomSheetState.MediaDeleteConfirmationState( - eventId = eventId, - mediaInfo = state.mediaInfo, - thumbnailSource = state.thumbnailSource, - ) + state.eventSink(MediaViewerEvents.ConfirmDelete(eventId)) }, onDismiss = { - mediaBottomSheetState = MediaBottomSheetState.Hidden + state.eventSink(MediaViewerEvents.CloseBottomSheet) }, ) } @@ -171,11 +160,10 @@ fun MediaViewerView( MediaDeleteConfirmationBottomSheet( state = bottomSheetState, onDelete = { - mediaBottomSheetState = MediaBottomSheetState.Hidden state.eventSink(MediaViewerEvents.Delete(it)) }, onDismiss = { - mediaBottomSheetState = MediaBottomSheetState.Hidden + state.eventSink(MediaViewerEvents.CloseBottomSheet) }, ) } 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 2b9e56820d..43835e71e5 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 @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.anApkMediaInfo +import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.tests.testutils.WarmUpRule @@ -67,7 +68,7 @@ class MediaViewerPresenterTest { assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canDownload).isTrue() assertThat(initialState.canShare).isTrue() - assertThat(initialState.canDelete).isFalse() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -87,7 +88,7 @@ class MediaViewerPresenterTest { assertThat(initialState.canShowInfo).isFalse() assertThat(initialState.canDownload).isTrue() assertThat(initialState.canShare).isTrue() - assertThat(initialState.canDelete).isFalse() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -107,7 +108,7 @@ class MediaViewerPresenterTest { assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canDownload).isTrue() assertThat(initialState.canShare).isFalse() - assertThat(initialState.canDelete).isFalse() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -127,7 +128,7 @@ class MediaViewerPresenterTest { assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canDownload).isFalse() assertThat(initialState.canShare).isTrue() - assertThat(initialState.canDelete).isFalse() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -147,7 +148,7 @@ class MediaViewerPresenterTest { assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canDownload).isTrue() assertThat(initialState.canShare).isTrue() - assertThat(initialState.canDelete).isTrue() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -168,7 +169,7 @@ class MediaViewerPresenterTest { assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canDownload).isTrue() assertThat(initialState.canShare).isTrue() - assertThat(initialState.canDelete).isFalse() + assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } From e5b34216e9c6ac83ddb808089912b6db8db865b6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 14:39:10 +0100 Subject: [PATCH 069/203] Media timeline: improve pagination logic. --- .../matrix/impl/timeline/RustTimeline.kt | 55 ++++++++----------- .../impl/gallery/MediaGalleryPresenter.kt | 8 --- .../impl/gallery/TimelineMediaItemsFactory.kt | 25 --------- .../gallery/FakeTimelineMediaItemsFactory.kt | 5 -- .../impl/gallery/MediaGalleryPresenterTest.kt | 1 - 5 files changed, 22 insertions(+), 72 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 1a9da807a0..c8e8b77b32 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -56,6 +56,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.launchIn @@ -64,8 +65,6 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.EditedContent import org.matrix.rustcomponents.sdk.FormattedBody @@ -172,36 +171,26 @@ class RustTimeline( } } - private val backwardsPaginationMutex = Mutex() - private val forwardsPaginationMutex = Mutex() - - private fun getPaginationMutex(direction: Timeline.PaginationDirection) = when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backwardsPaginationMutex - Timeline.PaginationDirection.FORWARDS -> forwardsPaginationMutex - } - // Use NonCancellable to avoid breaking the timeline when the coroutine is cancelled. override suspend fun paginate(direction: Timeline.PaginationDirection): Result = withContext(NonCancellable) { withContext(dispatcher) { initLatch.await() - getPaginationMutex(direction).withLock { - runCatching { - if (!canPaginate(direction)) throw TimelineException.CannotPaginate - updatePaginationStatus(direction) { it.copy(isPaginating = true) } - when (direction) { - Timeline.PaginationDirection.BACKWARDS -> inner.paginateBackwards(PAGINATION_SIZE.toUShort()) - Timeline.PaginationDirection.FORWARDS -> inner.focusedPaginateForwards(PAGINATION_SIZE.toUShort()) - } - }.onFailure { error -> + runCatching { + if (!canPaginate(direction)) throw TimelineException.CannotPaginate + updatePaginationStatus(direction) { it.copy(isPaginating = true) } + when (direction) { + Timeline.PaginationDirection.BACKWARDS -> inner.paginateBackwards(PAGINATION_SIZE.toUShort()) + Timeline.PaginationDirection.FORWARDS -> inner.focusedPaginateForwards(PAGINATION_SIZE.toUShort()) + } + }.onFailure { error -> + if (error is TimelineException.CannotPaginate) { + Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backPaginationStatus.value}") + } else { updatePaginationStatus(direction) { it.copy(isPaginating = false) } - if (error is TimelineException.CannotPaginate) { - Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backPaginationStatus.value}") - } else { - Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}") - } - }.onSuccess { hasReachedEnd -> - updatePaginationStatus(direction) { it.copy(isPaginating = false, hasMoreToLoad = !hasReachedEnd) } + Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}") } + }.onSuccess { hasReachedEnd -> + updatePaginationStatus(direction) { it.copy(isPaginating = false, hasMoreToLoad = !hasReachedEnd) } } } } @@ -223,13 +212,13 @@ class RustTimeline( override val timelineItems: Flow> = combine( _timelineItems, - backPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(), - forwardPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(), + backPaginationStatus.filter { !it.isPaginating }.distinctUntilChanged(), + forwardPaginationStatus.filter { !it.isPaginating }.distinctUntilChanged(), matrixRoom.roomInfoFlow.map { it.creator }, isTimelineInitialized, ) { timelineItems, - hasMoreToLoadBackward, - hasMoreToLoadForward, + backwardPaginationStatus, + forwardPaginationStatus, roomCreator, isTimelineInitialized -> withContext(dispatcher) { @@ -239,15 +228,15 @@ class RustTimeline( items = items, isDm = matrixRoom.isDm, roomCreator = roomCreator, - hasMoreToLoadBackwards = hasMoreToLoadBackward, + hasMoreToLoadBackwards = backwardPaginationStatus.hasMoreToLoad, ) } .let { items -> loadingIndicatorsPostProcessor.process( items = items, isTimelineInitialized = isTimelineInitialized, - hasMoreToLoadBackward = hasMoreToLoadBackward, - hasMoreToLoadForward = hasMoreToLoadForward + hasMoreToLoadBackward = backwardPaginationStatus.hasMoreToLoad, + hasMoreToLoadForward = forwardPaginationStatus.hasMoreToLoad, ) } .let { items -> diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 68f2aa98f0..c122e95447 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -183,14 +183,6 @@ class MediaGalleryPresenter @AssistedInject constructor( } .launchIn(this) - timeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS) - .onEach { backwardPaginationStatus -> - if (backwardPaginationStatus.canPaginate) { - timelineMediaItemsFactory.onCanPaginate() - } - } - .launchIn(this) - timelineMediaItemsFactory.timelineItems.map { timelineItems -> AsyncData.Success(timelineItems) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt index aca0e6e477..abad9d2052 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt @@ -14,7 +14,6 @@ import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem -import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.Flow @@ -23,14 +22,11 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import timber.log.Timber import javax.inject.Inject interface TimelineMediaItemsFactory { val timelineItems: Flow> - suspend fun replaceWith(timelineItems: List) - suspend fun onCanPaginate() } @ContributesBinding(RoomScope::class) @@ -38,7 +34,6 @@ class DefaultTimelineMediaItemsFactory @Inject constructor( private val dispatchers: CoroutineDispatchers, private val virtualItemFactory: VirtualItemFactory, private val eventItemFactory: EventItemFactory, - private val systemClock: SystemClock, ) : TimelineMediaItemsFactory { private val _timelineItems = MutableSharedFlow>(replay = 1) private val lock = Mutex() @@ -66,26 +61,6 @@ class DefaultTimelineMediaItemsFactory @Inject constructor( } } - /** - * Update the timestamp of the loading indicator, so that it may trigger a new pagination request. - */ - override suspend fun onCanPaginate() { - lock.withLock { - val values = _timelineItems.replayCache.firstOrNull() ?: return@withLock - val lastItem = values.lastOrNull() - if (lastItem is MediaItem.LoadingIndicator) { - val newList = values.toMutableList().apply { - removeAt(size - 1) - val newTs = systemClock.epochMillis() - add(lastItem.copy(timestamp = newTs)) - } - _timelineItems.emit(newList.toPersistentList()) - } else { - Timber.w("onCanPaginate called but last item is not a loading indicator") - } - } - } - private suspend fun buildAndEmitTimelineItemStates( timelineItems: List, ) { diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt index 618ba855ad..c9c95230db 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.flow.flowOf class FakeTimelineMediaItemsFactory( private val replaceWithLambda: (List) -> Unit = { lambdaError() }, - private val onCanPaginateLambda: () -> Unit = { lambdaError() } ) : TimelineMediaItemsFactory { override val timelineItems: Flow> get() = flowOf(emptyList().toImmutableList()) @@ -24,8 +23,4 @@ class FakeTimelineMediaItemsFactory( override suspend fun replaceWith(timelineItems: List) { replaceWithLambda(timelineItems) } - - override suspend fun onCanPaginate() { - onCanPaginateLambda() - } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index a5fe335fd3..39c88a5597 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -247,7 +247,6 @@ class MediaGalleryPresenterTest { room = room, timelineMediaItemsFactory = FakeTimelineMediaItemsFactory( replaceWithLambda = lambdaRecorder, Unit> { _ -> }, - onCanPaginateLambda = lambdaRecorder { }, ), localMediaFactory = localMediaFactory, mediaLoader = matrixMediaLoader, From 1b7d3a33b7b448e6b4c8bd555a53a64f53f66fb3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 13:58:46 +0100 Subject: [PATCH 070/203] Add test on DefaultEventItemFactory --- .../gallery/DefaultEventItemFactoryTest.kt | 410 ++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt new file mode 100644 index 0000000000..0fe977ff8a --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -0,0 +1,410 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.media.AudioDetails +import io.element.android.libraries.matrix.api.media.AudioInfo +import io.element.android.libraries.matrix.api.media.FileInfo +import io.element.android.libraries.matrix.api.media.ImageInfo +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent +import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent +import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent +import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent +import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.OtherState +import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent +import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent +import io.element.android.libraries.matrix.api.timeline.item.event.StateContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent +import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent +import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_UNIQUE_ID +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.timeline.aMessageContent +import io.element.android.libraries.matrix.test.timeline.aPollContent +import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent +import io.element.android.libraries.matrix.test.timeline.aStickerContent +import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation +import kotlinx.collections.immutable.persistentListOf +import org.junit.Test +import kotlin.time.Duration.Companion.seconds + +class DefaultEventItemFactoryTest { + @Test + fun `create check all null cases`() { + val factory = createDefaultEventItemFactory() + val contents = listOf( + CallNotifyContent, + FailedToParseMessageLikeContent("", ""), + FailedToParseStateContent("", "", ""), + LegacyCallInviteContent, + aPollContent(), + aProfileChangeMessageContent(), + RedactedContent, + RoomMembershipContent( + userId = A_USER_ID, + userDisplayName = null, + change = null, + ), + StateContent("", OtherState.RoomCreate), + aStickerContent( + info = ImageInfo( + width = null, + height = null, + mimetype = null, + size = null, + thumbnailInfo = null, + thumbnailSource = null, + blurhash = null, + ), + mediaSource = MediaSource("") + ), + UnableToDecryptContent(UnableToDecryptContent.Data.Unknown), + UnknownContent, + ) + contents.forEach { + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = it + ) + ) + ) + assertThat(result).isNull() + } + } + + @Test + fun `create MessageContent check all null cases`() { + val factory = createDefaultEventItemFactory() + val messageTypes = listOf( + EmoteMessageType("", null), + NoticeMessageType("", null), + OtherMessageType("", ""), + LocationMessageType("", "", null), + TextMessageType("", null) + ) + messageTypes.forEach { + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = it + ) + ) + ) + ) + assertThat(result).isNull() + } + } + + @Test + fun `create for FileMessageType`() { + val factory = createDefaultEventItemFactory() + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = FileMessageType( + filename = "filename.apk", + caption = "caption", + formattedCaption = null, + source = MediaSource(""), + info = FileInfo( + mimetype = MimeTypes.Apk, + size = 123L, + thumbnailInfo = null, + thumbnailSource = null, + ) + ) + ) + ) + ) + ) + assertThat(result).isEqualTo( + MediaItem.File( + id = A_UNIQUE_ID, + eventId = AN_EVENT_ID, + mediaInfo = MediaInfo( + mimeType = MimeTypes.Apk, + filename = "filename.apk", + caption = "caption", + formattedFileSize = "123 Bytes", + fileExtension = "apk", + senderId = A_USER_ID, + senderName = "alice", + senderAvatar = null, + dateSent = "1 Jan 1970", + ), + mediaSource = MediaSource(""), + ) + ) + } + + @Test + fun `create for ImageMessageType`() { + val factory = createDefaultEventItemFactory() + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = ImageMessageType( + filename = "filename.jpg", + caption = "caption", + formattedCaption = null, + source = MediaSource(""), + info = ImageInfo( + mimetype = MimeTypes.Jpeg, + size = 123L, + thumbnailInfo = null, + thumbnailSource = null, + height = 1L, + width = 2L, + blurhash = null, + ) + ) + ) + ) + ) + ) + assertThat(result).isEqualTo( + MediaItem.Image( + id = A_UNIQUE_ID, + eventId = AN_EVENT_ID, + mediaInfo = MediaInfo( + mimeType = MimeTypes.Jpeg, + filename = "filename.jpg", + caption = "caption", + formattedFileSize = "123 Bytes", + fileExtension = "jpg", + senderId = A_USER_ID, + senderName = "alice", + senderAvatar = null, + dateSent = "1 Jan 1970", + ), + mediaSource = MediaSource(""), + thumbnailSource = null, + ) + ) + } + + @Test + fun `create for AudioMessageType`() { + val factory = createDefaultEventItemFactory() + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = AudioMessageType( + filename = "filename.mp3", + caption = "caption", + formattedCaption = null, + source = MediaSource(""), + info = AudioInfo( + mimetype = MimeTypes.Mp3, + size = 123L, + duration = 456.seconds, + ) + ) + ) + ) + ) + ) + assertThat(result).isEqualTo( + MediaItem.File( + id = A_UNIQUE_ID, + eventId = AN_EVENT_ID, + mediaInfo = MediaInfo( + mimeType = MimeTypes.Mp3, + filename = "filename.mp3", + caption = "caption", + formattedFileSize = "123 Bytes", + fileExtension = "mp3", + senderId = A_USER_ID, + senderName = "alice", + senderAvatar = null, + dateSent = "1 Jan 1970", + ), + mediaSource = MediaSource(""), + ) + ) + } + + @Test + fun `create for VideoMessageType`() { + val factory = createDefaultEventItemFactory() + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = VideoMessageType( + filename = "filename.mp4", + caption = "caption", + formattedCaption = null, + source = MediaSource(""), + info = VideoInfo( + mimetype = MimeTypes.Mp4, + size = 123L, + thumbnailInfo = null, + duration = 123.seconds, + height = 1L, + width = 2L, + thumbnailSource = null, + blurhash = null + ) + ) + ) + ) + ) + ) + assertThat(result).isEqualTo( + MediaItem.Video( + id = A_UNIQUE_ID, + eventId = AN_EVENT_ID, + mediaInfo = MediaInfo( + mimeType = MimeTypes.Mp4, + filename = "filename.mp4", + caption = "caption", + formattedFileSize = "123 Bytes", + fileExtension = "mp4", + senderId = A_USER_ID, + senderName = "alice", + senderAvatar = null, + dateSent = "1 Jan 1970", + ), + mediaSource = MediaSource(""), + thumbnailSource = null, + duration = "2:03", + ) + ) + } + + @Test + fun `create for VoiceMessageType`() { + val factory = createDefaultEventItemFactory() + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = VoiceMessageType( + filename = "filename.ogg", + caption = "caption", + formattedCaption = null, + source = MediaSource(""), + info = AudioInfo( + mimetype = MimeTypes.Ogg, + size = 123L, + duration = 456.seconds, + ), + details = AudioDetails( + duration = 456.seconds, + waveform = persistentListOf(), + ) + ) + ) + ) + ) + ) + assertThat(result).isEqualTo( + MediaItem.File( + id = A_UNIQUE_ID, + eventId = AN_EVENT_ID, + mediaInfo = MediaInfo( + mimeType = MimeTypes.Ogg, + filename = "filename.ogg", + caption = "caption", + formattedFileSize = "123 Bytes", + fileExtension = "ogg", + senderId = A_USER_ID, + senderName = "alice", + senderAvatar = null, + dateSent = "1 Jan 1970", + ), + mediaSource = MediaSource(""), + ) + ) + } + + @Test + fun `create for StickerMessageType`() { + val factory = createDefaultEventItemFactory() + val result = factory.create( + MatrixTimelineItem.Event( + uniqueId = A_UNIQUE_ID, + event = anEventTimelineItem( + content = aMessageContent( + messageType = StickerMessageType( + filename = "filename.gif", + caption = "caption", + formattedCaption = null, + source = MediaSource(""), + info = ImageInfo( + mimetype = MimeTypes.Gif, + size = 123L, + thumbnailInfo = null, + thumbnailSource = null, + height = 1L, + width = 2L, + blurhash = null, + ) + ) + ) + ) + ) + ) + assertThat(result).isEqualTo( + MediaItem.Image( + id = A_UNIQUE_ID, + eventId = AN_EVENT_ID, + mediaInfo = MediaInfo( + mimeType = MimeTypes.Gif, + filename = "filename.gif", + caption = "caption", + formattedFileSize = "123 Bytes", + fileExtension = "gif", + senderId = A_USER_ID, + senderName = "alice", + senderAvatar = null, + dateSent = "1 Jan 1970", + ), + mediaSource = MediaSource(""), + thumbnailSource = null, + ) + ) + } +} + +private fun createDefaultEventItemFactory() = DefaultEventItemFactory( + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), +) From bb06b6811002730c1eb876202337ece73ccaf306 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 15:56:54 +0100 Subject: [PATCH 071/203] Let the Presenter use real classes. --- libraries/mediaviewer/impl/build.gradle.kts | 1 + .../impl/gallery/EventItemFactory.kt | 13 ++----- .../impl/gallery/MediaItemsPostProcessor.kt | 11 +----- .../impl/gallery/TimelineMediaItemsFactory.kt | 16 ++------ .../impl/gallery/VirtualItemFactory.kt | 13 ++----- .../gallery/DefaultEventItemFactoryTest.kt | 18 ++++----- .../impl/gallery/FakeEventItemFactory.kt | 16 -------- .../gallery/FakeMediaItemsPostProcessor.kt | 23 ------------ .../gallery/FakeTimelineMediaItemsFactory.kt | 26 ------------- .../impl/gallery/FakeVirtualItemFactory.kt | 16 -------- .../impl/gallery/MediaGalleryPresenterTest.kt | 37 ++++++++++++------- 11 files changed, 45 insertions(+), 145 deletions(-) delete mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt delete mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt delete mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt delete mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index d77df8ab36..4fa63820d3 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) + testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaviewer.test) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index 711ac8c66c..15848d85a1 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -7,10 +7,8 @@ package io.element.android.libraries.mediaviewer.impl.gallery -import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.dateformatter.api.toHumanReadableDuration -import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent @@ -45,18 +43,13 @@ import java.text.DateFormat import java.util.Date import javax.inject.Inject -interface EventItemFactory { - fun create(currentTimelineItem: MatrixTimelineItem.Event): MediaItem.Event? -} - -@ContributesBinding(RoomScope::class) -class DefaultEventItemFactory @Inject constructor( +class EventItemFactory @Inject constructor( private val fileSizeFormatter: FileSizeFormatter, private val fileExtensionExtractor: FileExtensionExtractor, -) : EventItemFactory { +) { private val timeFormatter = DateFormat.getDateInstance() - override fun create( + fun create( currentTimelineItem: MatrixTimelineItem.Event, ): MediaItem.Event? { val event = currentTimelineItem.event diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index ff983ac97f..5693fbc8c4 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -7,23 +7,14 @@ package io.element.android.libraries.mediaviewer.impl.gallery -import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.di.RoomScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject -interface MediaItemsPostProcessor { +class MediaItemsPostProcessor @Inject constructor() { fun process( mediaItems: AsyncData>, - ): AsyncData -} - -@ContributesBinding(RoomScope::class) -class DefaultMediaItemsPostProcessor @Inject constructor() : MediaItemsPostProcessor { - override fun process( - mediaItems: AsyncData>, ): AsyncData { return when (mediaItems) { is AsyncData.Uninitialized -> AsyncData.Uninitialized diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt index abad9d2052..79fcb8fd99 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/TimelineMediaItemsFactory.kt @@ -7,12 +7,10 @@ package io.element.android.libraries.mediaviewer.impl.gallery -import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.diff.DefaultDiffCacheInvalidator import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList @@ -24,17 +22,11 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import javax.inject.Inject -interface TimelineMediaItemsFactory { - val timelineItems: Flow> - suspend fun replaceWith(timelineItems: List) -} - -@ContributesBinding(RoomScope::class) -class DefaultTimelineMediaItemsFactory @Inject constructor( +class TimelineMediaItemsFactory @Inject constructor( private val dispatchers: CoroutineDispatchers, private val virtualItemFactory: VirtualItemFactory, private val eventItemFactory: EventItemFactory, -) : TimelineMediaItemsFactory { +) { private val _timelineItems = MutableSharedFlow>(replay = 1) private val lock = Mutex() private val diffCache = MutableListDiffCache() @@ -50,9 +42,9 @@ class DefaultTimelineMediaItemsFactory @Inject constructor( } } - override val timelineItems: Flow> = _timelineItems.distinctUntilChanged() + val timelineItems: Flow> = _timelineItems.distinctUntilChanged() - override suspend fun replaceWith( + suspend fun replaceWith( timelineItems: List, ) = withContext(dispatchers.computation) { lock.withLock { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt index 9f541b6e16..22d5ef546b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt @@ -7,22 +7,15 @@ package io.element.android.libraries.mediaviewer.impl.gallery -import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter -import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import javax.inject.Inject -interface VirtualItemFactory { - fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? -} - -@ContributesBinding(RoomScope::class) -class DefaultVirtualItemFactory @Inject constructor( +class VirtualItemFactory @Inject constructor( private val daySeparatorFormatter: DaySeparatorFormatter, -) : VirtualItemFactory { - override fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? { +) { + fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? { return when (val virtual = timelineItem.virtual) { is VirtualTimelineItem.DayDivider -> MediaItem.DateSeparator( id = timelineItem.uniqueId, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt index 0fe977ff8a..487c30dbbf 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -55,7 +55,7 @@ import kotlin.time.Duration.Companion.seconds class DefaultEventItemFactoryTest { @Test fun `create check all null cases`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val contents = listOf( CallNotifyContent, FailedToParseMessageLikeContent("", ""), @@ -100,7 +100,7 @@ class DefaultEventItemFactoryTest { @Test fun `create MessageContent check all null cases`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val messageTypes = listOf( EmoteMessageType("", null), NoticeMessageType("", null), @@ -125,7 +125,7 @@ class DefaultEventItemFactoryTest { @Test fun `create for FileMessageType`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val result = factory.create( MatrixTimelineItem.Event( uniqueId = A_UNIQUE_ID, @@ -169,7 +169,7 @@ class DefaultEventItemFactoryTest { @Test fun `create for ImageMessageType`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val result = factory.create( MatrixTimelineItem.Event( uniqueId = A_UNIQUE_ID, @@ -217,7 +217,7 @@ class DefaultEventItemFactoryTest { @Test fun `create for AudioMessageType`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val result = factory.create( MatrixTimelineItem.Event( uniqueId = A_UNIQUE_ID, @@ -260,7 +260,7 @@ class DefaultEventItemFactoryTest { @Test fun `create for VideoMessageType`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val result = factory.create( MatrixTimelineItem.Event( uniqueId = A_UNIQUE_ID, @@ -310,7 +310,7 @@ class DefaultEventItemFactoryTest { @Test fun `create for VoiceMessageType`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val result = factory.create( MatrixTimelineItem.Event( uniqueId = A_UNIQUE_ID, @@ -357,7 +357,7 @@ class DefaultEventItemFactoryTest { @Test fun `create for StickerMessageType`() { - val factory = createDefaultEventItemFactory() + val factory = createEventItemFactory() val result = factory.create( MatrixTimelineItem.Event( uniqueId = A_UNIQUE_ID, @@ -404,7 +404,7 @@ class DefaultEventItemFactoryTest { } } -private fun createDefaultEventItemFactory() = DefaultEventItemFactory( +private fun createEventItemFactory() = EventItemFactory( fileSizeFormatter = FakeFileSizeFormatter(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), ) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt deleted file mode 100644 index 6462204721..0000000000 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeEventItemFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery - -import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem - -class FakeEventItemFactory : EventItemFactory { - override fun create(currentTimelineItem: MatrixTimelineItem.Event): MediaItem.Event? { - return null - } -} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt deleted file mode 100644 index c94fbbbb2c..0000000000 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaItemsPostProcessor.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery - -import io.element.android.libraries.architecture.AsyncData -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -class FakeMediaItemsPostProcessor : MediaItemsPostProcessor { - override fun process(mediaItems: AsyncData>): AsyncData { - return AsyncData.Success( - GroupedMediaItems( - imageAndVideoItems = persistentListOf(), - fileItems = persistentListOf() - ) - ) - } -} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt deleted file mode 100644 index c9c95230db..0000000000 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeTimelineMediaItemsFactory.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery - -import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem -import io.element.android.tests.testutils.lambda.lambdaError -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class FakeTimelineMediaItemsFactory( - private val replaceWithLambda: (List) -> Unit = { lambdaError() }, -) : TimelineMediaItemsFactory { - override val timelineItems: Flow> - get() = flowOf(emptyList().toImmutableList()) - - override suspend fun replaceWith(timelineItems: List) { - replaceWithLambda(timelineItems) - } -} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt deleted file mode 100644 index 40e2780a41..0000000000 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeVirtualItemFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery - -import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem - -class FakeVirtualItemFactory : VirtualItemFactory { - override fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? { - return null - } -} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index 39c88a5597..ee0a698285 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -9,10 +9,11 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.net.Uri import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter +import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_USER_ID @@ -24,12 +25,15 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory +import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation 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.collections.immutable.persistentListOf +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -55,7 +59,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) @@ -84,7 +88,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.mode).isEqualTo(MediaGalleryMode.Images) initialState.eventSink(MediaGalleryEvents.ChangeMode(MediaGalleryMode.Files)) @@ -106,7 +110,7 @@ class MediaGalleryPresenterTest { `present - bottom sheet state - own message`(canDeleteOwn = false) } - private suspend fun `present - bottom sheet state - own message`(canDeleteOwn: Boolean) { + private suspend fun TestScope.`present - bottom sheet state - own message`(canDeleteOwn: Boolean) { val presenter = createMediaGalleryPresenter( room = FakeMatrixRoom( sessionId = A_USER_ID, @@ -116,7 +120,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) val item = aMediaItemImage( @@ -150,7 +154,7 @@ class MediaGalleryPresenterTest { `present - bottom sheet state - other message`(canDeleteOther = false) } - private suspend fun `present - bottom sheet state - other message`(canDeleteOther: Boolean) { + private suspend fun TestScope.`present - bottom sheet state - other message`(canDeleteOther: Boolean) { val presenter = createMediaGalleryPresenter( room = FakeMatrixRoom( sessionId = A_USER_ID, @@ -160,7 +164,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) val item = aMediaItemImage( @@ -193,7 +197,7 @@ class MediaGalleryPresenterTest { ) ) presenter.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() // Delete bottom sheet val item = aMediaItemImage() @@ -226,14 +230,14 @@ class MediaGalleryPresenterTest { navigator = navigator, ) presenter.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() initialState.eventSink(MediaGalleryEvents.ViewInTimeline(AN_EVENT_ID)) onViewInTimelineClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } } - private fun createMediaGalleryPresenter( + private fun TestScope.createMediaGalleryPresenter( matrixMediaLoader: FakeMatrixMediaLoader = FakeMatrixMediaLoader(), localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(), snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), @@ -245,14 +249,21 @@ class MediaGalleryPresenterTest { return MediaGalleryPresenter( navigator = navigator, room = room, - timelineMediaItemsFactory = FakeTimelineMediaItemsFactory( - replaceWithLambda = lambdaRecorder, Unit> { _ -> }, + timelineMediaItemsFactory = TimelineMediaItemsFactory( + dispatchers = testCoroutineDispatchers(), + virtualItemFactory = VirtualItemFactory( + daySeparatorFormatter = FakeDaySeparatorFormatter(), + ), + eventItemFactory = EventItemFactory( + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), + ), ), localMediaFactory = localMediaFactory, mediaLoader = matrixMediaLoader, localMediaActions = localMediaActions, snackbarDispatcher = snackbarDispatcher, - mediaItemsPostProcessor = FakeMediaItemsPostProcessor(), + mediaItemsPostProcessor = MediaItemsPostProcessor(), ) } } From ec821bb9c7a45ad4659f9144c8ca258ffb4d98b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 15:58:44 +0100 Subject: [PATCH 072/203] Order imports. --- .../mediaviewer/impl/gallery/MediaGalleryStateProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 81b34020f5..481e200f04 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -14,9 +14,9 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile -import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo import kotlinx.collections.immutable.toImmutableList open class MediaGalleryStateProvider : PreviewParameterProvider { From f130df15d12471eeab7694b378a221b4ad6f7983 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:01:25 +0000 Subject: [PATCH 073/203] Update nschloe/action-cached-lfs-checkout action to v1.2.3 --- .github/workflows/generate_github_pages.yml | 2 +- .github/workflows/nightlyReports.yml | 2 +- .github/workflows/recordScreenshots.yml | 4 ++-- .github/workflows/tests.yml | 2 +- .github/workflows/validate-lfs.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/generate_github_pages.yml b/.github/workflows/generate_github_pages.yml index e39175c755..e63cb52cc8 100644 --- a/.github/workflows/generate_github_pages.yml +++ b/.github/workflows/generate_github_pages.yml @@ -12,7 +12,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} steps: - name: ⏬ Checkout with LFS - uses: nschloe/action-cached-lfs-checkout@v1.2.2 + uses: nschloe/action-cached-lfs-checkout@v1.2.3 - name: Use JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index e49bcb9663..55293f972f 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.repository == 'element-hq/element-x-android' }} steps: - name: ⏬ Checkout with LFS - uses: nschloe/action-cached-lfs-checkout@v1.2.2 + uses: nschloe/action-cached-lfs-checkout@v1.2.3 - name: Use JDK 21 uses: actions/setup-java@v4 diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index 161e2ade89..17012282e3 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -24,13 +24,13 @@ jobs: labels: Record-Screenshots - name: ⏬ Checkout with LFS (PR) if: github.event.label.name == 'Record-Screenshots' - uses: nschloe/action-cached-lfs-checkout@v1.2.2 + uses: nschloe/action-cached-lfs-checkout@v1.2.3 with: persist-credentials: false ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} - name: ⏬ Checkout with LFS (Branch) if: github.event_name == 'workflow_dispatch' - uses: nschloe/action-cached-lfs-checkout@v1.2.2 + uses: nschloe/action-cached-lfs-checkout@v1.2.3 with: persist-credentials: false - name: ☕️ Use JDK 21 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a3fedc9a6d..ef7a8843c2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: sudo swapon /mnt/swapfile sudo swapon --show - name: ⏬ Checkout with LFS - uses: nschloe/action-cached-lfs-checkout@v1.2.2 + uses: nschloe/action-cached-lfs-checkout@v1.2.3 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 diff --git a/.github/workflows/validate-lfs.yml b/.github/workflows/validate-lfs.yml index 1a70b1661e..dd6cc5e64f 100644 --- a/.github/workflows/validate-lfs.yml +++ b/.github/workflows/validate-lfs.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest name: Validate steps: - - uses: nschloe/action-cached-lfs-checkout@v1.2.2 + - uses: nschloe/action-cached-lfs-checkout@v1.2.3 - run: | ./tools/git/validate_lfs.sh From 1c691d2433ae4347d7b26d205f3874ae1a24f248 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2024 16:07:27 +0100 Subject: [PATCH 074/203] Add unit test on MediaItemsPostProcessor --- .../impl/gallery/MediaGalleryStateProvider.kt | 10 +- .../impl/gallery/MediaItemsPostProcessor.kt | 12 +- .../ui/MediaItemDateSeparatorProvider.kt | 6 +- .../gallery/MediaItemsPostProcessorTest.kt | 210 ++++++++++++++++++ 4 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 481e200f04..58d566dddd 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -12,7 +12,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState -import io.element.android.libraries.mediaviewer.impl.gallery.ui.aDate +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemDateSeparator import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator @@ -29,9 +29,9 @@ open class MediaGalleryStateProvider : PreviewParameterProvider { - imageAndVideoItemsSubList.add(item) + imageAndVideoItemsSubList.add(0, item) } is MediaItem.File -> { - fileItemsSublist.add(item) + fileItemsSublist.add(0, item) } } } @@ -67,6 +67,14 @@ class MediaItemsPostProcessor @Inject constructor() { } } } + if (imageAndVideoItemsSubList.isNotEmpty()) { + // Should not happen, since the SDK is always adding a date separator + imageAndVideoItems.addAll(imageAndVideoItemsSubList) + } + if (fileItemsSublist.isNotEmpty()) { + // Should not happen, since the SDK is always adding a date separator + fileItems.addAll(fileItemsSublist) + } return GroupedMediaItems( imageAndVideoItems = imageAndVideoItems.toImmutableList(), fileItems = fileItems.toImmutableList(), diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt index 2d7c3d50ab..32169751f0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt @@ -14,12 +14,12 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem class MediaItemDateSeparatorProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aDate(), - aDate(formattedDate = "A long date that should be truncated"), + aMediaItemDateSeparator(), + aMediaItemDateSeparator(formattedDate = "A long date that should be truncated"), ) } -fun aDate( +fun aMediaItemDateSeparator( id: UniqueId = UniqueId("dateId"), formattedDate: String = "October 2024", ): MediaItem.DateSeparator { diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt new file mode 100644 index 0000000000..75a911f1dc --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemDateSeparator +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo +import kotlinx.collections.immutable.toImmutableList +import org.junit.Test + +class MediaItemsPostProcessorTest { + private val file1 = aMediaItemFile(id = UniqueId("1")) + private val file2 = aMediaItemFile(id = UniqueId("2")) + private val file3 = aMediaItemFile(id = UniqueId("3")) + private val image1 = aMediaItemImage(id = UniqueId("1")) + private val image2 = aMediaItemImage(id = UniqueId("2")) + private val image3 = aMediaItemImage(id = UniqueId("3")) + private val video1 = aMediaItemVideo(id = UniqueId("1")) + private val video2 = aMediaItemVideo(id = UniqueId("2")) + private val video3 = aMediaItemVideo(id = UniqueId("3")) + private val date1 = aMediaItemDateSeparator(id = UniqueId("1")) + private val date2 = aMediaItemDateSeparator(id = UniqueId("2")) + private val date3 = aMediaItemDateSeparator(id = UniqueId("3")) + private val loading1 = aMediaItemLoadingIndicator(id = UniqueId("1")) + + @Test + fun `process Uninitialized`() { + val sut = MediaItemsPostProcessor() + val result = sut.process(AsyncData.Uninitialized) + assertThat(result).isEqualTo(AsyncData.Uninitialized) + } + + @Test + fun `process Loading`() { + val sut = MediaItemsPostProcessor() + val result = sut.process(AsyncData.Loading()) + assertThat(result).isEqualTo(AsyncData.Loading()) + } + + @Test + fun `process Failure`() { + val sut = MediaItemsPostProcessor() + val result = sut.process(AsyncData.Failure(AN_EXCEPTION)) + assertThat(result).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) + } + + @Test + fun `process Empty`() { + test( + mediaItems = listOf(), + expectedImageAndVideoItems = emptyList(), + expectedFileItems = emptyList(), + ) + } + + @Test + fun `process will reorder files`() { + test( + mediaItems = listOf( + file3, + file2, + file1, + date1, + ), + expectedImageAndVideoItems = emptyList(), + expectedFileItems = listOf( + date1, + file1, + file2, + file3, + ), + ) + } + + @Test + fun `process will reorder images`() { + test( + mediaItems = listOf( + image3, + image2, + image1, + date1, + ), + expectedImageAndVideoItems = listOf( + date1, + image1, + image2, + image3, + ), + expectedFileItems = emptyList(), + ) + } + + @Test + fun `process will split images, videos and files`() { + test( + mediaItems = listOf( + file1, + image1, + video1, + date1, + ), + expectedImageAndVideoItems = listOf( + date1, + video1, + image1, + ), + expectedFileItems = listOf( + date1, + file1, + ), + ) + } + + @Test + fun `process will skip date if there is no items`() { + test( + mediaItems = listOf( + date1, + date2, + date3, + ), + expectedImageAndVideoItems = emptyList(), + expectedFileItems = emptyList(), + ) + } + + @Test + fun `process will add the loading indicator to both list`() { + test( + mediaItems = listOf( + loading1, + ), + expectedImageAndVideoItems = listOf( + loading1, + ), + expectedFileItems = listOf( + loading1, + ), + ) + } + + @Test + fun `process will handle complex case`() { + test( + mediaItems = listOf( + file1, + image1, + video1, + date1, + file3, + date3, + video3, + video2, + date2, + loading1, + ), + expectedImageAndVideoItems = listOf( + date1, + video1, + image1, + date2, + video2, + video3, + loading1, + ), + expectedFileItems = listOf( + date1, + file1, + date3, + file3, + loading1, + ), + ) + } + + private fun test( + mediaItems: List, + expectedImageAndVideoItems: List, + expectedFileItems: List, + ) { + val sut = MediaItemsPostProcessor() + val result = sut.process(AsyncData.Success(mediaItems.toImmutableList())) + val data = result.dataOrNull()!! + + // Compare the lists to have better failure info + assertThat(data.imageAndVideoItems.toList()).isEqualTo(expectedImageAndVideoItems) + assertThat(data.fileItems.toList()).isEqualTo(expectedFileItems) + + assertThat(result).isEqualTo( + AsyncData.Success( + GroupedMediaItems( + imageAndVideoItems = expectedImageAndVideoItems.toImmutableList(), + fileItems = expectedFileItems.toImmutableList(), + ) + ) + ) + } +} From fc35e8e94962f68cfa69560df480ddafb37262ab Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 10 Dec 2024 16:09:13 +0000 Subject: [PATCH 075/203] Update screenshots --- ...es.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png | 3 +++ ....mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png | 3 +++ ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png | 3 +++ ...ibraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png | 3 +++ ...ibraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png | 3 +++ 10 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png new file mode 100644 index 0000000000..ef0ab7b7ee --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:925df53e0666d800b0f047f45abda50f1744ef1571594be50efd6008ed988b72 +size 14549 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png index 18144ddccb..7a192f89f4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b7055ce0a8214e7c445c2d1b7d30ad5ffc63617c9f9f837661dfbd057198681 -size 26092 +oid sha256:ce8fec32ec1a902edb12d9580ca73d0cc5a9838f91d31dbbf8329326b9fa1a68 +size 39676 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png index 5b1aa7f25a..03b462a778 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:789e94ac051194e1a43d2b211301537a17596fd30b7a670a7d39107dcfe6471d -size 40514 +oid sha256:983e505a211c92ae92e091f9ba7cc43a655d5f3ce6d6bcf70971d43984507326 +size 36153 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png new file mode 100644 index 0000000000..90c8032c3e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fff7687206e1b1c03ecc4da231e8d030ec730573070550f1d6a7c355ba0c90d2 +size 14525 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png new file mode 100644 index 0000000000..2ffaf9289e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3fe3f3e7668a3d529132172366622db970391dcc8718f87a7fc90ec67b93ed1 +size 13992 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png index aca9e44958..41f7c7f5fd 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f07b50b154c426638c38897fca296f5d52fb0054ad459eadf8fbe344c9b0526 -size 25475 +oid sha256:946183ccbaf14471579f3b48861715d04ed45d6352d36126a419de7e6f362bfd +size 37632 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png index 5e1abf250a..85ef965bae 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44a96a7ad9425d869b06d3339b8356fbb9d213f1ce1a987b57ddc8117885daea -size 38480 +oid sha256:4e7a43195bd617ee952ea084e6b17a7e08e8b3634e8f3ce7df6e98f067ab08dc +size 34296 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png new file mode 100644 index 0000000000..506cb4f837 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd4903a8a383c5fe3c8c5bfeaf26ab16d3c785fccaab5b66de8b31f3380e9272 +size 14104 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png new file mode 100644 index 0000000000..99618a6ea0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a6916d2441316cb3ef55ee4c6a3b3ba9134d246d72c27aa3871408a6b9e59fc +size 30716 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png new file mode 100644 index 0000000000..70f2f64c13 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:514f67acd325e01d010466786eb85e7db8071c36e2f21454be8f30c4a6a57425 +size 32356 From cedb1fd0da6efae3a5688fcb52d7d45511336618 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:02:52 +0000 Subject: [PATCH 076/203] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.70 --- 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 a0fd714363..2943814b01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -173,7 +173,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.69" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.70" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 093a20981a809025e3eda80408b6e6402e2ce77b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:31:02 +0000 Subject: [PATCH 077/203] Update dependency io.nlopez.compose.rules:detekt to v0.4.22 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 31edb5b08c..942fdd3788 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ allprojects { config.from(files("$rootDir/tools/detekt/detekt.yml")) } dependencies { - detektPlugins("io.nlopez.compose.rules:detekt:0.4.19") + detektPlugins("io.nlopez.compose.rules:detekt:0.4.22") } tasks.withType().configureEach { From fd2660948324522ed535a65ba56e1595706f0a85 Mon Sep 17 00:00:00 2001 From: gradle-update-robot Date: Wed, 11 Dec 2024 00:33:23 +0000 Subject: [PATCH 078/203] Update Gradle Wrapper from 8.10.2 to 8.11.1 Signed-off-by: gradle-update-robot --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fb602ee2af..eb1a55be0e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 0113a52c666f0267f7c0b47aad92f494f7460c8b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 09:55:04 +0100 Subject: [PATCH 079/203] Fix test. I'll iterate on the various date format in a separate PR. --- .../mediaviewer/impl/gallery/EventItemFactory.kt | 8 +++----- .../impl/gallery/DefaultEventItemFactoryTest.kt | 15 +++++++++------ .../impl/gallery/MediaGalleryPresenterTest.kt | 3 +++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index 15848d85a1..6b96500149 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import io.element.android.libraries.androidutils.filesize.FileSizeFormatter +import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter import io.element.android.libraries.dateformatter.api.toHumanReadableDuration import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType @@ -39,21 +40,18 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getDisambigua import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor import timber.log.Timber -import java.text.DateFormat -import java.util.Date import javax.inject.Inject class EventItemFactory @Inject constructor( private val fileSizeFormatter: FileSizeFormatter, private val fileExtensionExtractor: FileExtensionExtractor, + private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, ) { - private val timeFormatter = DateFormat.getDateInstance() - fun create( currentTimelineItem: MatrixTimelineItem.Event, ): MediaItem.Event? { val event = currentTimelineItem.event - val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp)) + val sentTime = lastMessageTimestampFormatter.format(currentTimelineItem.event.timestamp) return when (val content = event.content) { CallNotifyContent, is FailedToParseMessageLikeContent, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt index 487c30dbbf..3dde8176b4 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -10,6 +10,8 @@ package io.element.android.libraries.mediaviewer.impl.gallery import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE +import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.matrix.api.media.AudioDetails import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo @@ -160,7 +162,7 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = "1 Jan 1970", + dateSent = A_FORMATTED_DATE, ), mediaSource = MediaSource(""), ) @@ -207,7 +209,7 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = "1 Jan 1970", + dateSent = A_FORMATTED_DATE, ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -251,7 +253,7 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = "1 Jan 1970", + dateSent = A_FORMATTED_DATE, ), mediaSource = MediaSource(""), ) @@ -299,7 +301,7 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = "1 Jan 1970", + dateSent = A_FORMATTED_DATE, ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -348,7 +350,7 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = "1 Jan 1970", + dateSent = A_FORMATTED_DATE, ), mediaSource = MediaSource(""), ) @@ -395,7 +397,7 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = "1 Jan 1970", + dateSent = A_FORMATTED_DATE, ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -407,4 +409,5 @@ class DefaultEventItemFactoryTest { private fun createEventItemFactory() = EventItemFactory( fileSizeFormatter = FakeFileSizeFormatter(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), + lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE), ) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index ee0a698285..4aeada8701 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -10,7 +10,9 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.net.Uri import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter +import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter +import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -257,6 +259,7 @@ class MediaGalleryPresenterTest { eventItemFactory = EventItemFactory( fileSizeFormatter = FakeFileSizeFormatter(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), + lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE), ), ), localMediaFactory = localMediaFactory, From 213dd408f26bf34fcf24f2f7f867997a55924a23 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 09:57:45 +0100 Subject: [PATCH 080/203] Handle new EventCache Exception --- .../libraries/matrix/impl/auth/AuthenticationException.kt | 1 + .../matrix/impl/auth/AuthenticationExceptionMappingTest.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt index cf48d68c70..c3a920dc69 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt @@ -22,6 +22,7 @@ fun Throwable.mapAuthenticationException(): AuthenticationException { is ClientBuildException.SlidingSync -> AuthenticationException.Generic(message) is ClientBuildException.WellKnownDeserializationException -> AuthenticationException.Generic(message) is ClientBuildException.WellKnownLookupFailed -> AuthenticationException.Generic(message) + is ClientBuildException.EventCache -> AuthenticationException.Generic(message) } else -> AuthenticationException.Generic(message) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt index 81a1667f4e..810adbfb3a 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt @@ -52,6 +52,8 @@ class AuthenticationExceptionMappingTest { .isException("WellKnown Deserialization") assertThat(ClientBuildException.WellKnownLookupFailed("WellKnown Lookup Failed").mapAuthenticationException()) .isException("WellKnown Lookup Failed") + assertThat(ClientBuildException.EventCache("EventCache error").mapAuthenticationException()) + .isException("EventCache error") } private inline fun ThrowableSubject.isException(message: String) { From 1ea5388dc2a762ebf025c2799b10b02c307e6939 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 09:59:13 +0100 Subject: [PATCH 081/203] `DayDivider` has been renamed to `DateDivider`. For the main timeline, we can keep the name `VirtualTimelineItem.DayDivider` --- .../impl/timeline/item/virtual/VirtualTimelineItemMapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt index 841194abb4..befdf8903b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt @@ -13,7 +13,7 @@ import org.matrix.rustcomponents.sdk.VirtualTimelineItem as RustVirtualTimelineI class VirtualTimelineItemMapper { fun map(virtualTimelineItem: RustVirtualTimelineItem): VirtualTimelineItem { return when (virtualTimelineItem) { - is RustVirtualTimelineItem.DayDivider -> VirtualTimelineItem.DayDivider(virtualTimelineItem.ts.toLong()) + is RustVirtualTimelineItem.DateDivider -> VirtualTimelineItem.DayDivider(virtualTimelineItem.ts.toLong()) RustVirtualTimelineItem.ReadMarker -> VirtualTimelineItem.ReadMarker } } From 6fab5a5eea438cf2d5f49d600909a7965fa579cf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 10:08:53 +0100 Subject: [PATCH 082/203] Add TODO for the event cache --- .../android/libraries/matrix/impl/RustMatrixClientFactory.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 0ce1d2362b..e688452510 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -109,6 +109,9 @@ class RustMatrixClientFactory @Inject constructor( .addRootCertificates(userCertificatesProvider.provides()) .autoEnableBackups(true) .autoEnableCrossSigning(true) + // TODO Add a feature flag to enable persistent storage + // See https://github.com/matrix-org/matrix-rust-sdk/pull/4396 + .useEventCachePersistentStorage(false) .roomKeyRecipientStrategy( strategy = if (featureFlagService.isFeatureEnabled(FeatureFlags.OnlySignedDeviceIsolationMode)) { CollectStrategy.IdentityBasedStrategy From 11ebe163d344a4d9bb6332380ad2b3d084eb197d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 10:41:55 +0100 Subject: [PATCH 083/203] Fix tests --- .../matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt index c22e7adb79..a1af968c34 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt @@ -41,6 +41,7 @@ class FakeRustClientBuilder : ClientBuilder(NoPointer) { override fun slidingSyncVersionBuilder(versionBuilder: SlidingSyncVersionBuilder) = this override fun userAgent(userAgent: String) = this override fun username(username: String) = this + override fun useEventCachePersistentStorage(value: Boolean) = this override suspend fun buildWithQrCode(qrCodeData: QrCodeData, oidcConfiguration: OidcConfiguration, progressListener: QrLoginProgressListener): Client { return FakeRustClient() From 4e70c7edbeb7eb26810ebd53624817123aaa1412 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 12:28:28 +0100 Subject: [PATCH 084/203] Fix compilation issue. --- .../android/libraries/matrix/impl/room/RustMatrixRoom.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 55f639e266..c84a37cdc3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -74,6 +74,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener @@ -233,7 +234,8 @@ class RustMatrixRoom( RoomMessageEventMessageType.IMAGE, RoomMessageEventMessageType.VIDEO, RoomMessageEventMessageType.AUDIO, - ) + ), + dateDividerMode = DateDividerMode.DAILY, ).let { inner -> createTimeline(inner, mode = Timeline.Mode.MEDIA) } From 5515c938d4f65c3dab2331ab96166d75dfd5869b Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:34:11 +0000 Subject: [PATCH 085/203] [Doc] Improve instructions for building Rust SDK locally (#4015) * Improve instructions for building Rust SDK locally * Update _developer_onboarding.md --- README.md | 9 +++-- docs/_developer_onboarding.md | 76 ++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 5ae9b0c2b4..b486d55ecb 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Element X Android supports many languages. You can help us to translate the app Note that for now, we keep control on the French and German translations. -Translations can be checked screen per screen using our tool Element X Android Gallery, available at https://element-hq.github.io/element-x-android/. Note that this page is updated every Tuesday. +Translations can be checked screen per screen using our tool Element X Android Gallery, available at https://element-hq.github.io/element-x-android/. Note that this page is updated every Tuesday. More instructions about translating the application can be found at [CONTRIBUTING.md](CONTRIBUTING.md#strings). @@ -83,8 +83,11 @@ You can also come chat with the community in the Matrix [room](https://matrix.to ## Build instructions -Just clone the project and open it in Android Studio. -Makes sure to select the `app` configuration when building (as we also have sample apps in the project). +Just clone the project and open it in Android Studio. Make sure to select the +`app` configuration when building (as we also have sample apps in the project). + +To build against a local copy of the Rust SDK, see the [Developer +onboarding](docs/_developer_onboarding.md#build-the-sdk-locally) instructions. ## Support diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index 9d5bdafb7a..e234275af6 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -102,8 +102,8 @@ From these kotlin bindings we can generate native libs (.so files) and kotlin cl #### Matrix Rust Component Kotlin -To use these bindings in an android project, we need to wrap this up into an android library (as the form of an .aar file). -This is the goal of https://github.com/matrix-org/matrix-rust-components-kotlin. +To use these bindings in an android project, we need to wrap this up into an android library (as the form of an .aar file). +This is the goal of https://github.com/matrix-org/matrix-rust-components-kotlin. This repository is used for distributing kotlin releases of the Matrix Rust SDK. It'll provide the corresponding aar and also publish them on maven. @@ -117,41 +117,43 @@ You can also have access to the aars through the [release](https://github.com/ma #### Build the SDK locally -Easiest way: run the script [../tools/sdk/build_rust_sdk.sh](../tools/sdk/build_rust_sdk.sh) and just answer the questions. - -Legacy way: - -If you need to locally build the sdk-android you can use -the [build](https://github.com/matrix-org/matrix-rust-components-kotlin/blob/main/scripts/build.sh) script. - -For this please check the [prerequisites](https://github.com/matrix-org/matrix-rust-components-kotlin/blob/main/README.md#prerequisites) from the repo. - -Checkout both [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk) and [matrix-rust-components-kotlin](https://github.com/matrix-org/matrix-rust-components-kotlin) repositories -```shell -git clone git@github.com:matrix-org/matrix-rust-sdk.git -git clone git@github.com:matrix-org/matrix-rust-components-kotlin.git -``` - -Then you can launch the build script from the matrix-rust-components-kotlin repository with the following params: - -- `-p` Local path to the rust-sdk repository -- `-o` Optional output path with the expected name of the aar file. By default the aar will be located in the corresponding build/outputs/aar directory. -- `-r` Flag to build in release mode -- `-m` Option to select the gradle module to build. Default is sdk. -- `-t` Option to to select an android target to build against. Default will build for all targets. - -So for example to build the sdk against aarch64-linux-android target and copy the generated aar to Element X project: - -```shell -./scripts/build.sh -p [YOUR MATRIX RUST SDK PATH] -t aarch64-linux-android -o [YOUR element-x-android PATH]/libraries/rustsdk/matrix-rust-sdk.aar -``` +Prerequisites: +* Install the Android NDK (Native Development Kit). To do this from within + Android Studio: + 1. **Tools > SDK Manager** + 2. Click the **SDK Tools** tab. + 3. Select the **NDK (Side by side)** checkbox + 4. Click **OK**. + 5. Click **OK**. + 6. When the installation is complete, click **Finish**. +* Install `cargo-ndk`: + ``` + cargo install cargo-ndk + ``` +* Install the Android Rust toolchain: + ``` + rustup target add aarch64-linux-android + ``` +* Depending on the location of the Android SDK, you may need to set + `ANDROID_HOME`: + ``` + export ANDROID_HOME=$HOME/android/sdk + ``` + +You can then build the Rust SDK by running the script +[`tools/sdk/build_rust_sdk.sh`](../tools/sdk/build_rust_sdk.sh) and just answering +the questions. + +This will prompt you for the path to the Rust SDK, then build it and +`matrix-rust-components-kotlin`, eventually producing an aar file at +`./libraries/rustsdk/matrix-rust-sdk.aar`, which will be picked up +automatically by the Element X Android build. Troubleshooting: - You may need to set `ANDROID_NDK_HOME` e.g `export ANDROID_NDK_HOME=~/Library/Android/sdk/ndk`. - If you get the error `thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', .cargo/registry/src/index.crates.io-6f17d22bba15001f/cargo-ndk-2.11.0/src/cli.rs:345:18` try updating your Cargo NDK version. In this case, 2.11.0 is too old so `cargo install cargo-ndk` to install a newer version. - - If you get the error `Unsupported class file major version 64` try changing your JVM version. In this case, Java 20 is not supported in Gradle yet, so downgrade to an earlier version (Java 17 worked in this case). - -You are good to test your local rust development now! + - If you get the error `Unsupported class file major version `, try changing your JVM version by setting + `JAVA_HOME` and, if building via Android Studio, "File | Settings | Build, Execution, Deployment | Build Tools | Gradle | Gradle JDK". ### The Android project @@ -262,7 +264,7 @@ Here are the main points: #### Template and naming -This documentation provides you with the steps to install and use the AS plugin for generating modules in your project. +This documentation provides you with the steps to install and use the AS plugin for generating modules in your project. The plugin and templates will help you quickly create new features with a standardized structure. A. Installation @@ -276,7 +278,7 @@ Follow these steps to install and configure the plugin and templates: - Navigate to File/Manage IDE Settings/Import Settings - Pick the `tmp/file_templates.zip` files - Click on OK -4. Configure generate-module-from-template plugin : +4. Configure generate-module-from-template plugin : - Navigate to AS/Settings/Tools/Module Template Settings - Click on + / Import From File - Pick the `tools/templates/FeatureModule.json` @@ -296,9 +298,9 @@ Example for a new feature called RoomDetails: 5. The modules api/impl should be created under `features/roomdetails` directory. 6. Sync project with Gradle so the modules are recognized (no need to add them to settings.gradle). 7. You can now add more Presentation classes (Events, State, StateProvider, View, Presenter) in the impl module with the `Template Presentation Classes`. - To use it, just right click on the package where you want to generate classes, and click on `Template Presentation Classes`. + To use it, just right click on the package where you want to generate classes, and click on `Template Presentation Classes`. Fill the text field with the base name of the classes, ie `RootRoomDetails` in the `root` package. - + Note that naming of files and classes is important, since those names are used to set up code coverage rules. For instance, presenters MUST have a suffix `Presenter`,states MUST have a suffix `State`, etc. Also we want to have a common naming along all the modules. From e9a1c30462b7363a28f6d8c0a7aa88380a2f0f36 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Dec 2024 16:02:00 +0100 Subject: [PATCH 086/203] knock requests : expose api through Room --- .../libraries/matrix/api/room/MatrixRoom.kt | 6 +++ .../matrix/api/room/knock/KnockRequest.kt | 26 +++++++++++ .../matrix/impl/room/RustMatrixRoom.kt | 13 ++++++ .../impl/room/knock/RustKnockRequest.kt | 38 ++++++++++++++++ .../matrix/test/room/FakeMatrixRoom.kt | 8 ++++ .../test/room/knock/FakeKnockRequest.kt | 43 +++++++++++++++++++ 6 files changed, 134 insertions(+) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 840308af23..82ea24e7ff 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange @@ -56,6 +57,11 @@ interface MatrixRoom : Closeable { val roomTypingMembersFlow: Flow> val identityStateChangesFlow: Flow> + /** + * The current knock requests in the room as a Flow. + */ + val knockRequestsFlow: Flow> + /** * A one-to-one is a room with exactly 2 members. * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules). diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt new file mode 100644 index 0000000000..f5b25b5f2c --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.knock + +import io.element.android.libraries.matrix.api.core.UserId + +interface KnockRequest { + val userId: UserId + val displayName: String? + val avatarUrl: String? + val reason: String? + val timestamp: Long? + + suspend fun accept(): Result + + suspend fun decline(reason: String?): Result + + suspend fun declineAndBan(reason: String?): Result + + suspend fun markAsSeen(): Result +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index c84a37cdc3..44f588bd83 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange @@ -50,6 +51,7 @@ import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings import io.element.android.libraries.matrix.impl.core.RustSendHandle import io.element.android.libraries.matrix.impl.mapper.map import io.element.android.libraries.matrix.impl.room.draft.into +import io.element.android.libraries.matrix.impl.room.knock.RustKnockRequest import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper @@ -76,6 +78,8 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener +import org.matrix.rustcomponents.sdk.JoinRequest +import org.matrix.rustcomponents.sdk.RequestsToJoinListener import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener import org.matrix.rustcomponents.sdk.RoomListItem @@ -157,6 +161,15 @@ class RustMatrixRoom( }) } + override val knockRequestsFlow: Flow> = mxCallbackFlow { + innerRoom.subscribeToJoinRequests(object : RequestsToJoinListener { + override fun call(joinRequests: List) { + val knockRequests = joinRequests.map { RustKnockRequest(it) } + channel.trySend(knockRequests) + } + }) + } + // Create a dispatcher for all room methods... private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt new file mode 100644 index 0000000000..a3d133d599 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.room.knock + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.knock.KnockRequest +import org.matrix.rustcomponents.sdk.JoinRequest + +class RustKnockRequest( + private val inner: JoinRequest, +) : KnockRequest { + override val userId: UserId = UserId(inner.userId) + override val displayName: String? = inner.displayName + override val avatarUrl: String? = inner.avatarUrl + override val reason: String? = inner.reason + override val timestamp: Long? = inner.timestamp?.toLong() + + override suspend fun accept(): Result = runCatching { + inner.actions.accept() + } + + override suspend fun decline(reason: String?): Result = runCatching { + inner.actions.decline(reason) + } + + override suspend fun declineAndBan(reason: String?): Result = runCatching { + inner.actions.declineAndBan(reason) + } + + override suspend fun markAsSeen(): Result = runCatching { + inner.actions.markAsSeen() + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 41d913b89c..cfb73e267e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange @@ -163,6 +164,13 @@ class FakeMatrixRoom( _identityStateChangesFlow.tryEmit(identityStateChanges) } + private val _knockRequestsFlow: MutableSharedFlow> = MutableSharedFlow(replay = 1) + override val knockRequestsFlow: Flow> = _knockRequestsFlow + + fun emitKnockRequests(knockRequests: List) { + _knockRequestsFlow.tryEmit(knockRequests) + } + override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) override val roomNotificationSettingsStateFlow: MutableStateFlow = diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt new file mode 100644 index 0000000000..fad12e6bb6 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.room.knock + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.knock.KnockRequest +import io.element.android.libraries.matrix.test.AN_AVATAR_URL +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_NAME +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeKnockRequest( + override val userId: UserId = A_USER_ID, + override val displayName: String? = A_USER_NAME, + override val avatarUrl: String? = AN_AVATAR_URL, + override val reason: String? = null, + override val timestamp: Long? = null, + val acceptLambda: () -> Result = { lambdaError() }, + val declineLambda: (String?) -> Result = { lambdaError() }, + val declineAndBanLambda: (String?) -> Result = { lambdaError() }, + val markAsSeenLambda: () -> Result = { lambdaError() }, +) : KnockRequest { + override suspend fun accept(): Result { + return acceptLambda() + } + + override suspend fun decline(reason: String?): Result { + return declineLambda(reason) + } + + override suspend fun declineAndBan(reason: String?): Result { + return declineAndBanLambda(reason) + } + + override suspend fun markAsSeen(): Result { + return markAsSeenLambda() + } +} From cf93e339155d590fc0e9e03420bc970c27f56e68 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 11 Dec 2024 17:33:16 +0000 Subject: [PATCH 087/203] Support new properties in posthog UTD reports Add a few new properties to the UTD reports we send to Posthog. --- .../android/libraries/matrix/impl/analytics/UtdTracker.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt index 56310a7b58..a8577bbe40 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt @@ -39,6 +39,10 @@ class UtdTracker( timeToDecryptMillis = info.timeToDecryptMs?.toInt() ?: -1, domain = Error.Domain.E2EE, name = name, + eventLocalAgeMillis = info.eventLocalAgeMillis.toInt(), + userTrustsOwnIdentity = info.userTrustsOwnIdentity, + isFederated = info.ownHomeserver != info.senderHomeserver, + isMatrixDotOrg = info.ownHomeserver == "matrix.org", ) analyticsService.capture(event) } From 3e58c7aafd5f71082b1d7ad29d8e34b25752cc68 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 11 Dec 2024 18:03:09 +0000 Subject: [PATCH 088/203] Build SDK for the local hardware It's likely that you want to build the SDK to run on the emulator on your machine, so let's default to that, rather than aarch64. --- docs/_developer_onboarding.md | 4 ++-- tools/sdk/build_rust_sdk.sh | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index e234275af6..857bc6f44b 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -130,9 +130,9 @@ Prerequisites: ``` cargo install cargo-ndk ``` -* Install the Android Rust toolchain: +* Install the Android Rust toolchain for your machine's hardware: ``` - rustup target add aarch64-linux-android + rustup target add `uname -m`-linux-android ``` * Depending on the location of the Android SDK, you may need to set `ANDROID_HOME`: diff --git a/tools/sdk/build_rust_sdk.sh b/tools/sdk/build_rust_sdk.sh index f0c0378333..be4745a909 100755 --- a/tools/sdk/build_rust_sdk.sh +++ b/tools/sdk/build_rust_sdk.sh @@ -59,6 +59,10 @@ buildApp=${buildApp:-no} cd "${elementPwd}" +default_arch="$(uname -m)-linux-android" +read -p "Enter the architecture you want to build for (default '$default_arch'): " target_arch +target_arch="${target_arch:-${default_arch}}" + # If folder ../matrix-rust-components-kotlin does not exist, clone the repo if [ ! -d "../matrix-rust-components-kotlin" ]; then printf "\nFolder ../matrix-rust-components-kotlin does not exist. Cloning the repository into ../matrix-rust-components-kotlin.\n\n" @@ -71,8 +75,8 @@ git reset --hard git checkout main git pull -printf "\nBuilding the SDK for aarch64-linux-android...\n\n" -./scripts/build.sh -p "${rustSdkPath}" -m sdk -t aarch64-linux-android -o "${elementPwd}/libraries/rustsdk" +printf "\nBuilding the SDK for ${target_arch}...\n\n" +./scripts/build.sh -p "${rustSdkPath}" -m sdk -t "${target_arch}" -o "${elementPwd}/libraries/rustsdk" cd "${elementPwd}" mv ./libraries/rustsdk/sdk-android-debug.aar ./libraries/rustsdk/matrix-rust-sdk.aar From ca66840f91eca88da309a378c0bb01ce98b01ebd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:01:35 +0000 Subject: [PATCH 089/203] Update camera to v1.4.1 --- 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 2943814b01..a816910acc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ constraintlayout_compose = "1.1.0" lifecycle = "2.8.7" activity = "1.9.3" media3 = "1.5.0" -camera = "1.4.0" +camera = "1.4.1" # Compose compose_bom = "2024.11.00" From 8e9865d24aa87b0ef49fec1ff20db9abf08bd892 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:01:42 +0000 Subject: [PATCH 090/203] Update dependency androidx.compose:compose-bom to v2024.12.01 --- 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 2943814b01..8e94e6fdf3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ media3 = "1.5.0" camera = "1.4.0" # Compose -compose_bom = "2024.11.00" +compose_bom = "2024.12.01" composecompiler = "1.5.15" # Coroutines From 09c5688c63f885a305f19696fde13d0050ef3229 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:58:03 +0000 Subject: [PATCH 091/203] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.71 --- 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 2943814b01..119790ca6c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -173,7 +173,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.70" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.71" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 56a5f55e18d61cb25fc10518abfd8173c365b604 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 11 Dec 2024 22:29:35 +0000 Subject: [PATCH 092/203] Handle `uname -m` returning arm64 --- docs/_developer_onboarding.md | 2 +- tools/sdk/build_rust_sdk.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index 857bc6f44b..738bd12430 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -132,7 +132,7 @@ Prerequisites: ``` * Install the Android Rust toolchain for your machine's hardware: ``` - rustup target add `uname -m`-linux-android + rustup target add aarch64-linux-android x86_64-linux-android ``` * Depending on the location of the Android SDK, you may need to set `ANDROID_HOME`: diff --git a/tools/sdk/build_rust_sdk.sh b/tools/sdk/build_rust_sdk.sh index be4745a909..7c65604167 100755 --- a/tools/sdk/build_rust_sdk.sh +++ b/tools/sdk/build_rust_sdk.sh @@ -60,6 +60,9 @@ buildApp=${buildApp:-no} cd "${elementPwd}" default_arch="$(uname -m)-linux-android" +# On ARM MacOS, `uname -m` returns arm64, but the toolchain is called aarch64 +default_arch="${default_arch/arm64/aarch64}" + read -p "Enter the architecture you want to build for (default '$default_arch'): " target_arch target_arch="${target_arch:-${default_arch}}" From da272ddb0770cc6b39b255642819c5a5b841a3ed Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Dec 2024 23:43:20 +0100 Subject: [PATCH 093/203] Implement month separator for the Gallery. Improve day separator rendering in the timeline. Use Today, Yesterday, and the name of the day if less than 7 days and do not render the year for the current year. Improve date format for the media viewer. Rework how date and time are computed. ActionListView: Time can take more space, so update the layout. --- .../messages/impl/MessagesFlowNode.kt | 12 +- .../impl/actionlist/ActionListPresenter.kt | 8 + .../impl/actionlist/ActionListState.kt | 1 + .../actionlist/ActionListStateProvider.kt | 11 + .../impl/actionlist/ActionListView.kt | 34 ++- .../event/TimelineItemEventFactory.kt | 25 +- .../TimelineItemDaySeparatorFactory.kt | 13 +- .../impl/timeline/model/TimelineItem.kt | 1 + .../messages/impl/MessagesViewTest.kt | 3 + .../actionlist/ActionListPresenterTest.kt | 28 +- .../fixtures/TimelineItemsFactoryFixtures.kt | 7 +- .../history/model/PollHistoryItemsFactory.kt | 11 +- .../impl/history/PollHistoryPresenterTest.kt | 4 +- .../datasource/RoomListRoomSummaryFactory.kt | 11 +- .../roomlist/impl/RoomListPresenterTest.kt | 12 +- .../impl/datasource/RoomListDataSourceTest.kt | 18 +- .../RoomListRoomSummaryFactoryTest.kt | 7 +- .../impl/model/RoomListRoomSummaryTest.kt | 4 +- .../search/RoomListSearchPresenterTest.kt | 4 +- .../incoming/IncomingVerificationPresenter.kt | 10 +- .../IncomingVerificationPresenterTest.kt | 15 +- .../core/extensions/BasicExtensions.kt | 15 + .../dateformatter/api/DateFormatter.kt | 26 ++ .../api/DaySeparatorFormatter.kt | 12 - .../api/LastMessageTimestampFormatter.kt | 12 - libraries/dateformatter/impl/build.gradle.kts | 14 + .../dateformatter/impl/DateFormatterDay.kt | 57 ++++ .../dateformatter/impl/DateFormatterFull.kt | 38 +++ .../dateformatter/impl/DateFormatterMonth.kt | 32 ++ ...stampFormatter.kt => DateFormatterTime.kt} | 18 +- .../impl/DateFormatterTimeOnly.kt | 22 ++ .../dateformatter/impl/DateFormatters.kt | 52 ++-- .../dateformatter/impl/DateTimeFormatters.kt | 54 ++++ .../impl/DefaultDateFormatter.kt | 48 +++ .../impl/DefaultDaySeparatorFormatter.kt | 25 -- .../impl/LocaleChangeObserver.kt | 56 ++++ .../impl/di/DateFormatterModule.kt | 4 - .../src/main/res/values-fr/translations.xml | 5 + .../impl/src/main/res/values/localazy.xml | 5 + .../impl/DefaultDateFormatterFrTest.kt | 277 ++++++++++++++++++ .../impl/DefaultDateFormatterTest.kt | 277 ++++++++++++++++++ ...efaultLastMessageTimestampFormatterTest.kt | 109 ------- .../dateformatter/test/FakeDateFormatter.kt | 25 ++ .../test/FakeDaySeparatorFormatter.kt | 22 -- .../test/FakeLastMessageTimestampFormatter.kt | 24 -- .../matrix/impl/room/RustMatrixRoom.kt | 2 +- .../libraries/mediaviewer/api/MediaInfo.kt | 11 + .../impl/DefaultMediaViewerEntryPoint.kt | 1 + .../impl/details/MediaDetailsBottomSheet.kt | 2 +- .../mediaviewer/impl/details/Preview.kt | 5 +- .../impl/gallery/EventItemFactory.kt | 32 +- .../impl/gallery/VirtualItemFactory.kt | 11 +- .../impl/local/AndroidLocalMediaFactory.kt | 4 + .../gallery/DefaultEventItemFactoryTest.kt | 23 +- .../impl/gallery/MediaGalleryPresenterTest.kt | 8 +- .../local/AndroidLocalMediaFactoryTest.kt | 17 +- .../mediaviewer/test/FakeLocalMediaFactory.kt | 3 +- tests/testutils/build.gradle.kts | 1 + .../InstrumentationStringProvider.kt | 26 ++ tools/localazy/config.json | 6 + 60 files changed, 1270 insertions(+), 350 deletions(-) create mode 100644 libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt delete mode 100644 libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DaySeparatorFormatter.kt delete mode 100644 libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/LastMessageTimestampFormatter.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt rename libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/{DefaultLastMessageTimestampFormatter.kt => DateFormatterTime.kt} (62%) create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt delete mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt create mode 100644 libraries/dateformatter/impl/src/main/res/values-fr/translations.xml create mode 100644 libraries/dateformatter/impl/src/main/res/values/localazy.xml create mode 100644 libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt create mode 100644 libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt delete mode 100644 libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt create mode 100644 libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt delete mode 100644 libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt delete mode 100644 libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeLastMessageTimestampFormatter.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 3b5fb67540..6754f5c683 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -55,6 +55,8 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.Overlay import io.element.android.libraries.architecture.overlay.operation.hide import io.element.android.libraries.architecture.overlay.operation.show +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId @@ -97,6 +99,7 @@ class MessagesFlowNode @AssistedInject constructor( private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider, private val timelineController: TimelineController, private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, + private val dateFormatter: DateFormatter, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialTarget.toNavTarget(), @@ -436,7 +439,14 @@ class MessagesFlowNode @AssistedInject constructor( senderId = event.senderId, senderName = event.safeSenderName, senderAvatar = event.senderAvatar.url, - dateSent = event.sentTime, + dateSent = dateFormatter.format( + event.sentTimeMillis, + mode = DateFormatterMode.Day, + ), + dateSentFull = dateFormatter.format( + timestamp = event.sentTimeMillis, + mode = DateFormatterMode.Full, + ), ), mediaSource = mediaSource, thumbnailSource = thumbnailSource, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 411ff37c8f..95a6dc5a6f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -37,6 +37,8 @@ import io.element.android.features.messages.impl.timeline.model.event.canBeCopie import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded import io.element.android.features.messages.impl.timeline.model.event.canReact import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.di.RoomScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags @@ -64,6 +66,7 @@ class DefaultActionListPresenter @AssistedInject constructor( private val room: MatrixRoom, private val userSendFailureFactory: VerifiedUserSendFailureFactory, private val featureFlagService: FeatureFlagService, + private val dateFormatter: DateFormatter, ) : ActionListPresenter { @AssistedFactory @ContributesBinding(RoomScope::class) @@ -131,6 +134,11 @@ class DefaultActionListPresenter @AssistedInject constructor( if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) { target.value = ActionListState.Target.Success( event = timelineItem, + sentTimeFull = dateFormatter.format( + timelineItem.sentTimeMillis, + DateFormatterMode.Full, + useRelative = true, + ), displayEmojiReactions = displayEmojiReactions, verifiedUserSendFailure = verifiedUserSendFailure, actions = actions.toImmutableList() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt index 75c598df36..56bc1ca0bd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt @@ -24,6 +24,7 @@ data class ActionListState( data class Loading(val event: TimelineItem.Event) : Target data class Success( val event: TimelineItem.Event, + val sentTimeFull: String, val displayEmojiReactions: Boolean, val verifiedUserSendFailure: VerifiedUserSendFailure, val actions: ImmutableList, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index a5f027a535..1638a03fa3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -37,6 +37,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent( timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), @@ -49,6 +50,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayNameAmbiguous = true, timelineItemReactions = reactionsState, ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList( @@ -62,6 +64,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemVideoContent(), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList( @@ -75,6 +78,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemFileContent(), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList( @@ -88,6 +92,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemAudioContent(), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList( @@ -101,6 +106,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemVoiceContent(caption = null), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList( @@ -114,6 +120,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemLocationContent(), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), @@ -125,6 +132,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemLocationContent(), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), @@ -136,6 +144,7 @@ open class ActionListStateProvider : PreviewParameterProvider { content = aTimelineItemPollContent(), timelineItemReactions = reactionsState ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemPollActionList(), @@ -147,6 +156,7 @@ open class ActionListStateProvider : PreviewParameterProvider { timelineItemReactions = reactionsState, messageShield = MessageShield.UnknownDevice(isCritical = true) ), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), @@ -155,6 +165,7 @@ open class ActionListStateProvider : PreviewParameterProvider { anActionListState( target = ActionListState.Target.Success( event = aTimelineItemEvent(), + sentTimeFull = "January 1, 1970 at 12:00 AM", displayEmojiReactions = true, verifiedUserSendFailure = anUnsignedDeviceSendFailure(), actions = aTimelineItemActionList(), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index 7d30edd116..4cf0928d5c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -185,6 +185,7 @@ private fun ActionListViewContent( Column { MessageSummary( event = target.event, + sentTimeFull = target.sentTimeFull, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) @@ -245,7 +246,11 @@ private fun ActionListViewContent( @Suppress("MultipleEmitters") // False positive @Composable -private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modifier) { +private fun MessageSummary( + event: TimelineItem.Event, + sentTimeFull: String, + modifier: Modifier = Modifier, +) { val content: @Composable () -> Unit val icon: @Composable () -> Unit = { Avatar(avatarData = event.senderAvatar.copy(size = AvatarSize.MessageActionSender)) } val contentStyle = ElementTheme.typography.fontBodyMdRegular.copy(color = MaterialTheme.colorScheme.secondary) @@ -300,20 +305,23 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif icon() Spacer(modifier = Modifier.width(8.dp)) Column(modifier = Modifier.weight(1f)) { - SenderName( - senderId = event.senderId, - senderProfile = event.senderProfile, - senderNameMode = SenderNameMode.ActionList, - ) + Row { + SenderName( + modifier = Modifier.weight(1f), + senderId = event.senderId, + senderProfile = event.senderProfile, + senderNameMode = SenderNameMode.ActionList, + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = sentTimeFull, + style = ElementTheme.typography.fontBodyXsRegular, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.End, + ) + } content() } - Spacer(modifier = Modifier.width(16.dp)) - Text( - event.sentTime, - style = ElementTheme.typography.fontBodyXsRegular, - color = MaterialTheme.colorScheme.secondary, - textAlign = TextAlign.End, - ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index d94ca9013a..3700e02ccf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -20,7 +20,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemGrou import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.libraries.core.bool.orTrue -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.MatrixClient @@ -32,14 +33,13 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getDisambigua import io.element.android.libraries.matrix.ui.messages.reply.map import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import java.text.DateFormat import java.util.Date class TimelineItemEventFactory @AssistedInject constructor( @Assisted private val config: TimelineItemsFactoryConfig, private val contentFactory: TimelineItemContentFactory, private val matrixClient: MatrixClient, - private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, + private val dateFormatter: DateFormatter, private val permalinkParser: PermalinkParser, ) { @AssistedFactory @@ -57,9 +57,10 @@ class TimelineItemEventFactory @AssistedInject constructor( val groupPosition = computeGroupPosition(currentTimelineItem, timelineItems, index) val senderProfile = currentTimelineItem.event.senderProfile - val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) - val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp)) - + val sentTime = dateFormatter.format( + timestamp = currentTimelineItem.event.timestamp, + mode = DateFormatterMode.TimeOnly, + ) val senderAvatarData = AvatarData( id = currentSender.value, name = senderProfile.getDisambiguatedDisplayName(currentSender), @@ -78,6 +79,7 @@ class TimelineItemEventFactory @AssistedInject constructor( isMine = currentTimelineItem.event.isOwn, isEditable = currentTimelineItem.event.isEditable, canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo, + sentTimeMillis = currentTimelineItem.event.timestamp, sentTime = sentTime, groupPosition = groupPosition, reactionsState = currentTimelineItem.computeReactionsState(), @@ -106,7 +108,6 @@ class TimelineItemEventFactory @AssistedInject constructor( if (!config.computeReactions) { return TimelineItemReactions(reactions = persistentListOf()) } - val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) var aggregatedReactions = this.event.reactions.map { reaction -> // Sort reactions within an aggregation by timestamp descending. // This puts the most recent at the top, useful in cases like the @@ -121,7 +122,10 @@ class TimelineItemEventFactory @AssistedInject constructor( AggregatedReactionSender( senderId = it.senderId, timestamp = date, - sentTime = timeFormatter.format(date), + sentTime = dateFormatter.format( + it.timestamp, + DateFormatterMode.TimeOrDate, + ), ) } .toImmutableList() @@ -157,7 +161,10 @@ class TimelineItemEventFactory @AssistedInject constructor( url = roomMember?.avatarUrl, size = AvatarSize.TimelineReadReceipt, ), - formattedDate = lastMessageTimestampFormatter.format(receipt.timestamp) + formattedDate = dateFormatter.format( + receipt.timestamp, + mode = DateFormatterMode.TimeOrDate, + ) ) } .toImmutableList() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt index 41966c036b..cd680d4e80 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt @@ -9,13 +9,20 @@ package io.element.android.features.messages.impl.timeline.factories.virtual import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel -import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import javax.inject.Inject -class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) { +class TimelineItemDaySeparatorFactory @Inject constructor( + private val dateFormatter: DateFormatter, +) { fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel { - val formattedDate = daySeparatorFormatter.format(virtualItem.timestamp) + val formattedDate = dateFormatter.format( + timestamp = virtualItem.timestamp, + mode = DateFormatterMode.Day, + useRelative = true, + ) return TimelineItemDaySeparatorModel( formattedDate = formattedDate ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 0a392aac6a..53237ef4de 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -71,6 +71,7 @@ sealed interface TimelineItem { val senderProfile: ProfileTimelineDetails, val senderAvatar: AvatarData, val content: TimelineItemEventContent, + val sentTimeMillis: Long = 0L, val sentTime: String = "", val isMine: Boolean = false, val isEditable: Boolean, 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 b15f358828..c8305f971b 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 @@ -327,6 +327,7 @@ class MessagesViewTest { actionListState = anActionListState( target = ActionListState.Target.Success( event = timelineItem, + sentTimeFull = "", displayEmojiReactions = true, actions = persistentListOf(TimelineItemAction.Edit), verifiedUserSendFailure = VerifiedUserSendFailure.None, @@ -399,6 +400,7 @@ class MessagesViewTest { actionListState = anActionListState( target = ActionListState.Target.Success( event = timelineItem, + sentTimeFull = "", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf(TimelineItemAction.Edit), @@ -427,6 +429,7 @@ class MessagesViewTest { actionListState = anActionListState( target = ActionListState.Target.Success( event = timelineItem, + sentTimeFull = "", displayEmojiReactions = true, verifiedUserSendFailure = aChangedIdentitySendFailure(), actions = persistentListOf(), 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 49db6f6c95..14605f31d5 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 @@ -26,6 +26,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -86,6 +87,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -128,6 +130,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -170,6 +173,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -215,6 +219,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -263,6 +268,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -308,6 +314,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -355,6 +362,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -403,6 +411,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -448,6 +457,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -496,6 +506,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -542,6 +553,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -592,6 +604,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -641,6 +654,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -691,6 +705,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -738,6 +753,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = stateEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -808,6 +824,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -855,6 +872,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -909,6 +927,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1006,6 +1025,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1046,6 +1066,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1089,6 +1110,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1131,6 +1153,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1174,6 +1197,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1214,6 +1238,7 @@ class ActionListPresenterTest { assertThat(successState.target).isEqualTo( ActionListState.Target.Success( event = messageEvent, + sentTimeFull = "0 Full true", displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( @@ -1268,6 +1293,7 @@ private fun createActionListPresenter( initialState = mapOf( FeatureFlags.MediaCaptionCreation.key to allowCaption, ), - ) + ), + dateFormatter = FakeDateFormatter(), ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt index 51c4cb43ba..df76e15b6c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt @@ -28,8 +28,7 @@ import io.element.android.features.messages.impl.utils.FakeTextPillificationHelp import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.features.poll.test.pollcontent.FakePollContentStateFactory import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter -import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem @@ -80,7 +79,7 @@ internal fun TestScope.aTimelineItemsFactory( failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(), ), matrixClient = matrixClient, - lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), + dateFormatter = FakeDateFormatter(), permalinkParser = FakePermalinkParser(), config = config ) @@ -88,7 +87,7 @@ internal fun TestScope.aTimelineItemsFactory( }, virtualItemFactory = TimelineItemVirtualFactory( daySeparatorFactory = TimelineItemDaySeparatorFactory( - FakeDaySeparatorFormatter() + FakeDateFormatter() ), ), timelineItemGrouper = TimelineItemGrouper(), diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt index 60814477c9..1c667efffb 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt @@ -9,7 +9,8 @@ package io.element.android.features.poll.impl.history.model import io.element.android.features.poll.api.pollcontent.PollContentStateFactory import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import kotlinx.collections.immutable.toPersistentList @@ -18,7 +19,7 @@ import javax.inject.Inject class PollHistoryItemsFactory @Inject constructor( private val pollContentStateFactory: PollContentStateFactory, - private val daySeparatorFormatter: DaySeparatorFormatter, + private val dateFormatter: DateFormatter, private val dispatchers: CoroutineDispatchers, ) { suspend fun create(timelineItems: List): PollHistoryItems = withContext(dispatchers.computation) { @@ -45,7 +46,11 @@ class PollHistoryItemsFactory @Inject constructor( val pollContent = timelineItem.event.content as? PollContent ?: return null val pollContentState = pollContentStateFactory.create(timelineItem.event, pollContent) PollHistoryItem( - formattedDate = daySeparatorFormatter.format(timelineItem.event.timestamp), + formattedDate = dateFormatter.format( + timestamp = timelineItem.event.timestamp, + mode = DateFormatterMode.Day, + useRelative = true + ), state = pollContentState ) } diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt index 6dfa8df752..d3e67e223e 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt @@ -21,7 +21,7 @@ import io.element.android.features.poll.impl.history.model.PollHistoryItemsFacto import io.element.android.features.poll.impl.model.DefaultPollContentStateFactory import io.element.android.features.poll.test.actions.FakeEndPollAction import io.element.android.features.poll.test.actions.FakeSendPollResponseAction -import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -161,7 +161,7 @@ class PollHistoryPresenterTest { sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(), pollHistoryItemFactory: PollHistoryItemsFactory = PollHistoryItemsFactory( pollContentStateFactory = DefaultPollContentStateFactory(FakeMatrixClient()), - daySeparatorFormatter = FakeDaySeparatorFormatter(), + dateFormatter = FakeDateFormatter(), dispatchers = testCoroutineDispatchers(), ), ): PollHistoryPresenter { diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt index 534de3c4d4..a77f1545da 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt @@ -10,7 +10,8 @@ package io.element.android.features.roomlist.impl.datasource import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType import io.element.android.libraries.core.extensions.orEmpty -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.matrix.api.room.CurrentUserMembership @@ -22,7 +23,7 @@ import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject class RoomListRoomSummaryFactory @Inject constructor( - private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, + private val dateFormatter: DateFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, ) { fun create(roomSummary: RoomSummary): RoomListRoomSummary { @@ -36,7 +37,11 @@ class RoomListRoomSummaryFactory @Inject constructor( numberOfUnreadMentions = roomInfo.numUnreadMentions, numberOfUnreadNotifications = roomInfo.numUnreadNotifications, isMarkedUnread = roomInfo.isMarkedUnread, - timestamp = lastMessageTimestampFormatter.format(roomSummary.lastMessageTimestamp), + timestamp = dateFormatter.format( + timestamp = roomSummary.lastMessageTimestamp, + mode = DateFormatterMode.TimeOrDate, + useRelative = true, + ), lastMessage = roomSummary.lastMessage?.let { message -> roomLastMessageFormatter.format(message.event, roomInfo.isDm) }.orEmpty(), diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt index 69e9a7d401..84ef3078e4 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt @@ -31,9 +31,8 @@ import io.element.android.features.roomlist.impl.search.RoomListSearchState import io.element.android.features.roomlist.impl.search.aRoomListSearchState import io.element.android.libraries.androidutils.system.DateTimeObserver import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter -import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter @@ -188,6 +187,7 @@ class RoomListPresenterTest { createRoomListRoomSummary( numberOfUnreadMentions = 1, numberOfUnreadMessages = 2, + timestamp = "0 TimeOrDate true", ) ) cancelAndIgnoreRemainingEvents() @@ -633,9 +633,7 @@ class RoomListPresenterTest { networkMonitor: NetworkMonitor = FakeNetworkMonitor(), snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), leaveRoomState: LeaveRoomState = aLeaveRoomState(), - lastMessageTimestampFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter().apply { - givenFormat(A_FORMATTED_DATE) - }, + dateFormatter: DateFormatter = FakeDateFormatter(), roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(), sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), featureFlagService: FeatureFlagService = FakeFeatureFlagService(), @@ -652,7 +650,7 @@ class RoomListPresenterTest { roomListDataSource = RoomListDataSource( roomListService = client.roomListService, roomListRoomSummaryFactory = aRoomListRoomSummaryFactory( - lastMessageTimestampFormatter = lastMessageTimestampFormatter, + dateFormatter = dateFormatter, roomLastMessageFormatter = roomLastMessageFormatter, ), coroutineDispatchers = testCoroutineDispatchers(), diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSourceTest.kt index f02c53e6f6..1839b35688 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSourceTest.kt @@ -11,7 +11,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.roomlist.impl.FakeDateTimeObserver import io.element.android.libraries.androidutils.system.DateTimeObserver -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.aRoomSummary @@ -30,12 +30,12 @@ class RoomListDataSourceTest { postAllRooms(listOf(aRoomSummary())) } val dateTimeObserver = FakeDateTimeObserver() - val lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter() - lastMessageTimestampFormatter.givenFormat("Today") + var dateFormatterResult = "Today" + val dateFormatter = FakeDateFormatter({ _, _, _ -> dateFormatterResult }) val roomListDataSource = createRoomListDataSource( roomListService = roomListService, roomListRoomSummaryFactory = aRoomListRoomSummaryFactory( - lastMessageTimestampFormatter = lastMessageTimestampFormatter, + dateFormatter = dateFormatter, ), dateTimeObserver = dateTimeObserver, ) @@ -47,7 +47,7 @@ class RoomListDataSourceTest { val initialRoomList = awaitItem() assertThat(initialRoomList).isNotEmpty() assertThat(initialRoomList.first().timestamp).isEqualTo("Today") - lastMessageTimestampFormatter.givenFormat("Yesterday") + dateFormatterResult = "Yesterday" // Trigger a date change dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now())) // Check there is a new list and it's not the same as the previous one @@ -64,12 +64,12 @@ class RoomListDataSourceTest { postAllRooms(listOf(aRoomSummary())) } val dateTimeObserver = FakeDateTimeObserver() - val lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter() - lastMessageTimestampFormatter.givenFormat("Today") + var dateFormatterResult = "Today" + val dateFormatter = FakeDateFormatter({ _, _, _ -> dateFormatterResult }) val roomListDataSource = createRoomListDataSource( roomListService = roomListService, roomListRoomSummaryFactory = aRoomListRoomSummaryFactory( - lastMessageTimestampFormatter = lastMessageTimestampFormatter, + dateFormatter = dateFormatter, ), dateTimeObserver = dateTimeObserver, ) @@ -80,7 +80,7 @@ class RoomListDataSourceTest { val initialRoomList = awaitItem() assertThat(initialRoomList).isNotEmpty() assertThat(initialRoomList.first().timestamp).isEqualTo("Today") - lastMessageTimestampFormatter.givenFormat("Yesterday") + dateFormatterResult = "Yesterday" // Trigger a timezone change dateTimeObserver.given(DateTimeObserver.Event.TimeZoneChanged) // Check there is a new list and it's not the same as the previous one diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactoryTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactoryTest.kt index 8a26120a9e..41996b24db 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactoryTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactoryTest.kt @@ -7,13 +7,14 @@ package io.element.android.features.roomlist.impl.datasource -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter fun aRoomListRoomSummaryFactory( - lastMessageTimestampFormatter: LastMessageTimestampFormatter = LastMessageTimestampFormatter { _ -> "Today" }, + dateFormatter: DateFormatter = FakeDateFormatter { _, _, _ -> "Today" }, roomLastMessageFormatter: RoomLastMessageFormatter = RoomLastMessageFormatter { _, _ -> "Hey" } ) = RoomListRoomSummaryFactory( - lastMessageTimestampFormatter = lastMessageTimestampFormatter, + dateFormatter = dateFormatter, roomLastMessageFormatter = roomLastMessageFormatter ) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt index 7e91fa59de..fbe7137ed8 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt @@ -8,7 +8,6 @@ package io.element.android.features.roomlist.impl.model import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -84,6 +83,7 @@ internal fun createRoomListRoomSummary( isFavorite: Boolean = false, displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM, heroes: List = emptyList(), + timestamp: String? = null, ) = RoomListRoomSummary( id = A_ROOM_ID.value, roomId = A_ROOM_ID, @@ -92,7 +92,7 @@ internal fun createRoomListRoomSummary( numberOfUnreadMessages = numberOfUnreadMessages, numberOfUnreadNotifications = numberOfUnreadNotifications, isMarkedUnread = isMarkedUnread, - timestamp = A_FORMATTED_DATE, + timestamp = timestamp, lastMessage = "", avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem), displayType = displayType, diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTest.kt index 6ede9544ec..0d86860445 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTest.kt @@ -12,7 +12,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags @@ -143,7 +143,7 @@ fun TestScope.createRoomListSearchPresenter( dataSource = RoomListSearchDataSource( roomListService = roomListService, roomSummaryFactory = aRoomListRoomSummaryFactory( - lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), + dateFormatter = FakeDateFormatter(), roomLastMessageFormatter = FakeRoomLastMessageFormatter(), ), coroutineDispatchers = testCoroutineDispatchers(), diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt index ebd897d84c..601e7cce16 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt @@ -20,7 +20,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.verifysession.impl.incoming.IncomingVerificationState.Step import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.VerificationFlowState @@ -37,7 +38,7 @@ class IncomingVerificationPresenter @AssistedInject constructor( @Assisted private val navigator: IncomingVerificationNavigator, private val sessionVerificationService: SessionVerificationService, private val stateMachine: IncomingVerificationStateMachine, - private val dateFormatter: LastMessageTimestampFormatter, + private val dateFormatter: DateFormatter, ) : Presenter { @AssistedFactory interface Factory { @@ -59,7 +60,10 @@ class IncomingVerificationPresenter @AssistedInject constructor( } val stateAndDispatch = stateMachine.rememberStateAndDispatch() val formattedSignInTime = remember { - dateFormatter.format(sessionVerificationRequestDetails.firstSeenTimestamp) + dateFormatter.format( + timestamp = sessionVerificationRequestDetails.firstSeenTimestamp, + mode = DateFormatterMode.TimeOrDate, + ) } val step by remember { derivedStateOf { diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt index 773b7b390b..c4406009da 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt @@ -9,9 +9,8 @@ package io.element.android.features.verifysession.impl.incoming import com.google.common.truth.Truth.assertThat import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter -import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -56,7 +55,7 @@ class IncomingVerificationPresenterTest { IncomingVerificationState.Step.Initial( deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, - formattedSignInTime = A_FORMATTED_DATE, + formattedSignInTime = "567 TimeOrDate false", isWaiting = false, ) ) @@ -119,7 +118,7 @@ class IncomingVerificationPresenterTest { IncomingVerificationState.Step.Initial( deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, - formattedSignInTime = A_FORMATTED_DATE, + formattedSignInTime = "567 TimeOrDate false", isWaiting = false, ) ) @@ -178,7 +177,7 @@ class IncomingVerificationPresenterTest { IncomingVerificationState.Step.Initial( deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, - formattedSignInTime = A_FORMATTED_DATE, + formattedSignInTime = "567 TimeOrDate false", isWaiting = false, ) ) @@ -210,7 +209,7 @@ class IncomingVerificationPresenterTest { IncomingVerificationState.Step.Initial( deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, - formattedSignInTime = A_FORMATTED_DATE, + formattedSignInTime = "567 TimeOrDate false", isWaiting = false, ) ) @@ -281,7 +280,7 @@ class IncomingVerificationPresenterTest { sessionVerificationRequestDetails: SessionVerificationRequestDetails = aSessionVerificationRequestDetails, navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() }, service: SessionVerificationService = FakeSessionVerificationService(), - dateFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE), + dateFormatter: DateFormatter = FakeDateFormatter(), ) = IncomingVerificationPresenter( sessionVerificationRequestDetails = sessionVerificationRequestDetails, navigator = navigator, diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index 12aa5c4bfe..22a0c518ec 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -7,6 +7,8 @@ package io.element.android.libraries.core.extensions +import java.util.Locale + fun Boolean.toOnOff() = if (this) "ON" else "OFF" fun Boolean.to01() = if (this) "1" else "0" @@ -68,3 +70,16 @@ fun String.replacePrefix(oldPrefix: String, newPrefix: String): String { fun String.withBrackets(prefix: String = "(", suffix: String = ")"): String { return "$prefix$this$suffix" } + +/** + * Capitalize the string. + */ +fun String.safeCapitalize(): String { + return replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase(Locale.getDefault()) + } else { + it.toString() + } + } +} diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt new file mode 100644 index 0000000000..4475ced912 --- /dev/null +++ b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.api + +interface DateFormatter { + fun format( + timestamp: Long?, + mode: DateFormatterMode = DateFormatterMode.Full, + useRelative: Boolean = false, + ): String +} + +enum class DateFormatterMode { + Full, + Month, + Day, + // Time if same day, else date + TimeOrDate, + // Only time whatever the day + TimeOnly, +} diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DaySeparatorFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DaySeparatorFormatter.kt deleted file mode 100644 index 4cc35218a0..0000000000 --- a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DaySeparatorFormatter.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.dateformatter.api - -interface DaySeparatorFormatter { - fun format(timestamp: Long): String -} diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/LastMessageTimestampFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/LastMessageTimestampFormatter.kt deleted file mode 100644 index c5b9778669..0000000000 --- a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/LastMessageTimestampFormatter.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.dateformatter.api - -fun interface LastMessageTimestampFormatter { - fun format(timestamp: Long?): String -} diff --git a/libraries/dateformatter/impl/build.gradle.kts b/libraries/dateformatter/impl/build.gradle.kts index eb05eb18e0..e814a1e2b8 100644 --- a/libraries/dateformatter/impl/build.gradle.kts +++ b/libraries/dateformatter/impl/build.gradle.kts @@ -16,15 +16,29 @@ setupAnvil() android { namespace = "io.element.android.libraries.dateformatter.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } + dependencies { implementation(libs.dagger) + implementation(projects.libraries.core) implementation(projects.libraries.di) + implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) api(projects.libraries.dateformatter.api) api(libs.datetime) testImplementation(libs.test.junit) testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(libs.test.robolectric) testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.services.toolbox.test) + testImplementation(projects.tests.testutils) + testImplementation(libs.androidx.compose.ui.test.junit) } } diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt new file mode 100644 index 0000000000..2f34d480e0 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.safeCapitalize +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +interface DateFormatterDay { + fun format( + timestamp: Long, + useRelative: Boolean, + ): String +} + +@ContributesBinding(AppScope::class) +class DefaultDateFormatterDay @Inject constructor( + private val localDateTimeProvider: LocalDateTimeProvider, + private val dateFormatters: DateFormatters, +) : DateFormatterDay { + override fun format( + timestamp: Long, + useRelative: Boolean, + ): String { + val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) + val today = localDateTimeProvider.providesNow() + return if (useRelative) { + val dayDiff = today.date.toEpochDays() - dateToFormat.date.toEpochDays() + when (dayDiff) { + 0 -> dateFormatters.getRelativeDay(timestamp, "Today") + 1 -> dateFormatters.getRelativeDay(timestamp, "Yesterday") + else -> if (dayDiff < 7) { + dateFormatters.formatDateWithDay(dateToFormat) + } else { + if (today.year == dateToFormat.year) { + dateFormatters.formatDateWithFullFormatNoYear(dateToFormat) + } else { + dateFormatters.formatDateWithFullFormat(dateToFormat) + } + } + } + } else { + if (today.year == dateToFormat.year) { + dateFormatters.formatDateWithFullFormatNoYear(dateToFormat) + } else { + dateFormatters.formatDateWithFullFormat(dateToFormat) + } + } + .safeCapitalize() + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt new file mode 100644 index 0000000000..80e613e38e --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import io.element.android.services.toolbox.api.strings.StringProvider +import javax.inject.Inject + +class DateFormatterFull @Inject constructor( + private val stringProvider: StringProvider, + private val localDateTimeProvider: LocalDateTimeProvider, + private val dateFormatters: DateFormatters, + private val dateFormatterDay: DateFormatterDay, +) { + fun format( + timestamp: Long, + useRelative: Boolean, + ): String { + val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) + val time = dateFormatters.formatTime(dateToFormat) + return if (useRelative) { + val now = localDateTimeProvider.providesNow() + if (now.date == dateToFormat.date) { + time + } else { + val dateStr = dateFormatterDay.format(timestamp, true) + stringProvider.getString(R.string.common_date_date_at_time, dateStr, time) + } + } else { + val dateStr = dateFormatters.formatDateWithFullFormat(dateToFormat) + stringProvider.getString(R.string.common_date_date_at_time, dateStr, time) + } + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt new file mode 100644 index 0000000000..3d56ebcea1 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import io.element.android.libraries.core.extensions.safeCapitalize +import io.element.android.services.toolbox.api.strings.StringProvider +import javax.inject.Inject + +class DateFormatterMonth @Inject constructor( + private val stringProvider: StringProvider, + private val localDateTimeProvider: LocalDateTimeProvider, + private val dateFormatters: DateFormatters, +) { + fun format( + timestamp: Long, + useRelative: Boolean, + ): String { + val today = localDateTimeProvider.providesNow() + val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) + return if (useRelative && dateToFormat.month == today.month && dateToFormat.year == today.year) { + stringProvider.getString(R.string.common_date_this_month) + } else { + dateFormatters.formatDateWithMonthAndYear(dateToFormat) + } + .safeCapitalize() + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt similarity index 62% rename from libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt rename to libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt index 8c34905836..b0ad28fdcf 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright 2024 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only * Please see LICENSE in the repository root for full details. @@ -7,18 +7,16 @@ package io.element.android.libraries.dateformatter.impl -import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter -import io.element.android.libraries.di.AppScope import javax.inject.Inject -@ContributesBinding(AppScope::class) -class DefaultLastMessageTimestampFormatter @Inject constructor( +class DateFormatterTime @Inject constructor( private val localDateTimeProvider: LocalDateTimeProvider, private val dateFormatters: DateFormatters, -) : LastMessageTimestampFormatter { - override fun format(timestamp: Long?): String { - if (timestamp == null) return "" +) { + fun format( + timestamp: Long, + useRelative: Boolean, + ): String { val currentDate = localDateTimeProvider.providesNow() val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) val isSameDay = currentDate.date == dateToFormat.date @@ -30,7 +28,7 @@ class DefaultLastMessageTimestampFormatter @Inject constructor( dateFormatters.formatDate( dateToFormat = dateToFormat, currentDate = currentDate, - useRelative = true + useRelative = useRelative, ) } } diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt new file mode 100644 index 0000000000..ce412f0d43 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import javax.inject.Inject + +class DateFormatterTimeOnly @Inject constructor( + private val localDateTimeProvider: LocalDateTimeProvider, + private val dateFormatters: DateFormatters, +) { + fun format( + timestamp: Long, + ): String { + val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) + return dateFormatters.formatTime(dateToFormat) + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt index a78cc81c24..e2637b5613 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt @@ -7,57 +7,63 @@ package io.element.android.libraries.dateformatter.impl -import android.text.format.DateFormat import android.text.format.DateUtils +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn import kotlinx.datetime.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.toInstant import kotlinx.datetime.toJavaLocalDate import kotlinx.datetime.toJavaLocalDateTime +import timber.log.Timber import java.time.Period -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle import java.util.Locale import javax.inject.Inject import kotlin.math.absoluteValue +@SingleIn(AppScope::class) class DateFormatters @Inject constructor( - private val locale: Locale, + localeChangeObserver: LocaleChangeObserver, private val clock: Clock, private val timeZoneProvider: TimezoneProvider, -) { - private val onlyTimeFormatter: DateTimeFormatter by lazy { - DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale) +) : LocaleChangeListener { + init { + localeChangeObserver.addListener(this) } - private val dateWithMonthFormatter: DateTimeFormatter by lazy { - val pattern = DateFormat.getBestDateTimePattern(locale, "d MMM") ?: "d MMM" - DateTimeFormatter.ofPattern(pattern, locale) - } + private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(Locale.getDefault()) - private val dateWithYearFormatter: DateTimeFormatter by lazy { - val pattern = DateFormat.getBestDateTimePattern(locale, "dd.MM.yyyy") ?: "dd.MM.yyyy" - DateTimeFormatter.ofPattern(pattern, locale) + override fun onLocaleChange() { + Timber.w("Locale changed, updating formatters") + dateTimeFormatters = DateTimeFormatters(Locale.getDefault()) } - private val dateWithFullFormatFormatter: DateTimeFormatter by lazy { - DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale) + internal fun formatTime(localDateTime: LocalDateTime): String { + return dateTimeFormatters.onlyTimeFormatter.format(localDateTime.toJavaLocalDateTime()) } - internal fun formatTime(localDateTime: LocalDateTime): String { - return onlyTimeFormatter.format(localDateTime.toJavaLocalDateTime()) + internal fun formatDateWithMonthAndYear(localDateTime: LocalDateTime): String { + return dateTimeFormatters.dateWithMonthAndYearFormatter.format(localDateTime.toJavaLocalDateTime()) } internal fun formatDateWithMonth(localDateTime: LocalDateTime): String { - return dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime()) + return dateTimeFormatters.dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime()) + } + + internal fun formatDateWithDay(localDateTime: LocalDateTime): String { + return dateTimeFormatters.dateWithDayFormatter.format(localDateTime.toJavaLocalDateTime()) } internal fun formatDateWithYear(localDateTime: LocalDateTime): String { - return dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime()) + return dateTimeFormatters.dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime()) } internal fun formatDateWithFullFormat(localDateTime: LocalDateTime): String { - return dateWithFullFormatFormatter.format(localDateTime.toJavaLocalDateTime()) + return dateTimeFormatters.dateWithFullFormatFormatter.format(localDateTime.toJavaLocalDateTime()) + } + + internal fun formatDateWithFullFormatNoYear(localDateTime: LocalDateTime): String { + return dateTimeFormatters.dateWithFullFormatNoYearFormatter.format(localDateTime.toJavaLocalDateTime()) } internal fun formatDate( @@ -75,12 +81,12 @@ class DateFormatters @Inject constructor( } } - private fun getRelativeDay(ts: Long): String { + internal fun getRelativeDay(ts: Long, default: String = ""): String { return DateUtils.getRelativeTimeSpanString( ts, clock.now().toEpochMilliseconds(), DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_SHOW_WEEKDAY - )?.toString() ?: "" + )?.toString() ?: default } } diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt new file mode 100644 index 0000000000..15dc6aa05e --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import android.text.format.DateFormat +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale + +class DateTimeFormatters( + private val locale: Locale, +) { + val onlyTimeFormatter: DateTimeFormatter by lazy { + DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale) + } + + val dateWithMonthAndYearFormatter: DateTimeFormatter by lazy { + val pattern = bestDateTimePattern("MMMM YYYY") + DateTimeFormatter.ofPattern(pattern, locale) + } + + val dateWithMonthFormatter: DateTimeFormatter by lazy { + val pattern = bestDateTimePattern("d MMM") + DateTimeFormatter.ofPattern(pattern, locale) + } + + val dateWithDayFormatter: DateTimeFormatter by lazy { + val pattern = bestDateTimePattern("EEEE") + DateTimeFormatter.ofPattern(pattern, locale) + } + + val dateWithYearFormatter: DateTimeFormatter by lazy { + val pattern = bestDateTimePattern("dd.MM.yyyy") + DateTimeFormatter.ofPattern(pattern, locale) + } + + val dateWithFullFormatFormatter: DateTimeFormatter by lazy { + DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(locale) + } + + val dateWithFullFormatNoYearFormatter: DateTimeFormatter by lazy { + val pattern = DateFormat.getBestDateTimePattern(locale, "EEEE d MMMM") ?: "EEEE d MMMM" + DateTimeFormatter.ofPattern(pattern, locale) + } + + private fun bestDateTimePattern(pattern: String): String { + return DateFormat.getBestDateTimePattern(locale, pattern) ?: pattern + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt new file mode 100644 index 0000000000..7497f8ee45 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultDateFormatter @Inject constructor( + private val dateFormatterFull: DateFormatterFull, + private val dateFormatterMonth: DateFormatterMonth, + private val dateFormatterDay: DateFormatterDay, + private val dateFormatterTime: DateFormatterTime, + private val dateFormatterTimeOnly: DateFormatterTimeOnly, +) : DateFormatter { + override fun format( + timestamp: Long?, + mode: DateFormatterMode, + useRelative: Boolean, + ): String { + timestamp ?: return "" + return when (mode) { + DateFormatterMode.Full -> { + dateFormatterFull.format(timestamp, useRelative) + } + DateFormatterMode.Month -> { + dateFormatterMonth.format(timestamp, useRelative) + } + DateFormatterMode.Day -> { + dateFormatterDay.format(timestamp, useRelative) + } + DateFormatterMode.TimeOrDate -> { + dateFormatterTime.format(timestamp, useRelative) + } + DateFormatterMode.TimeOnly -> { + dateFormatterTimeOnly.format(timestamp) + } + } + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt deleted file mode 100644 index 89ef9ee412..0000000000 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.dateformatter.impl - -import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter -import io.element.android.libraries.di.AppScope -import javax.inject.Inject - -@ContributesBinding(AppScope::class) -class DefaultDaySeparatorFormatter @Inject constructor( - private val localDateTimeProvider: LocalDateTimeProvider, - private val dateFormatters: DateFormatters, -) : DaySeparatorFormatter { - override fun format(timestamp: Long): String { - val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) - // TODO use relative formatting once iOS uses it too - return dateFormatters.formatDateWithFullFormat(dateToFormat) - } -} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt new file mode 100644 index 0000000000..e89bfe7a99 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.di.SingleIn +import javax.inject.Inject + +fun interface LocaleChangeObserver { + fun addListener(listener: LocaleChangeListener) +} + +interface LocaleChangeListener { + fun onLocaleChange() +} + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultLocaleChangeObserver @Inject constructor( + @ApplicationContext private val context: Context, +) : LocaleChangeObserver { + init { + registerReceiver(object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + listeners.forEach(LocaleChangeListener::onLocaleChange) + } + }) + } + + private val listeners = mutableSetOf() + + override fun addListener(listener: LocaleChangeListener) { + listeners.add(listener) + } + + private fun registerReceiver(receiver: BroadcastReceiver) { + val filter = IntentFilter() + filter.addAction(Intent.ACTION_LOCALE_CHANGED) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + filter.addAction(Intent.ACTION_APPLICATION_LOCALE_CHANGED) + } + context.registerReceiver(receiver, filter) + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt index 568bee5378..3c409a977f 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt @@ -14,7 +14,6 @@ import io.element.android.libraries.dateformatter.impl.TimezoneProvider import io.element.android.libraries.di.AppScope import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone -import java.util.Locale @Module @ContributesTo(AppScope::class) @@ -22,9 +21,6 @@ object DateFormatterModule { @Provides fun providesClock(): Clock = Clock.System - @Provides - fun providesLocale(): Locale = Locale.getDefault() - @Provides fun providesTimezone(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() } } diff --git a/libraries/dateformatter/impl/src/main/res/values-fr/translations.xml b/libraries/dateformatter/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..f263536767 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s à %2$s" + "Ce mois-ci" + diff --git a/libraries/dateformatter/impl/src/main/res/values/localazy.xml b/libraries/dateformatter/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..8b0dab8cff --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values/localazy.xml @@ -0,0 +1,5 @@ + + + "%1$s at %2$s" + "This month" + diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt new file mode 100644 index 0000000000..6301698406 --- /dev/null +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt @@ -0,0 +1,277 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.dateformatter.api.DateFormatterMode +import io.element.android.libraries.dateformatter.test.FakeClock +import io.element.android.tests.testutils.InstrumentationStringProvider +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(qualifiers = "fr") +class DefaultDateFormatterFrTest { + @Test + fun `test null`() { + val now = "1980-04-06T18:35:24.00Z" + val ts: Long? = null + val formatter = createFormatter(now) + assertThat(formatter.format(ts)).isEmpty() + } + + @Test + fun `test epoch`() { + val now = "1980-04-06T18:35:24.00Z" + val ts = 0L + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("1 janvier 1970 à 00:00") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Janvier 1970") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("1 janvier 1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("01.01.1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("00:00") + } + + @Test + fun `test epoch relative`() { + val now = "1980-04-06T18:35:24.00Z" + val ts = 0L + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("1 janvier 1970 à 00:00") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Janvier 1970") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("1 janvier 1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("01.01.1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("00:00") + } + + @Test + fun `test now`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:35") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35") + } + + @Test + fun `test now relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:35") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") + } + + @Test + fun `test one second before`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:23.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:35") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35") + } + + @Test + fun `test one second before relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:23.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:35") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") + } + + @Test + fun `test one minute before`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:34:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:34") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:34") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:34") + } + + @Test + fun `test one minute before relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:34:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:34") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:34") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:34") + } + + @Test + fun `test one hour before`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T17:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 17:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("17:35") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("17:35") + } + + @Test + fun `test one hour before relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T17:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("17:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("17:35") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("17:35") + } + + @Test + fun `test one day before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-05T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("5 avril 1980 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Samedi 5 avril") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5 avr.") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35") + } + + @Test + fun `test one day before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-05T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Hier à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Hier") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Hier") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") + } + + @Test + fun `test one month before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-03-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 mars 1980 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Mars 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Jeudi 6 mars") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6 mars") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35") + } + + @Test + fun `test one month before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-03-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Jeudi 6 mars à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Mars 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Jeudi 6 mars") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6 mars") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") + } + + @Test + fun `test one year before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1979-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1979 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1979") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("6 avril 1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("06.04.1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35") + } + + @Test + fun `test one year before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1979-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6 avril 1979 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Avril 1979") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("6 avril 1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06.04.1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") + } + + /** + * Create DefaultLastMessageFormatter and set current time to the provided date. + */ + private fun createFormatter(@Suppress("SameParameterValue") currentDate: String): DefaultDateFormatter { + val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) } + val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } + val dateFormatters = DateFormatters( + localeChangeObserver = {}, + clock = clock, + timeZoneProvider = { TimeZone.UTC }, + ) + val stringProvider = InstrumentationStringProvider() + val dateFormatterDay = DefaultDateFormatterDay( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ) + return DefaultDateFormatter( + dateFormatterFull = DateFormatterFull( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + dateFormatterDay = dateFormatterDay, + ), + dateFormatterMonth = DateFormatterMonth( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterDay = dateFormatterDay, + dateFormatterTime = DateFormatterTime( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterTimeOnly = DateFormatterTimeOnly( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + ) + } +} diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt new file mode 100644 index 0000000000..57db7bc260 --- /dev/null +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt @@ -0,0 +1,277 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.dateformatter.api.DateFormatterMode +import io.element.android.libraries.dateformatter.test.FakeClock +import io.element.android.tests.testutils.InstrumentationStringProvider +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(qualifiers = "en") +class DefaultDateFormatterTest { + @Test + fun `test null`() { + val now = "1980-04-06T18:35:24.00Z" + val ts: Long? = null + val formatter = createFormatter(now) + assertThat(formatter.format(ts)).isEmpty() + } + + @Test + fun `test epoch`() { + val now = "1980-04-06T18:35:24.00Z" + val ts = 0L + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("January 1, 1970 at 12:00 AM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("January 1970") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("January 1, 1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("01.01.1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("12:00 AM") + } + + @Test + fun `test epoch relative`() { + val now = "1980-04-06T18:35:24.00Z" + val ts = 0L + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("January 1, 1970 at 12:00 AM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("January 1970") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("January 1, 1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("01.01.1970") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("12:00 AM") + } + + @Test + fun `test now`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM") + } + + @Test + fun `test now relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") + } + + @Test + fun `test one second before`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:23.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM") + } + + @Test + fun `test one second before relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:35:23.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") + } + + @Test + fun `test one minute before`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:34:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:34 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:34 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:34 PM") + } + + @Test + fun `test one minute before relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T18:34:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:34 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:34 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:34 PM") + } + + @Test + fun `test one hour before`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T17:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 5:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("5:35 PM") + } + + @Test + fun `test one hour before relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-06T17:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("5:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("5:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("5:35 PM") + } + + @Test + fun `test one day before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-05T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 5, 1980 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Saturday 5 April") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5 Apr") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM") + } + + @Test + fun `test one day before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-05T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Yesterday at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Yesterday") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Yesterday") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") + } + + @Test + fun `test one month before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-03-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("March 6, 1980 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("March 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Thursday 6 March") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6 Mar") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM") + } + + @Test + fun `test one month before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-03-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Thursday 6 March at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("March 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Thursday 6 March") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6 Mar") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") + } + + @Test + fun `test one year before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1979-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1979 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1979") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("April 6, 1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("06.04.1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM") + } + + @Test + fun `test one year before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1979-04-06T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("April 6, 1979 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("April 1979") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("April 6, 1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06.04.1979") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") + } + + /** + * Create DefaultLastMessageFormatter and set current time to the provided date. + */ + private fun createFormatter(@Suppress("SameParameterValue") currentDate: String): DefaultDateFormatter { + val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) } + val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } + val dateFormatters = DateFormatters( + localeChangeObserver = {}, + clock = clock, + timeZoneProvider = { TimeZone.UTC }, + ) + val stringProvider = InstrumentationStringProvider() + val dateFormatterDay = DefaultDateFormatterDay( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ) + return DefaultDateFormatter( + dateFormatterFull = DateFormatterFull( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + dateFormatterDay = dateFormatterDay, + ), + dateFormatterMonth = DateFormatterMonth( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterDay = dateFormatterDay, + dateFormatterTime = DateFormatterTime( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterTimeOnly = DateFormatterTimeOnly( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + ) + } +} diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt deleted file mode 100644 index 5c8de4462b..0000000000 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.dateformatter.impl - -import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter -import io.element.android.libraries.dateformatter.test.FakeClock -import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime -import org.junit.Test -import java.util.Locale - -class DefaultLastMessageTimestampFormatterTest { - @Test - fun `test null`() { - val now = "1980-04-06T18:35:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(null)).isEmpty() - } - - @Test - fun `test epoch`() { - val now = "1980-04-06T18:35:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(0)).isEqualTo("01.01.1970") - } - - @Test - fun `test now`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1980-04-06T18:35:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6:35 PM") - } - - @Test - fun `test one second before`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1980-04-06T18:35:23.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6:35 PM") - } - - @Test - fun `test one minute before`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1980-04-06T18:34:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6:34 PM") - } - - @Test - fun `test one hour before`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1980-04-06T17:35:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("5:35 PM") - } - - @Test - fun `test one day before same time`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1980-04-05T18:35:24.00Z" - val formatter = createFormatter(now) - // TODO DateUtils.getRelativeTimeSpanString returns null. - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("") - } - - @Test - fun `test one month before same time`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1980-03-06T18:35:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6 Mar") - } - - @Test - fun `test one year before same time`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1979-04-06T18:35:24.00Z" - val formatter = createFormatter(now) - assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("06.04.1979") - } - - @Test - fun `test full format`() { - val now = "1980-04-06T18:35:24.00Z" - val dat = "1979-04-06T18:35:24.00Z" - val clock = FakeClock().apply { givenInstant(Instant.parse(now)) } - val dateFormatters = DateFormatters(Locale.US, clock) { TimeZone.UTC } - assertThat(dateFormatters.formatDateWithFullFormat(Instant.parse(dat).toLocalDateTime(TimeZone.UTC))).isEqualTo("Friday, April 6, 1979") - } - - /** - * Create DefaultLastMessageFormatter and set current time to the provided date. - */ - private fun createFormatter(@Suppress("SameParameterValue") currentDate: String): LastMessageTimestampFormatter { - val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) } - val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } - val dateFormatters = DateFormatters(Locale.US, clock) { TimeZone.UTC } - return DefaultLastMessageTimestampFormatter(localDateTimeProvider, dateFormatters) - } -} diff --git a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt new file mode 100644 index 0000000000..722e43f2c9 --- /dev/null +++ b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.test + +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode + +class FakeDateFormatter( + private val formatLambda: (Long?, DateFormatterMode, Boolean) -> String = { timestamp, mode, useRelative -> + "$timestamp $mode $useRelative" + }, +) : DateFormatter { + override fun format( + timestamp: Long?, + mode: DateFormatterMode, + useRelative: Boolean, + ): String { + return formatLambda(timestamp, mode, useRelative) + } +} diff --git a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt deleted file mode 100644 index 529d884809..0000000000 --- a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.dateformatter.test - -import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter - -class FakeDaySeparatorFormatter : DaySeparatorFormatter { - private var format = "" - - fun givenFormat(format: String) { - this.format = format - } - - override fun format(timestamp: Long): String { - return format - } -} diff --git a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeLastMessageTimestampFormatter.kt b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeLastMessageTimestampFormatter.kt deleted file mode 100644 index 7edcf321cb..0000000000 --- a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeLastMessageTimestampFormatter.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.dateformatter.test - -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter - -const val A_FORMATTED_DATE = "formatted_date" - -class FakeLastMessageTimestampFormatter( - var format: String = "", -) : LastMessageTimestampFormatter { - fun givenFormat(format: String) { - this.format = format - } - - override fun format(timestamp: Long?): String { - return format - } -} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index c84a37cdc3..daf90d6356 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -235,7 +235,7 @@ class RustMatrixRoom( RoomMessageEventMessageType.VIDEO, RoomMessageEventMessageType.AUDIO, ), - dateDividerMode = DateDividerMode.DAILY, + dateDividerMode = DateDividerMode.MONTHLY, ).let { inner -> createTimeline(inner, mode = Timeline.Mode.MEDIA) } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index 17a1052954..7daa5ab7ef 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -23,6 +23,7 @@ data class MediaInfo( val senderName: String?, val senderAvatar: String?, val dateSent: String?, + val dateSentFull: String?, ) : Parcelable fun anImageMediaInfo( @@ -30,6 +31,7 @@ fun anImageMediaInfo( caption: String? = null, senderName: String? = null, dateSent: String? = null, + dateSentFull: String? = null, ): MediaInfo = MediaInfo( filename = "an image file.jpg", caption = caption, @@ -40,12 +42,14 @@ fun anImageMediaInfo( senderName = senderName, senderAvatar = null, dateSent = dateSent, + dateSentFull = dateSentFull, ) fun aVideoMediaInfo( caption: String? = null, senderName: String? = null, dateSent: String? = null, + dateSentFull: String? = null, ): MediaInfo = MediaInfo( filename = "a video file.mp4", caption = caption, @@ -56,6 +60,7 @@ fun aVideoMediaInfo( senderName = senderName, senderAvatar = null, dateSent = dateSent, + dateSentFull = dateSentFull, ) fun aPdfMediaInfo( @@ -63,6 +68,7 @@ fun aPdfMediaInfo( caption: String? = null, senderName: String? = null, dateSent: String? = null, + dateSentFull: String? = null, ): MediaInfo = MediaInfo( filename = filename, caption = caption, @@ -73,12 +79,14 @@ fun aPdfMediaInfo( senderName = senderName, senderAvatar = null, dateSent = dateSent, + dateSentFull = dateSentFull, ) fun anApkMediaInfo( senderId: UserId? = UserId("@alice:server.org"), senderName: String? = null, dateSent: String? = null, + dateSentFull: String? = null, ): MediaInfo = MediaInfo( filename = "an apk file.apk", caption = null, @@ -89,11 +97,13 @@ fun anApkMediaInfo( senderName = senderName, senderAvatar = null, dateSent = dateSent, + dateSentFull = dateSentFull, ) fun anAudioMediaInfo( senderName: String? = null, dateSent: String? = null, + dateSentFull: String? = null, ): MediaInfo = MediaInfo( filename = "an audio file.mp3", caption = null, @@ -104,4 +114,5 @@ fun anAudioMediaInfo( senderName = senderName, senderAvatar = null, dateSent = dateSent, + dateSentFull = dateSentFull, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt index f9611a7023..d85bf08b8e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt @@ -53,6 +53,7 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint senderName = null, senderAvatar = null, dateSent = null, + dateSentFull = null, ), mediaSource = MediaSource(url = avatarUrl), thumbnailSource = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt index a11abe945b..42127db229 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt @@ -71,7 +71,7 @@ fun MediaDetailsBottomSheet( } SectionText( title = stringResource(R.string.screen_media_details_uploaded_on), - text = state.mediaInfo.dateSent.orEmpty(), + text = state.mediaInfo.dateSentFull.orEmpty(), ) SectionText( title = stringResource(R.string.screen_media_details_filename), diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt index 880fcb2b91..5957fd480f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt @@ -10,12 +10,15 @@ package io.element.android.libraries.mediaviewer.impl.details import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.mediaviewer.api.anImageMediaInfo -fun aMediaDetailsBottomSheetState(): MediaBottomSheetState.MediaDetailsBottomSheetState { +fun aMediaDetailsBottomSheetState( + dateSentFull: String = "December 6, 2024 at 12:59", +): MediaBottomSheetState.MediaDetailsBottomSheetState { return MediaBottomSheetState.MediaDetailsBottomSheetState( eventId = EventId("\$eventId"), canDelete = true, mediaInfo = anImageMediaInfo( senderName = "Alice", + dateSentFull = dateSentFull, ), thumbnailSource = null, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index 6b96500149..8fcea07f52 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -8,7 +8,8 @@ package io.element.android.libraries.mediaviewer.impl.gallery import io.element.android.libraries.androidutils.filesize.FileSizeFormatter -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.dateformatter.api.toHumanReadableDuration import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType @@ -45,13 +46,20 @@ import javax.inject.Inject class EventItemFactory @Inject constructor( private val fileSizeFormatter: FileSizeFormatter, private val fileExtensionExtractor: FileExtensionExtractor, - private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, + private val dateFormatter: DateFormatter, ) { fun create( currentTimelineItem: MatrixTimelineItem.Event, ): MediaItem.Event? { val event = currentTimelineItem.event - val sentTime = lastMessageTimestampFormatter.format(currentTimelineItem.event.timestamp) + val dateSent = dateFormatter.format( + currentTimelineItem.event.timestamp, + mode = DateFormatterMode.Day, + ) + val dateSentFull = dateFormatter.format( + timestamp = currentTimelineItem.event.timestamp, + mode = DateFormatterMode.Full, + ) return when (val content = event.content) { CallNotifyContent, is FailedToParseMessageLikeContent, @@ -90,7 +98,8 @@ class EventItemFactory @Inject constructor( senderId = event.sender, senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), senderAvatar = event.senderProfile.getAvatarUrl(), - dateSent = sentTime, + dateSent = dateSent, + dateSentFull = dateSentFull, ), mediaSource = type.source, ) @@ -106,7 +115,8 @@ class EventItemFactory @Inject constructor( senderId = event.sender, senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), senderAvatar = event.senderProfile.getAvatarUrl(), - dateSent = sentTime, + dateSent = dateSent, + dateSentFull = dateSentFull, ), mediaSource = type.source, ) @@ -122,7 +132,8 @@ class EventItemFactory @Inject constructor( senderId = event.sender, senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), senderAvatar = event.senderProfile.getAvatarUrl(), - dateSent = sentTime, + dateSent = dateSent, + dateSentFull = dateSentFull, ), mediaSource = type.source, thumbnailSource = null, @@ -139,7 +150,8 @@ class EventItemFactory @Inject constructor( senderId = event.sender, senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), senderAvatar = event.senderProfile.getAvatarUrl(), - dateSent = sentTime, + dateSent = dateSent, + dateSentFull = dateSentFull, ), mediaSource = type.source, thumbnailSource = null, @@ -156,7 +168,8 @@ class EventItemFactory @Inject constructor( senderId = event.sender, senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), senderAvatar = event.senderProfile.getAvatarUrl(), - dateSent = sentTime, + dateSent = dateSent, + dateSentFull = dateSentFull, ), mediaSource = type.source, thumbnailSource = type.info?.thumbnailSource, @@ -174,7 +187,8 @@ class EventItemFactory @Inject constructor( senderId = event.sender, senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender), senderAvatar = event.senderProfile.getAvatarUrl(), - dateSent = sentTime, + dateSent = dateSent, + dateSentFull = dateSentFull, ), mediaSource = type.source, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt index 22d5ef546b..df0976b468 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/VirtualItemFactory.kt @@ -7,19 +7,24 @@ package io.element.android.libraries.mediaviewer.impl.gallery -import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter +import io.element.android.libraries.dateformatter.api.DateFormatter +import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import javax.inject.Inject class VirtualItemFactory @Inject constructor( - private val daySeparatorFormatter: DaySeparatorFormatter, + private val dateFormatter: DateFormatter, ) { fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? { return when (val virtual = timelineItem.virtual) { is VirtualTimelineItem.DayDivider -> MediaItem.DateSeparator( id = timelineItem.uniqueId, - formattedDate = daySeparatorFormatter.format(virtual.timestamp) + formattedDate = dateFormatter.format( + timestamp = virtual.timestamp, + mode = DateFormatterMode.Month, + useRelative = true, + ) ) VirtualTimelineItem.LastForwardIndicator -> null is VirtualTimelineItem.LoadingIndicator -> MediaItem.LoadingIndicator( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index 62706f120e..c17d613e55 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -46,6 +46,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderName = mediaInfo.senderName, senderAvatar = mediaInfo.senderAvatar, dateSent = mediaInfo.dateSent, + dateSentFull = mediaInfo.dateSentFull, ) override fun createFromUri( @@ -63,6 +64,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderName = null, senderAvatar = null, dateSent = null, + dateSentFull = null, ) private fun createFromUri( @@ -75,6 +77,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderName: String?, senderAvatar: String?, dateSent: String?, + dateSentFull: String?, ): LocalMedia { val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream val fileName = name ?: context.getFileName(uri) ?: "" @@ -92,6 +95,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderName = senderName, senderAvatar = senderAvatar, dateSent = dateSent, + dateSentFull = dateSentFull, ) ) } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt index 3dde8176b4..36b767e870 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -10,8 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes -import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.matrix.api.media.AudioDetails import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo @@ -162,7 +161,8 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = A_FORMATTED_DATE, + dateSent = "0 Day false", + dateSentFull = "0 Full false", ), mediaSource = MediaSource(""), ) @@ -209,7 +209,8 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = A_FORMATTED_DATE, + dateSent = "0 Day false", + dateSentFull = "0 Full false", ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -253,7 +254,8 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = A_FORMATTED_DATE, + dateSent = "0 Day false", + dateSentFull = "0 Full false", ), mediaSource = MediaSource(""), ) @@ -301,7 +303,8 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = A_FORMATTED_DATE, + dateSent = "0 Day false", + dateSentFull = "0 Full false", ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -350,7 +353,8 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = A_FORMATTED_DATE, + dateSent = "0 Day false", + dateSentFull = "0 Full false", ), mediaSource = MediaSource(""), ) @@ -397,7 +401,8 @@ class DefaultEventItemFactoryTest { senderId = A_USER_ID, senderName = "alice", senderAvatar = null, - dateSent = A_FORMATTED_DATE, + dateSent = "0 Day false", + dateSentFull = "0 Full false", ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -409,5 +414,5 @@ class DefaultEventItemFactoryTest { private fun createEventItemFactory() = EventItemFactory( fileSizeFormatter = FakeFileSizeFormatter(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), - lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE), + dateFormatter = FakeDateFormatter(), ) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index 4aeada8701..8eeaef976c 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -10,9 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.net.Uri import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter -import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE -import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter -import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter +import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -254,12 +252,12 @@ class MediaGalleryPresenterTest { timelineMediaItemsFactory = TimelineMediaItemsFactory( dispatchers = testCoroutineDispatchers(), virtualItemFactory = VirtualItemFactory( - daySeparatorFormatter = FakeDaySeparatorFormatter(), + dateFormatter = FakeDateFormatter(), ), eventItemFactory = EventItemFactory( fileSizeFormatter = FakeFileSizeFormatter(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), - lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE), + dateFormatter = FakeDateFormatter(), ), ), localMediaFactory = localMediaFactory, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index f60c43572e..d1f0f745f8 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -27,11 +27,15 @@ class AndroidLocalMediaFactoryTest { @Test fun `test AndroidLocalMediaFactory`() { val sut = createAndroidLocalMediaFactory() - val result = sut.createFromMediaFile(aMediaFile(), anImageMediaInfo( - senderId = A_USER_ID, - senderName = A_USER_NAME, - dateSent = "12:34", - )) + val result = sut.createFromMediaFile( + mediaFile = aMediaFile(), + mediaInfo = anImageMediaInfo( + senderId = A_USER_ID, + senderName = A_USER_NAME, + dateSent = "12:34", + dateSentFull = "full", + ) + ) assertThat(result.uri.toString()).endsWith("aPath") assertThat(result.info).isEqualTo( MediaInfo( @@ -43,7 +47,8 @@ class AndroidLocalMediaFactoryTest { senderId = A_USER_ID, senderName = A_USER_NAME, senderAvatar = null, - dateSent = "12:34" + dateSent = "12:34", + dateSentFull = "full" ) ) } diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index c41435afc0..39014f90cb 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -40,7 +40,8 @@ class FakeLocalMediaFactory( senderId = null, senderName = null, senderAvatar = null, - dateSent = null + dateSent = null, + dateSentFull = null, ) return aLocalMedia(uri, mediaInfo) } diff --git a/tests/testutils/build.gradle.kts b/tests/testutils/build.gradle.kts index ce9698ab3e..7d9fa12efc 100644 --- a/tests/testutils/build.gradle.kts +++ b/tests/testutils/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.core) implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) implementation(libs.test.turbine) implementation(libs.molecule.runtime) implementation(libs.androidx.compose.ui.test.junit) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt new file mode 100644 index 0000000000..fa60e497cd --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.tests.testutils + +import androidx.test.platform.app.InstrumentationRegistry +import io.element.android.services.toolbox.api.strings.StringProvider + +class InstrumentationStringProvider : StringProvider { + private val resource = InstrumentationRegistry.getInstrumentation().context.resources + override fun getString(resId: Int): String { + return resource.getString(resId) + } + + override fun getString(resId: Int, vararg formatArgs: Any?): String { + return resource.getString(resId, *formatArgs) + } + + override fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any?): String { + return resource.getQuantityString(resId, quantity, *formatArgs) + } +} diff --git a/tools/localazy/config.json b/tools/localazy/config.json index fe2f7d3e03..2efd8eac97 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -80,6 +80,12 @@ ".*voice_message_tooltip" ] }, + { + "name" : ":libraries:dateformatter:impl", + "includeRegex" : [ + "common\\.date\\..*" + ] + }, { "name" : ":libraries:permissions:api", "includeRegex" : [ From 54975b1d685d1e08b80aa382906b18471a933ee5 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 11 Dec 2024 23:10:31 +0000 Subject: [PATCH 094/203] Update screenshots --- ...ssages.impl.actionlist_ActionListViewContent_Day_10_en.png | 4 ++-- ...ssages.impl.actionlist_ActionListViewContent_Day_11_en.png | 4 ++-- ...ssages.impl.actionlist_ActionListViewContent_Day_12_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_2_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_3_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_4_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_5_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_6_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_7_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_8_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_9_en.png | 4 ++-- ...ages.impl.actionlist_ActionListViewContent_Night_10_en.png | 4 ++-- ...ages.impl.actionlist_ActionListViewContent_Night_11_en.png | 4 ++-- ...ages.impl.actionlist_ActionListViewContent_Night_12_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_2_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_3_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_4_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_5_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_6_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_7_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_8_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_9_en.png | 4 ++-- ...iaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png | 4 ++-- ...viewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png | 4 ++-- ...ibraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png | 4 ++-- 27 files changed, 54 insertions(+), 54 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png index 0e0342e192..223d7869b6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e67a540966100272311381e87011149cdb15c8191a6f2bbc40d1febff999c431 -size 24067 +oid sha256:dbef9e8e887fa493ca9b875e64dc164bf35dd84ad25b84a1ad9b6fd523b26c38 +size 27280 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png index f500ff60dd..cc268a914f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65023f7233f112547ccd0850c321b2d6610cfb6a1371c494f68722e75874f871 -size 45915 +oid sha256:c1a0bc9c83b7e01f9492443433781509c0d899b397652fc7e9b7a539d8ce0412 +size 48219 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png index c80b8639e0..d2eec8525c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2c2e01bd133c7b84e381141b6baf22783cb585cb3af9f790785dbf8aeaeeed6 -size 47644 +oid sha256:3a624587095825c971186288a0ad5a40ddfcadc8f4b9f92b1102d8ae20ca3bda +size 49821 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png index 4088be4e1e..392c35230c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bd6fb63059cebc7dc7b850b5aa73549ab9846eda68b1b6d9a4f8e0716d42c3c -size 40419 +oid sha256:4cdcc38bfae43298654c3e09d7ab1ca1e0d2fd32157a464539a360f3943f4f75 +size 42839 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png index b3dbf77d1d..89efb1c866 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b4e4a075bcb4b95455c27ce2e5f9f1bdac4a1aca22ad944dd53fdbaeb6ca970 -size 44550 +oid sha256:a56b3e488ceecd0bfb763d60b6827f7d8c460fa198d380a502ff0d97b13c9bc0 +size 45962 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png index 87c07faab3..496cf3053b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a2bc7e9098301d17e72a61a7baf01e52aa11566e1da4ffe3b43a66fa37652b2 -size 42103 +oid sha256:600f28097c6ef8c54fe136d9bb05b8b800d693bff23f238a4e3e0216ee9918ff +size 44404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png index 25da7b8bf1..834430dfb3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f097af2773ff2ecf70a69e4292dc118b4c8616f1c8f979e7d121e86a16ef3072 -size 38506 +oid sha256:054cfe9290f91cf0f5d17e8f7c49e71eb50a2bb17067b1edc3c29f89410f76e8 +size 40806 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png index 2bf210d590..f082532ffd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f49870a5333cbaf1c6c3ac5ddcb85290d04efc79ffec93d8dc39b59f9712820 -size 42391 +oid sha256:814bb524af65d077e3610e8cb920e5207aa808111a530357e8687831bd9c0413 +size 44687 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png index 6de43e00ee..6db122607c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:883f160afa3fc3d2c0be5098205ea616875084ee0f122ebf994d0fee53a8ecd1 -size 39847 +oid sha256:4da93eb0b9fcea861ae80d6e14bc852acaacea52402b1b41e8c982b9c06b2b63 +size 42105 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png index cf03f3c12f..358a9d9a81 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7790c892daeee8910ce17caac2c957d09fffec0d41a51b3adc1d5bef8dcee1a1 -size 42261 +oid sha256:4886407cfe23d5d16aadeef4226698957dad99dfc4d9ac5c184fe64347b3ee41 +size 44524 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png index 63da19cb01..4de1129978 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc7b733680a0d86ee1231345e58641fb3a208da21de62a50f624d9ae04c6e140 -size 31661 +oid sha256:3c0d8491dbf0b6f50ae6be5c7aaf0d77b9eaa3a31e7763f120d6e08993c1c74b +size 34028 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png index 7e43e760b2..65a49cb609 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20504466241e36817557812f6b378aceab9c2e271a596bd3d037c6be41af7c54 -size 23624 +oid sha256:64a6d1a6af14176c933387a7a36de0d7392b1cd31cb04fcc5c6a282317874aac +size 26597 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png index 1667fe5ff0..89e23b0444 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48028e4c3ca7e9b871683165f029430bcd4e0fc2411e4ffc83a93abb641d96a2 -size 45132 +oid sha256:f92ebc24de4dd34b84fcbbcc656a369d4a79bca160666c2fd4d32b5516c2f8fb +size 47245 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png index e44f324015..6e575e21c5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b040fe47521f5d9d76d7892412bb2b5cf7e2b93b951f5de5f772503797fb6b24 -size 46548 +oid sha256:2a4a3730a941a4c6863c116b401ea7e4d5c2cef3fe4098e353954c4f93aa660a +size 48771 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png index 5797dc5345..053b325557 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6db046a97f14f2df3db2415d71a02ffd6e706fe73def67c21f0d844be279da59 -size 39617 +oid sha256:77521de94c04228ca0fb5d09cb7809527cf0dc0acc52a271a8fa4738a752aa66 +size 42028 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png index 9b837817e6..a3c9b39e27 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ee53243923d7b5adcc2094d3fe6bb87be00a179cae11966dcb230f2c3e8246e -size 43681 +oid sha256:171ef67bdc5a1b6d921ecbdb13e3c79dfab073d7289862181a69515e6063c4c5 +size 45246 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png index 6d0c2a905b..1374a97dd7 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b88e54ba4743e1e245b5a4c7d6b4045f816241f6cfc8716b8cede0b62666222b -size 41286 +oid sha256:1a8328afeab339104a83db16d7b4b894c060ff7e80c2b5c4d6ec64d3bc4cdc3a +size 43613 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png index c479ebef74..6f45705e5e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d88b4d82bb0d416aff7b32647479181ac9702c36c08370e7671d6e5ef80687c7 -size 37825 +oid sha256:d575d08141bf446de510162f5dc1e04fccf4831649495bdf94945bd8957768a6 +size 40201 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png index 885d6afaed..59e4356eb2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3d4dd1766a3f46acd86bdd5ec920c56a5e5f897c81c1477df10ba59dcd3d5b7 -size 41554 +oid sha256:606379fcb517afa4583ac03fcb2af4f5649a7fc064df841f442c19f5a71f629b +size 43863 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png index 13b4a76016..8225529b52 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:842fbea74e53c8804fd0a3e9fe96dcb21b5c91a099e1de33d8ec983fd9a8a80a -size 39112 +oid sha256:d63dec242ea479beb9a7bf0a58c6f31e2bba84df0a7db51fb07ea46612944d9a +size 41355 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png index 18e022f292..26f908facc 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1311ec5e008b44d81e6556164f780c57dea3e4721cc47caccdbf337dd4027eb7 -size 41402 +oid sha256:f5421b7b5ce8441213b1323b4cd88c2f5aff6689ecd091b5be581fe5690cd611 +size 43685 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png index 99c6f765a5..0e328d0740 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2cf27d2f053f33c67d944a8f45e0206165b2db2f3b959eb9e0bb943f84fe6a1 -size 30657 +oid sha256:e1f4600552aebd3a567251a85105a423882942b23e96f7958238a9b5ba0bb8fc +size 33137 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png index 55e944524e..ceac4aade2 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f3557280a4010e7ffdb6f22c11561573780c0b24c27e264073d3a3899169014 -size 29659 +oid sha256:2c7ba307cf21056623bf35c8558809fdbc6deaacf4e9365a99ffcf829e8d9188 +size 34477 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png index 33c24c3341..8a0b19ace2 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63efb9744640b71cfde672821c7c1ea8b33f9c3c2e2ad4d5f41149b9749b31c2 -size 28016 +oid sha256:724bff130c547e7f0065ccb4c1b3319162ded2e6c1c1db666f4e08e01289a5a0 +size 32776 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png index 03b462a778..319ab0eca4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:983e505a211c92ae92e091f9ba7cc43a655d5f3ce6d6bcf70971d43984507326 -size 36153 +oid sha256:25ebb7460550b31bf2cebfca7bdab32e5f89e327b68f6687bf490e5d14cb9220 +size 40950 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png index 85ef965bae..8e10c8a8aa 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e7a43195bd617ee952ea084e6b17a7e08e8b3634e8f3ce7df6e98f067ab08dc -size 34296 +oid sha256:39ecc9a50285df492417f5f22adcc391be2dcad0cc388efb756274c83aba077d +size 39030 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png index 99618a6ea0..a8777362d7 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a6916d2441316cb3ef55ee4c6a3b3ba9134d246d72c27aa3871408a6b9e59fc -size 30716 +oid sha256:2d5f183f53f9e8d0dbbae473f2f853f4372dbff15b1d6ea17e78b4770781fa34 +size 35468 From 605447ad8743d78707d40b1159e6c4517acf6971 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 00:04:55 +0100 Subject: [PATCH 095/203] Avoid code duplication between the 2 tests. --- .../impl/DefaultDateFormatterFrTest.kt | 43 --------------- .../impl/DefaultDateFormatterTest.kt | 43 --------------- .../libraries/dateformatter/impl/Factory.kt | 53 +++++++++++++++++++ 3 files changed, 53 insertions(+), 86 deletions(-) create mode 100644 libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt index 6301698406..cddfc5b8e0 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt @@ -10,10 +10,7 @@ package io.element.android.libraries.dateformatter.impl import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import io.element.android.libraries.dateformatter.api.DateFormatterMode -import io.element.android.libraries.dateformatter.test.FakeClock -import io.element.android.tests.testutils.InstrumentationStringProvider import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -234,44 +231,4 @@ class DefaultDateFormatterFrTest { assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06.04.1979") assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") } - - /** - * Create DefaultLastMessageFormatter and set current time to the provided date. - */ - private fun createFormatter(@Suppress("SameParameterValue") currentDate: String): DefaultDateFormatter { - val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) } - val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } - val dateFormatters = DateFormatters( - localeChangeObserver = {}, - clock = clock, - timeZoneProvider = { TimeZone.UTC }, - ) - val stringProvider = InstrumentationStringProvider() - val dateFormatterDay = DefaultDateFormatterDay( - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ) - return DefaultDateFormatter( - dateFormatterFull = DateFormatterFull( - stringProvider = stringProvider, - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - dateFormatterDay = dateFormatterDay, - ), - dateFormatterMonth = DateFormatterMonth( - stringProvider = stringProvider, - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ), - dateFormatterDay = dateFormatterDay, - dateFormatterTime = DateFormatterTime( - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ), - dateFormatterTimeOnly = DateFormatterTimeOnly( - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ), - ) - } } diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt index 57db7bc260..142af801ea 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt @@ -10,10 +10,7 @@ package io.element.android.libraries.dateformatter.impl import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import io.element.android.libraries.dateformatter.api.DateFormatterMode -import io.element.android.libraries.dateformatter.test.FakeClock -import io.element.android.tests.testutils.InstrumentationStringProvider import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -234,44 +231,4 @@ class DefaultDateFormatterTest { assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06.04.1979") assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") } - - /** - * Create DefaultLastMessageFormatter and set current time to the provided date. - */ - private fun createFormatter(@Suppress("SameParameterValue") currentDate: String): DefaultDateFormatter { - val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) } - val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } - val dateFormatters = DateFormatters( - localeChangeObserver = {}, - clock = clock, - timeZoneProvider = { TimeZone.UTC }, - ) - val stringProvider = InstrumentationStringProvider() - val dateFormatterDay = DefaultDateFormatterDay( - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ) - return DefaultDateFormatter( - dateFormatterFull = DateFormatterFull( - stringProvider = stringProvider, - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - dateFormatterDay = dateFormatterDay, - ), - dateFormatterMonth = DateFormatterMonth( - stringProvider = stringProvider, - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ), - dateFormatterDay = dateFormatterDay, - dateFormatterTime = DateFormatterTime( - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ), - dateFormatterTimeOnly = DateFormatterTimeOnly( - localDateTimeProvider = localDateTimeProvider, - dateFormatters = dateFormatters, - ), - ) - } } diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt new file mode 100644 index 0000000000..02993ee7d7 --- /dev/null +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl + +import io.element.android.libraries.dateformatter.test.FakeClock +import io.element.android.tests.testutils.InstrumentationStringProvider +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone + +/** + * Create DefaultDateFormatter and set current time to the provided date. + */ +fun createFormatter(currentDate: String): DefaultDateFormatter { + val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) } + val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } + val dateFormatters = DateFormatters( + localeChangeObserver = {}, + clock = clock, + timeZoneProvider = { TimeZone.UTC }, + ) + val stringProvider = InstrumentationStringProvider() + val dateFormatterDay = DefaultDateFormatterDay( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ) + return DefaultDateFormatter( + dateFormatterFull = DateFormatterFull( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + dateFormatterDay = dateFormatterDay, + ), + dateFormatterMonth = DateFormatterMonth( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterDay = dateFormatterDay, + dateFormatterTime = DateFormatterTime( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterTimeOnly = DateFormatterTimeOnly( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + ) +} From 5e0e64f9c9307f1bcb3410ae729438e1b25bf055 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 00:20:31 +0100 Subject: [PATCH 096/203] Add more tests. --- .../impl/DefaultDateFormatterFrTest.kt | 26 +++++++++++++++++++ .../impl/DefaultDateFormatterTest.kt | 26 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt index cddfc5b8e0..8dd0c61d9f 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt @@ -180,6 +180,32 @@ class DefaultDateFormatterFrTest { assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") } + @Test + fun `test two days before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-04T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("4 avril 1980 à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Vendredi 4 avril") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("4 avr.") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35") + } + + @Test + fun `test two days before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-04T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Vendredi à 18:35") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Vendredi") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("4 avr.") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35") + } + @Test fun `test one month before same time`() { val now = "1980-04-06T18:35:24.00Z" diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt index 142af801ea..b7bf9d818e 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt @@ -180,6 +180,32 @@ class DefaultDateFormatterTest { assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") } + @Test + fun `test two days before same time`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-04T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 4, 1980 at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980") + assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Friday 4 April") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("4 Apr") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM") + } + + @Test + fun `test two days before same time relative`() { + val now = "1980-04-06T18:35:24.00Z" + val dat = "1980-04-04T18:35:24.00Z" + val ts = Instant.parse(dat).toEpochMilliseconds() + val formatter = createFormatter(now) + assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Friday at 6:35 PM") + assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month") + assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Friday") + assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("4 Apr") + assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM") + } + @Test fun `test one month before same time`() { val now = "1980-04-06T18:35:24.00Z" From d4b0e37a8339f860fe020050965b137ca53d86e8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 00:20:44 +0100 Subject: [PATCH 097/203] Add doc and examples. --- .../dateformatter/api/DateFormatter.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt index 4475ced912..5632962582 100644 --- a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt +++ b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt @@ -16,11 +16,41 @@ interface DateFormatter { } enum class DateFormatterMode { + /** + * Full date and time. + * Example: + * "April 6, 1980 at 6:35 PM" + * Format can be shorter when useRelative is true. + * Example: + * "6:35 PM" + */ Full, + + /** + * Only month and year. + * Example: + * "April 1980" + * "This month" can be returned when useRelative is true. + * Example: + * "This month" + */ Month, + + /** + * Only day. + * Example: + * "Sunday 6 April" + * "Today", "Yesterday" and day of week can be returned when useRelative is true. + */ Day, - // Time if same day, else date + + /** + * Time if same day, else date. + */ TimeOrDate, - // Only time whatever the day + + /** + * Only time whatever the day. + */ TimeOnly, } From 95215369c92489f1641779c546aeaf98f274899b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 00:41:15 +0100 Subject: [PATCH 098/203] Extract UnableToDecryptInfo constructor invocation to a factory. --- .../matrix/impl/analytics/UtdTrackerTest.kt | 12 +++++----- .../fixtures/factories/UnableToDecryptInfo.kt | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt index 994c9a339c..62147d182c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt @@ -9,10 +9,10 @@ package io.element.android.libraries.matrix.impl.analytics import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Error +import io.element.android.libraries.matrix.impl.fixtures.factories.aRustUnableToDecryptInfo import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.services.analytics.test.FakeAnalyticsService import org.junit.Test -import org.matrix.rustcomponents.sdk.UnableToDecryptInfo import uniffi.matrix_sdk_crypto.UtdCause class UtdTrackerTest { @@ -21,7 +21,7 @@ class UtdTrackerTest { val fakeAnalyticsService = FakeAnalyticsService() val sut = UtdTracker(fakeAnalyticsService) sut.onUtd( - UnableToDecryptInfo( + aRustUnableToDecryptInfo( eventId = AN_EVENT_ID.value, timeToDecryptMs = null, cause = UtdCause.UNKNOWN, @@ -46,7 +46,7 @@ class UtdTrackerTest { val fakeAnalyticsService = FakeAnalyticsService() val sut = UtdTracker(fakeAnalyticsService) sut.onUtd( - UnableToDecryptInfo( + aRustUnableToDecryptInfo( eventId = AN_EVENT_ID.value, timeToDecryptMs = 123.toULong(), cause = UtdCause.UNKNOWN, @@ -71,7 +71,7 @@ class UtdTrackerTest { val fakeAnalyticsService = FakeAnalyticsService() val sut = UtdTracker(fakeAnalyticsService) sut.onUtd( - UnableToDecryptInfo( + aRustUnableToDecryptInfo( eventId = AN_EVENT_ID.value, timeToDecryptMs = 123.toULong(), cause = UtdCause.SENT_BEFORE_WE_JOINED, @@ -96,7 +96,7 @@ class UtdTrackerTest { val fakeAnalyticsService = FakeAnalyticsService() val sut = UtdTracker(fakeAnalyticsService) sut.onUtd( - UnableToDecryptInfo( + aRustUnableToDecryptInfo( eventId = AN_EVENT_ID.value, timeToDecryptMs = 123.toULong(), cause = UtdCause.UNSIGNED_DEVICE, @@ -119,7 +119,7 @@ class UtdTrackerTest { val fakeAnalyticsService = FakeAnalyticsService() val sut = UtdTracker(fakeAnalyticsService) sut.onUtd( - UnableToDecryptInfo( + aRustUnableToDecryptInfo( eventId = AN_EVENT_ID.value, timeToDecryptMs = 123.toULong(), cause = UtdCause.VERIFICATION_VIOLATION, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt new file mode 100644 index 0000000000..668fd22b30 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.fixtures.factories + +import org.matrix.rustcomponents.sdk.UnableToDecryptInfo +import uniffi.matrix_sdk_crypto.UtdCause + +internal fun aRustUnableToDecryptInfo( + eventId: String, + timeToDecryptMs: ULong?, + cause: UtdCause, +): UnableToDecryptInfo { + return UnableToDecryptInfo( + eventId = eventId, + timeToDecryptMs = timeToDecryptMs, + cause = cause, + ) +} From 307b761e710a4413850a3c932f7276ee74bb27d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 00:43:37 +0100 Subject: [PATCH 099/203] Fix compilation issue. --- .../matrix/impl/fixtures/factories/UnableToDecryptInfo.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt index 668fd22b30..4743368472 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt @@ -14,10 +14,18 @@ internal fun aRustUnableToDecryptInfo( eventId: String, timeToDecryptMs: ULong?, cause: UtdCause, + eventLocalAgeMillis: Long = 0L, + userTrustsOwnIdentity: Boolean = false, + senderHomeserver: String = "", + ownHomeserver: String = "", ): UnableToDecryptInfo { return UnableToDecryptInfo( eventId = eventId, timeToDecryptMs = timeToDecryptMs, cause = cause, + eventLocalAgeMillis = eventLocalAgeMillis, + userTrustsOwnIdentity = userTrustsOwnIdentity, + senderHomeserver = senderHomeserver, + ownHomeserver = ownHomeserver, ) } From 2cb6e8fbcc93835bca36d8e761fba1b37de9977f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 03:35:44 +0000 Subject: [PATCH 100/203] Update dependency org.maplibre.gl:android-sdk to v11.7.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 119790ca6c..6918391346 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -187,7 +187,7 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.2" -maplibre = "org.maplibre.gl:android-sdk:11.6.1" +maplibre = "org.maplibre.gl:android-sdk:11.7.0" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" opusencoder = "io.element.android:opusencoder:1.1.0" From 6550ae361ad2e20cddfbb2ee5b75b62fbc9a3bc6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 09:21:51 +0100 Subject: [PATCH 101/203] Move FakeClock to the impl/test folder. --- .../io/element/android/libraries/dateformatter/impl/Factory.kt | 1 - .../element/android/libraries/dateformatter/impl}/FakeClock.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename libraries/dateformatter/{test/src/main/kotlin/io/element/android/libraries/dateformatter/test => impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl}/FakeClock.kt (88%) diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt index 02993ee7d7..98b20c81b6 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt @@ -7,7 +7,6 @@ package io.element.android.libraries.dateformatter.impl -import io.element.android.libraries.dateformatter.test.FakeClock import io.element.android.tests.testutils.InstrumentationStringProvider import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone diff --git a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeClock.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt similarity index 88% rename from libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeClock.kt rename to libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt index 79e0eda10f..c6bdbec73f 100644 --- a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeClock.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.libraries.dateformatter.test +package io.element.android.libraries.dateformatter.impl import kotlinx.datetime.Clock import kotlinx.datetime.Instant From ab0324c53fe05d6ae5394fdda8f2b0b70855ebd0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 11:23:26 +0100 Subject: [PATCH 102/203] Add preview for date rendering --- libraries/dateformatter/impl/build.gradle.kts | 3 +- .../dateformatter/impl/DateFormatters.kt | 3 +- .../impl/previews/DateForPreview.kt | 53 ++++++++ .../previews/DateFormatterModeProvider.kt | 16 +++ .../previews/DateFormatterModeViewPreview.kt | 124 ++++++++++++++++++ .../dateformatter/impl/previews/Factory.kt | 66 ++++++++++ .../impl/previews/PreviewClock.kt | 21 +++ .../impl/previews/PreviewStringProvider.kt | 29 ++++ .../libraries/dateformatter/impl/Factory.kt | 2 + 9 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt create mode 100644 libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt diff --git a/libraries/dateformatter/impl/build.gradle.kts b/libraries/dateformatter/impl/build.gradle.kts index e814a1e2b8..2fb4f8461f 100644 --- a/libraries/dateformatter/impl/build.gradle.kts +++ b/libraries/dateformatter/impl/build.gradle.kts @@ -8,7 +8,7 @@ import extension.setupAnvil */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } setupAnvil() @@ -25,6 +25,7 @@ android { dependencies { implementation(libs.dagger) implementation(projects.libraries.core) + implementation(projects.libraries.designsystem) implementation(projects.libraries.di) implementation(projects.libraries.uiStrings) implementation(projects.services.toolbox.api) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt index e2637b5613..a041952fc3 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt @@ -26,12 +26,13 @@ class DateFormatters @Inject constructor( localeChangeObserver: LocaleChangeObserver, private val clock: Clock, private val timeZoneProvider: TimezoneProvider, + locale: Locale, ) : LocaleChangeListener { init { localeChangeObserver.addListener(this) } - private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(Locale.getDefault()) + private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(locale) override fun onLocaleChange() { Timber.w("Locale changed, updating formatters") diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt new file mode 100644 index 0000000000..5b9f732ceb --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl.previews + +data class DateForPreview( + val semantic: String, + val date: String, +) + +val dateForPreviewToday = DateForPreview( + semantic = "Today", + date = "1980-04-06T18:35:24.00Z", +) + +val dateForPreviews = listOf( + DateForPreview( + semantic = "Now", + date = dateForPreviewToday.date, + ), + DateForPreview( + semantic = "One second ago", + date = "1980-04-06T18:35:23.00Z", + ), + DateForPreview( + semantic = "One minute ago", + date = "1980-04-06T18:34:24.00Z", + ), + DateForPreview( + semantic = "One hour ago", + date = "1980-04-06T17:35:24.00Z", + ), + DateForPreview( + semantic = "One day ago", + date = "1980-04-05T18:35:24.00Z", + ), + DateForPreview( + semantic = "Two days ago", + date = "1980-04-04T18:35:24.00Z", + ), + DateForPreview( + semantic = "One month ago", + date = "1980-03-06T18:35:24.00Z", + ), + DateForPreview( + semantic = "One year ago", + date = "1979-04-06T18:35:24.00Z", + ), +) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt new file mode 100644 index 0000000000..36d7acabfc --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl.previews + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.dateformatter.api.DateFormatterMode + +class DateFormatterModeProvider : PreviewParameterProvider { + override val values: Sequence + get() = DateFormatterMode.entries.asSequence() +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt new file mode 100644 index 0000000000..d12f7b0724 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl.previews + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.dateformatter.api.DateFormatterMode +import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.allBooleans +import kotlinx.datetime.Instant + +@Preview +@Composable +internal fun DateFormatterModeViewPreview( + @PreviewParameter(DateFormatterModeProvider::class) dateFormatterMode: DateFormatterMode, +) = ElementPreview { + DateFormatterModeView(dateFormatterMode) +} + +@Composable +private fun DateFormatterModeView( + mode: DateFormatterMode, +) { + val context = LocalContext.current + val composeLocale = Locale.current + val dateFormatter = remember { + createFormatter( + context = context, + currentDate = dateForPreviewToday.date, + locale = java.util.Locale.Builder() + .setLanguageTag(composeLocale.toLanguageTag()) + .build(), + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "Mode $mode / $composeLocale", + style = ElementTheme.typography.fontHeadingSmMedium + ) + val today = Instant.parse(dateForPreviewToday.date).toEpochMilliseconds() + Text( + text = "Today is: ${dateFormatter.format(today, DateFormatterMode.Full, useRelative = false)}", + style = ElementTheme.typography.fontHeadingSmMedium, + ) + dateForPreviews.forEach { dateForPreview -> + DateForPreviewItem( + dateForPreview = dateForPreview, + dateFormatter = dateFormatter, + mode = mode, + ) + } + } +} + +@Composable +private fun DateForPreviewItem( + dateForPreview: DateForPreview, + dateFormatter: DefaultDateFormatter, + mode: DateFormatterMode, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(2.dp), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(start = 8.dp), + text = dateForPreview.semantic, + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textSecondary, + ) + val ts = Instant.parse(dateForPreview.date).toEpochMilliseconds() + Row { + Column { + listOf("Absolute:", "Relative:").forEach { label -> + Text( + text = label, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textPrimary, + ) + } + } + Spacer(modifier = Modifier.width(8.dp)) + Column { + allBooleans.forEach { useRelative -> + Text( + modifier = Modifier.fillMaxWidth(), + text = dateFormatter.format(ts, mode, useRelative), + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textPrimary, + ) + } + } + } + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt new file mode 100644 index 0000000000..cf9787e9d3 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl.previews + +import android.content.Context +import io.element.android.libraries.dateformatter.impl.DateFormatterFull +import io.element.android.libraries.dateformatter.impl.DateFormatterMonth +import io.element.android.libraries.dateformatter.impl.DateFormatterTime +import io.element.android.libraries.dateformatter.impl.DateFormatterTimeOnly +import io.element.android.libraries.dateformatter.impl.DateFormatters +import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter +import io.element.android.libraries.dateformatter.impl.DefaultDateFormatterDay +import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import java.util.Locale + +/** + * Create DefaultDateFormatter and set current time to the provided date. + */ +fun createFormatter( + context: Context, + currentDate: String, + locale: Locale, +): DefaultDateFormatter { + val clock = PreviewClock().apply { givenInstant(Instant.parse(currentDate)) } + val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC } + val dateFormatters = DateFormatters( + localeChangeObserver = {}, + clock = clock, + timeZoneProvider = { TimeZone.UTC }, + locale = locale, + ) + val stringProvider = PreviewStringProvider(context.resources) + val dateFormatterDay = DefaultDateFormatterDay( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ) + return DefaultDateFormatter( + dateFormatterFull = DateFormatterFull( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + dateFormatterDay = dateFormatterDay, + ), + dateFormatterMonth = DateFormatterMonth( + stringProvider = stringProvider, + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterDay = dateFormatterDay, + dateFormatterTime = DateFormatterTime( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + dateFormatterTimeOnly = DateFormatterTimeOnly( + localDateTimeProvider = localDateTimeProvider, + dateFormatters = dateFormatters, + ), + ) +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt new file mode 100644 index 0000000000..3486d169a2 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl.previews + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant + +class PreviewClock : Clock { + private var instant: Instant = Instant.fromEpochMilliseconds(0) + + fun givenInstant(instant: Instant) { + this.instant = instant + } + + override fun now(): Instant = instant +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt new file mode 100644 index 0000000000..6498b30d88 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.dateformatter.impl.previews + +import android.content.res.Resources +import androidx.annotation.PluralsRes +import androidx.annotation.StringRes +import io.element.android.services.toolbox.api.strings.StringProvider + +class PreviewStringProvider( + private val resources: Resources +) : StringProvider { + override fun getString(@StringRes resId: Int): String { + return resources.getString(resId) + } + + override fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { + return resources.getString(resId, *formatArgs) + } + + override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { + return resources.getQuantityString(resId, quantity, *formatArgs) + } +} diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt index 98b20c81b6..dd1572fde6 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.dateformatter.impl import io.element.android.tests.testutils.InstrumentationStringProvider import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone +import java.util.Locale /** * Create DefaultDateFormatter and set current time to the provided date. @@ -21,6 +22,7 @@ fun createFormatter(currentDate: String): DefaultDateFormatter { localeChangeObserver = {}, clock = clock, timeZoneProvider = { TimeZone.UTC }, + locale = Locale.getDefault(), ) val stringProvider = InstrumentationStringProvider() val dateFormatterDay = DefaultDateFormatterDay( From ad074326ca82babd13218f0964702e7b05cca9c8 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 12 Dec 2024 10:39:07 +0000 Subject: [PATCH 103/203] Update screenshots --- ....dateformatter.impl.previews_DateFormatterModeView_0_en.png | 3 +++ ....dateformatter.impl.previews_DateFormatterModeView_1_en.png | 3 +++ ....dateformatter.impl.previews_DateFormatterModeView_2_en.png | 3 +++ ....dateformatter.impl.previews_DateFormatterModeView_3_en.png | 3 +++ ....dateformatter.impl.previews_DateFormatterModeView_4_en.png | 3 +++ 5 files changed, 15 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_4_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_0_en.png new file mode 100644 index 0000000000..7c2a059e7c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0506879a20bd64cb3a4ea41c93dfa78da1ca3b0c2728ce3044caa56f6648584 +size 105611 diff --git a/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_1_en.png new file mode 100644 index 0000000000..160c9d66fd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e78aa3521464a53c000298dbd4ef51d4d0ea1d3c75b7bb8dcbd933701bab36b +size 84060 diff --git a/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_2_en.png new file mode 100644 index 0000000000..36a6b1bf7a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9eec4fc7e72588f957cd7d741e29b8eaed71555fc0d66c43e6ae69cc64b924c +size 87650 diff --git a/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_3_en.png new file mode 100644 index 0000000000..ef7b68df39 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4ae4608296b5bb24c128572cc6b80379a9e3cd12ec89db9693c301e427f6ae5 +size 82330 diff --git a/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_4_en.png new file mode 100644 index 0000000000..7b48600104 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.dateformatter.impl.previews_DateFormatterModeView_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e125730c22fcbc843fc0445e0af06d48b7dc31896053b5a7341b6dde84526eb8 +size 82540 From 0d2efe5ffa803bad924da51696ed3ac5e7e9c1a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 11:40:53 +0100 Subject: [PATCH 104/203] Add a feature flag for the event cache. --- .../android/libraries/featureflag/api/FeatureFlags.kt | 7 +++++++ .../libraries/matrix/impl/RustMatrixClientFactory.kt | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 9add32499f..b028978c94 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -161,4 +161,11 @@ enum class FeatureFlags( defaultValue = { buildMeta -> buildMeta.buildType != BuildType.RELEASE }, isFinished = false, ), + EventCache( + key = "feature.event_cache", + title = "Use SDK Event cache", + description = "Warning: you must kill and restart the app for the change to take effect.", + defaultValue = { false }, + isFinished = false, + ), } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index e688452510..d5af4ff67f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -109,9 +109,7 @@ class RustMatrixClientFactory @Inject constructor( .addRootCertificates(userCertificatesProvider.provides()) .autoEnableBackups(true) .autoEnableCrossSigning(true) - // TODO Add a feature flag to enable persistent storage - // See https://github.com/matrix-org/matrix-rust-sdk/pull/4396 - .useEventCachePersistentStorage(false) + .useEventCachePersistentStorage(featureFlagService.isFeatureEnabled(FeatureFlags.EventCache)) .roomKeyRecipientStrategy( strategy = if (featureFlagService.isFeatureEnabled(FeatureFlags.OnlySignedDeviceIsolationMode)) { CollectStrategy.IdentityBasedStrategy From 4f1c745fc81cdd89da20caf95c36f013b583a878 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 11:58:46 +0100 Subject: [PATCH 105/203] Add a way to clear the cache for a specific room. --- .../roomlist/impl/RoomListContextMenu.kt | 18 ++++++++++++++++++ .../features/roomlist/impl/RoomListEvents.kt | 1 + .../roomlist/impl/RoomListPresenter.kt | 10 +++++++++- .../features/roomlist/impl/RoomListState.kt | 1 + .../RoomListStateContextMenuShownProvider.kt | 1 + .../libraries/matrix/api/room/MatrixRoom.kt | 5 +++++ .../matrix/impl/room/RustMatrixRoom.kt | 6 ++++++ .../matrix/test/room/FakeMatrixRoom.kt | 4 ++++ 8 files changed, 45 insertions(+), 1 deletion(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 6ba1d8ed8e..f44f192e77 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -61,6 +61,10 @@ fun RoomListContextMenu( onFavoriteChange = { isFavorite -> eventSink(RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, isFavorite)) }, + onClearCacheRoomClick = { + eventSink(RoomListEvents.HideContextMenu) + eventSink(RoomListEvents.ClearCacheOfRoom(contextMenu.roomId)) + }, ) } } @@ -73,6 +77,7 @@ private fun RoomListModalBottomSheetContent( onFavoriteChange: (isFavorite: Boolean) -> Unit, onRoomMarkReadClick: () -> Unit, onRoomMarkUnreadClick: () -> Unit, + onClearCacheRoomClick: () -> Unit, ) { Column( modifier = Modifier.fillMaxWidth() @@ -177,6 +182,18 @@ private fun RoomListModalBottomSheetContent( ), style = ListItemStyle.Destructive, ) + if (contextMenu.eventCacheFeatureFlagEnabled) { + ListItem( + headlineContent = { + Text(text = "Clear cache for this room") + }, + modifier = Modifier.clickable { onClearCacheRoomClick() }, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector(CompoundIcons.Delete()) + ), + style = ListItemStyle.Primary, + ) + } } } @@ -195,5 +212,6 @@ internal fun RoomListModalBottomSheetContentPreview( onRoomSettingsClick = {}, onLeaveRoomClick = {}, onFavoriteChange = {}, + onClearCacheRoomClick = {}, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index 9a8f2353ba..67c4544aaa 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -25,4 +25,5 @@ sealed interface RoomListEvents { data class MarkAsRead(val roomId: RoomId) : ContextMenuEvents data class MarkAsUnread(val roomId: RoomId) : ContextMenuEvents data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : ContextMenuEvents + data class ClearCacheOfRoom(val roomId: RoomId) : ContextMenuEvents } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index a2468c32d0..fdd92e0c44 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -146,6 +146,7 @@ class RoomListPresenter @Inject constructor( AcceptDeclineInviteEvents.DeclineInvite(event.roomListRoomSummary.toInviteData()) ) } + is RoomListEvents.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId) } } @@ -255,7 +256,8 @@ class RoomListPresenter @Inject constructor( isDm = event.roomListRoomSummary.isDm, isFavorite = event.roomListRoomSummary.isFavorite, markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread), - hasNewContent = event.roomListRoomSummary.hasNewContent + hasNewContent = event.roomListRoomSummary.hasNewContent, + eventCacheFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.EventCache), ) contextMenuState.value = initialState @@ -312,6 +314,12 @@ class RoomListPresenter @Inject constructor( } } + private fun CoroutineScope.clearCacheOfRoom(roomId: RoomId) = launch { + client.getRoom(roomId)?.use { room -> + room.clearEventCacheStorage() + } + } + /** * Checks if the user needs to migrate to a native sliding sync version. */ diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index a6b9673b54..a0a5633165 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -46,6 +46,7 @@ data class RoomListState( val isDm: Boolean, val isFavorite: Boolean, val markAsUnreadFeatureFlagEnabled: Boolean, + val eventCacheFeatureFlagEnabled: Boolean, val hasNewContent: Boolean, ) : ContextMenu } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateContextMenuShownProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateContextMenuShownProvider.kt index 6b74c7934a..36b951f73c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateContextMenuShownProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateContextMenuShownProvider.kt @@ -31,4 +31,5 @@ internal fun aContextMenuShown( markAsUnreadFeatureFlagEnabled = true, hasNewContent = hasNewContent, isFavorite = isFavorite, + eventCacheFeatureFlagEnabled = false, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 840308af23..45309f5d88 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -232,6 +232,11 @@ interface MatrixRoom : Closeable { */ suspend fun setUnreadFlag(isUnread: Boolean): Result + /** + * Clear the event cache storage for the current room. + */ + suspend fun clearEventCacheStorage(): Result + /** * Share a location message in the room. * diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index c84a37cdc3..f1e2495d60 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -569,6 +569,12 @@ class RustMatrixRoom( } } + override suspend fun clearEventCacheStorage(): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.clearEventCacheStorage() + } + } + override suspend fun kickUser(userId: UserId, reason: String?): Result = withContext(roomDispatcher) { runCatching { innerRoom.kickUser(userId.value, reason) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 41d913b89c..c47f4238b9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -574,6 +574,10 @@ class FakeMatrixRoom( fun givenRoomMembersState(state: MatrixRoomMembersState) { membersStateFlow.value = state } + + override suspend fun clearEventCacheStorage(): Result { + return Result.success(Unit) + } } fun defaultRoomPowerLevels() = MatrixRoomPowerLevels( From a02491ab6f41dee3e84b13ee1e08bb82a6a286f3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 12:09:59 +0100 Subject: [PATCH 106/203] Restore providing the Locale --- .../libraries/dateformatter/impl/di/DateFormatterModule.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt index 3c409a977f..568bee5378 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.dateformatter.impl.TimezoneProvider import io.element.android.libraries.di.AppScope import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone +import java.util.Locale @Module @ContributesTo(AppScope::class) @@ -21,6 +22,9 @@ object DateFormatterModule { @Provides fun providesClock(): Clock = Clock.System + @Provides + fun providesLocale(): Locale = Locale.getDefault() + @Provides fun providesTimezone(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() } } From f29dd2961a992c502fa50d393558ba9917acf7a2 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 12 Dec 2024 11:30:59 +0000 Subject: [PATCH 107/203] Update UtdTracker tests --- .../matrix/impl/analytics/UtdTrackerTest.kt | 110 +++++++++++++++++- .../fixtures/factories/UnableToDecryptInfo.kt | 4 +- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt index 62147d182c..deefe1a189 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt @@ -25,6 +25,7 @@ class UtdTrackerTest { eventId = AN_EVENT_ID.value, timeToDecryptMs = null, cause = UtdCause.UNKNOWN, + eventLocalAgeMillis = 100L, ) ) assertThat(fakeAnalyticsService.capturedEvents).containsExactly( @@ -34,7 +35,11 @@ class UtdTrackerTest { cryptoSDK = Error.CryptoSDK.Rust, timeToDecryptMillis = -1, domain = Error.Domain.E2EE, - name = Error.Name.OlmKeysNotSentError + name = Error.Name.OlmKeysNotSentError, + isFederated = false, + isMatrixDotOrg = false, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 100, ) ) assertThat(fakeAnalyticsService.screenEvents).isEmpty() @@ -59,7 +64,11 @@ class UtdTrackerTest { cryptoSDK = Error.CryptoSDK.Rust, timeToDecryptMillis = 123, domain = Error.Domain.E2EE, - name = Error.Name.OlmKeysNotSentError + name = Error.Name.OlmKeysNotSentError, + isFederated = false, + isMatrixDotOrg = false, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 0, ) ) assertThat(fakeAnalyticsService.screenEvents).isEmpty() @@ -84,7 +93,11 @@ class UtdTrackerTest { cryptoSDK = Error.CryptoSDK.Rust, timeToDecryptMillis = 123, domain = Error.Domain.E2EE, - name = Error.Name.ExpectedDueToMembership + name = Error.Name.ExpectedDueToMembership, + isFederated = false, + isMatrixDotOrg = false, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 0, ) ) assertThat(fakeAnalyticsService.screenEvents).isEmpty() @@ -109,7 +122,11 @@ class UtdTrackerTest { cryptoSDK = Error.CryptoSDK.Rust, timeToDecryptMillis = 123, domain = Error.Domain.E2EE, - name = Error.Name.ExpectedSentByInsecureDevice + name = Error.Name.ExpectedSentByInsecureDevice, + isFederated = false, + isMatrixDotOrg = false, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 0, ) ) } @@ -132,7 +149,90 @@ class UtdTrackerTest { cryptoSDK = Error.CryptoSDK.Rust, timeToDecryptMillis = 123, domain = Error.Domain.E2EE, - name = Error.Name.ExpectedVerificationViolation + name = Error.Name.ExpectedVerificationViolation, + isFederated = false, + isMatrixDotOrg = false, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 0, + ) + ) + } + + @Test + fun `when onUtd is called with different sender and receiver servers, the expected analytics Event is sent`() { + val fakeAnalyticsService = FakeAnalyticsService() + val sut = UtdTracker(fakeAnalyticsService) + sut.onUtd( + aRustUnableToDecryptInfo( + eventId = AN_EVENT_ID.value, + ownHomeserver = "example.com", + senderHomeserver = "matrix.org", + ) + ) + assertThat(fakeAnalyticsService.capturedEvents).containsExactly( + Error( + context = null, + cryptoModule = Error.CryptoModule.Rust, + cryptoSDK = Error.CryptoSDK.Rust, + timeToDecryptMillis = -1, + domain = Error.Domain.E2EE, + name = Error.Name.OlmKeysNotSentError, + isFederated = true, + isMatrixDotOrg = false, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 0, + ) + ) + } + + @Test + fun `when onUtd is called from a matrix-org user, the expected analytics Event is sent`() { + val fakeAnalyticsService = FakeAnalyticsService() + val sut = UtdTracker(fakeAnalyticsService) + sut.onUtd( + aRustUnableToDecryptInfo( + eventId = AN_EVENT_ID.value, + ownHomeserver = "matrix.org", + ) + ) + assertThat(fakeAnalyticsService.capturedEvents).containsExactly( + Error( + context = null, + cryptoModule = Error.CryptoModule.Rust, + cryptoSDK = Error.CryptoSDK.Rust, + timeToDecryptMillis = -1, + domain = Error.Domain.E2EE, + name = Error.Name.OlmKeysNotSentError, + isFederated = true, + isMatrixDotOrg = true, + userTrustsOwnIdentity = false, + eventLocalAgeMillis = 0, + ) + ) + } + + @Test + fun `when onUtd is called from a verified device, the expected analytics Event is sent`() { + val fakeAnalyticsService = FakeAnalyticsService() + val sut = UtdTracker(fakeAnalyticsService) + sut.onUtd( + aRustUnableToDecryptInfo( + eventId = AN_EVENT_ID.value, + userTrustsOwnIdentity = true, + ) + ) + assertThat(fakeAnalyticsService.capturedEvents).containsExactly( + Error( + context = null, + cryptoModule = Error.CryptoModule.Rust, + cryptoSDK = Error.CryptoSDK.Rust, + timeToDecryptMillis = -1, + domain = Error.Domain.E2EE, + name = Error.Name.OlmKeysNotSentError, + isFederated = false, + isMatrixDotOrg = false, + userTrustsOwnIdentity = true, + eventLocalAgeMillis = 0, ) ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt index 4743368472..775934716f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt @@ -12,8 +12,8 @@ import uniffi.matrix_sdk_crypto.UtdCause internal fun aRustUnableToDecryptInfo( eventId: String, - timeToDecryptMs: ULong?, - cause: UtdCause, + timeToDecryptMs: ULong? = null, + cause: UtdCause = UtdCause.UNKNOWN, eventLocalAgeMillis: Long = 0L, userTrustsOwnIdentity: Boolean = false, senderHomeserver: String = "", From a99eecdb50b4830ddb1b7af9cace6e3a9bdb153a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:47:40 +0100 Subject: [PATCH 108/203] We need to scroll more, there are too many items. --- .maestro/tests/roomList/createAndDeleteRoom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.maestro/tests/roomList/createAndDeleteRoom.yaml b/.maestro/tests/roomList/createAndDeleteRoom.yaml index ae6f5772c6..7cbf455ba2 100644 --- a/.maestro/tests/roomList/createAndDeleteRoom.yaml +++ b/.maestro/tests/roomList/createAndDeleteRoom.yaml @@ -30,5 +30,6 @@ appId: ${MAESTRO_APP_ID} # assert there's 1 member and 2 invitees - tapOn: "Back" - scroll +- scroll - tapOn: "Leave room" - tapOn: "Leave" From f4bd047586e1db697326195ab4eeda4aaaaa35f2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:53:02 +0100 Subject: [PATCH 109/203] Fix tests --- .../android/features/roomlist/impl/RoomListPresenterTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt index 69e9a7d401..ff929bda27 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt @@ -288,6 +288,7 @@ class RoomListPresenterTest { isDm = false, isFavorite = false, markAsUnreadFeatureFlagEnabled = true, + eventCacheFeatureFlagEnabled = false, hasNewContent = false, ) ) @@ -305,6 +306,7 @@ class RoomListPresenterTest { isDm = false, isFavorite = true, markAsUnreadFeatureFlagEnabled = true, + eventCacheFeatureFlagEnabled = false, hasNewContent = false, ) ) @@ -335,6 +337,7 @@ class RoomListPresenterTest { isDm = false, isFavorite = false, markAsUnreadFeatureFlagEnabled = true, + eventCacheFeatureFlagEnabled = false, hasNewContent = false, ) ) From 1a4e8e0ff23b90cdbb659a235101c8a2ad367d36 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:02:40 +0000 Subject: [PATCH 110/203] Update dependency io.sentry:sentry-android to v7.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 76086219ea..8d96e34295 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -195,7 +195,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics posthog = "com.posthog:posthog-android:3.9.3" -sentry = "io.sentry:sentry-android:7.18.1" +sentry = "io.sentry:sentry-android:7.19.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" From a02a9d6bd4150c65d826e6e3a07c7f6724dfe54c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:20:29 +0000 Subject: [PATCH 111/203] Update android.gradle.plugin to v8.7.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 ede0aff101..24a302a73a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ [versions] # Project -android_gradle_plugin = "8.7.2" +android_gradle_plugin = "8.7.3" kotlin = "2.1.0" kotlinpoet = "2.0.0" ksp = "2.1.0-1.0.29" From 0bd350eb958113f95af3155a3626fc679942619e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 20:54:36 +0000 Subject: [PATCH 112/203] Update dependency org.jetbrains.kotlinx:kover-gradle-plugin to v0.9.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 ede0aff101..0544d7bb0e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,7 @@ autoservice = "1.1.1" # quality androidx-test-ext-junit = "1.2.1" -kover = "0.8.3" +kover = "0.9.0" [libraries] # Project From c4f71c451706a274131f619c8d60848075219b2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 05:32:27 +0000 Subject: [PATCH 113/203] Update dependency com.google.accompanist:accompanist-permissions to v0.37.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 ede0aff101..01ceb49ea6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ composecompiler = "1.5.15" coroutines = "1.9.0" # Accompanist -accompanist = "0.36.0" +accompanist = "0.37.0" # Test test_core = "1.6.1" From ce09aac59dfe368ef36d84bd1fe52007a0f08efc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:01:23 +0100 Subject: [PATCH 114/203] Add audio file support in the gallery and in the media viewer. Make MediaPlayerControllerView compatible to control audio playback --- .../messages/impl/MessagesFlowNode.kt | 2 + .../components/media/WaveformPlaybackView.kt | 44 +- .../libraries/mediaviewer/api/MediaInfo.kt | 13 +- .../impl/DefaultMediaViewerEntryPoint.kt | 1 + .../impl/gallery/EventItemFactory.kt | 14 +- .../impl/gallery/MediaGalleryPresenter.kt | 1 + .../impl/gallery/MediaGalleryStateProvider.kt | 8 +- .../impl/gallery/MediaGalleryView.kt | 11 + .../mediaviewer/impl/gallery/MediaItem.kt | 15 + .../impl/gallery/MediaItemsPostProcessor.kt | 1 + .../impl/gallery/ui/AudioItemView.kt | 216 ++++++++++ .../impl/gallery/ui/MediaItemAudioProvider.kt | 54 +++ .../impl/local/AndroidLocalMediaFactory.kt | 4 + .../mediaviewer/impl/local/LocalMediaView.kt | 10 +- .../impl/local/audio/MediaAudioView.kt | 380 ++++++++++++++++++ .../local/audio/MediaInfoAudioProvider.kt | 23 ++ .../impl/local/audio/MediaMetadata.kt | 35 ++ .../impl/local/file/MediaInfoFileProvider.kt | 2 - .../local/video/MediaPlayerControllerState.kt | 8 +- .../MediaPlayerControllerStateProvider.kt | 5 + .../local/video/MediaPlayerControllerView.kt | 32 +- .../impl/local/video/MediaVideoView.kt | 1 + .../impl/viewer/MediaViewerStateProvider.kt | 11 + 23 files changed, 866 insertions(+), 25 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 6754f5c683..2d0be01de1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -47,6 +47,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.features.poll.api.create.CreatePollMode import io.element.android.libraries.architecture.BackstackWithOverlayBox @@ -447,6 +448,7 @@ class MessagesFlowNode @AssistedInject constructor( timestamp = event.sentTimeMillis, mode = DateFormatterMode.Full, ), + waveform = (content as? TimelineItemVoiceContent)?.waveform, ), mediaSource = mediaSource, thumbnailSource = thumbnailSource, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt index e7fc1c5ce4..4078399830 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt @@ -189,7 +189,7 @@ internal fun WaveformPlaybackViewPreview() = ElementPreview { showCursor = false, playbackProgress = 0.5f, onSeek = {}, - waveform = persistentListOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f), + waveform = aWaveForm().toPersistentList(), ) WaveformPlaybackView( modifier = Modifier.height(34.dp), @@ -219,3 +219,45 @@ private fun ImmutableList.normalisedData(maxSamplesCount: Int): Immutable return result.toPersistentList() } + +fun aWaveForm(): List { + return listOf( + 0.000f, + 0.000f, + 0.000f, + 0.003f, + 0.354f, + 0.353f, + 0.365f, + 0.790f, + 0.787f, + 0.167f, + 0.333f, + 0.975f, + 0.000f, + 0.102f, + 0.003f, + 0.531f, + 0.584f, + 0.317f, + 0.140f, + 0.475f, + 0.496f, + 0.561f, + 0.042f, + 0.263f, + 0.169f, + 0.829f, + 0.349f, + 0.010f, + 0.000f, + 0.000f, + 1.000f, + 0.334f, + 0.321f, + 0.011f, + 0.000f, + 0.000f, + 0.003f, + ) +} diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index 7daa5ab7ef..8d72d049ed 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -24,6 +24,7 @@ data class MediaInfo( val senderAvatar: String?, val dateSent: String?, val dateSentFull: String?, + val waveform: List?, ) : Parcelable fun anImageMediaInfo( @@ -43,6 +44,7 @@ fun anImageMediaInfo( senderAvatar = null, dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ) fun aVideoMediaInfo( @@ -61,6 +63,7 @@ fun aVideoMediaInfo( senderAvatar = null, dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ) fun aPdfMediaInfo( @@ -80,6 +83,7 @@ fun aPdfMediaInfo( senderAvatar = null, dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ) fun anApkMediaInfo( @@ -98,15 +102,19 @@ fun anApkMediaInfo( senderAvatar = null, dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ) fun anAudioMediaInfo( + filename: String = "an audio file.mp3", + caption: String? = null, senderName: String? = null, dateSent: String? = null, dateSentFull: String? = null, + waveForm: List? = null, ): MediaInfo = MediaInfo( - filename = "an audio file.mp3", - caption = null, + filename = filename, + caption = caption, mimeType = MimeTypes.Mp3, formattedFileSize = "7MB", fileExtension = "mp3", @@ -115,4 +123,5 @@ fun anAudioMediaInfo( senderAvatar = null, dateSent = dateSent, dateSentFull = dateSentFull, + waveform = waveForm, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt index d85bf08b8e..59a7f423e6 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt @@ -54,6 +54,7 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint senderAvatar = null, dateSent = null, dateSentFull = null, + waveform = null, ), mediaSource = MediaSource(url = avatarUrl), thumbnailSource = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index 8fcea07f52..4baa1d0ee1 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -86,7 +86,7 @@ class EventItemFactory @Inject constructor( Timber.w("Should not happen: ${content.type}") null } - is AudioMessageType -> MediaItem.File( + is AudioMessageType -> MediaItem.Audio( id = currentTimelineItem.uniqueId, eventId = currentTimelineItem.eventId, mediaInfo = MediaInfo( @@ -100,8 +100,11 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ), mediaSource = type.source, + duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), + waveform = null, ) is FileMessageType -> MediaItem.File( id = currentTimelineItem.uniqueId, @@ -117,6 +120,7 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ), mediaSource = type.source, ) @@ -134,6 +138,7 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ), mediaSource = type.source, thumbnailSource = null, @@ -152,6 +157,7 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ), mediaSource = type.source, thumbnailSource = null, @@ -170,12 +176,13 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, + waveform = null, ), mediaSource = type.source, thumbnailSource = type.info?.thumbnailSource, duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), ) - is VoiceMessageType -> MediaItem.File( + is VoiceMessageType -> MediaItem.Audio( id = currentTimelineItem.uniqueId, eventId = currentTimelineItem.eventId, mediaInfo = MediaInfo( @@ -189,8 +196,11 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, + waveform = type.details?.waveform, ), mediaSource = type.source, + duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), + waveform = type.details?.waveform, ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index c122e95447..242d23c10d 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -135,6 +135,7 @@ class MediaGalleryPresenter @AssistedInject constructor( thumbnailSource = when (event.mediaItem) { is MediaItem.Image -> event.mediaItem.thumbnailSource ?: event.mediaItem.mediaSource is MediaItem.Video -> event.mediaItem.thumbnailSource ?: event.mediaItem.mediaSource + is MediaItem.Audio -> null is MediaItem.File -> null }, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 58d566dddd..d148121dbd 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -9,9 +9,11 @@ package io.element.android.libraries.mediaviewer.impl.gallery import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.components.media.aWaveForm import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemAudio import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemDateSeparator import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage @@ -62,7 +64,11 @@ open class MediaGalleryStateProvider : PreviewParameterProvider AudioItemView( + item, + onClick = { onItemClick(item) }, + onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, + onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, + onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, + ) is MediaItem.DateSeparator -> DateItemView(item) is MediaItem.Image, is MediaItem.Video -> { @@ -312,6 +320,9 @@ private fun MediaGalleryImageGrid( is MediaItem.DateSeparator -> { DateItemView(item) } + is MediaItem.Audio -> { + // Should not happen + } is MediaItem.File -> { // Should not happen } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt index f43387fdb6..0925b4937b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt @@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.mediaviewer.api.MediaInfo +import kotlinx.collections.immutable.ImmutableList sealed interface MediaItem { data class DateSeparator( @@ -51,6 +52,15 @@ sealed interface MediaItem { get() = MediaRequestData(thumbnailSource ?: mediaSource, MediaRequestData.Kind.Thumbnail(100)) } + data class Audio( + val id: UniqueId, + val eventId: EventId?, + val mediaInfo: MediaInfo, + val mediaSource: MediaSource, + val duration: String?, + val waveform: ImmutableList?, + ) : Event + data class File( val id: UniqueId, val eventId: EventId?, @@ -66,6 +76,7 @@ fun MediaItem.id(): UniqueId { is MediaItem.Image -> id is MediaItem.Video -> id is MediaItem.File -> id + is MediaItem.Audio -> id } } @@ -74,6 +85,7 @@ fun MediaItem.Event.eventId(): EventId? { is MediaItem.Image -> eventId is MediaItem.Video -> eventId is MediaItem.File -> eventId + is MediaItem.Audio -> eventId } } @@ -82,6 +94,7 @@ fun MediaItem.Event.mediaInfo(): MediaInfo { is MediaItem.Image -> mediaInfo is MediaItem.Video -> mediaInfo is MediaItem.File -> mediaInfo + is MediaItem.Audio -> mediaInfo } } @@ -90,6 +103,7 @@ fun MediaItem.Event.mediaSource(): MediaSource { is MediaItem.Image -> mediaSource is MediaItem.Video -> mediaSource is MediaItem.File -> mediaSource + is MediaItem.Audio -> mediaSource } } @@ -98,5 +112,6 @@ fun MediaItem.Event.thumbnailSource(): MediaSource? { is MediaItem.Image -> thumbnailSource is MediaItem.Video -> thumbnailSource is MediaItem.File -> null + is MediaItem.Audio -> null } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index 6706dd08c8..1ed9f0e42b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -56,6 +56,7 @@ class MediaItemsPostProcessor @Inject constructor() { is MediaItem.Video -> { imageAndVideoItemsSubList.add(0, item) } + is MediaItem.Audio, is MediaItem.File -> { fileItemsSublist.add(0, item) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt new file mode 100644 index 0000000000..a15a12ba58 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt @@ -0,0 +1,216 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +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.libraries.core.extensions.withBrackets +import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView +import io.element.android.libraries.designsystem.preview.ElementPreview +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.IconButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import kotlinx.collections.immutable.toPersistentList + +@Composable +fun AudioItemView( + audio: MediaItem.Audio, + onClick: () -> Unit, + onShareClick: () -> Unit, + onDownloadClick: () -> Unit, + onInfoClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(top = 20.dp, start = 16.dp, end = 16.dp), + ) { + FilenameRow( + audio = audio, + onClick = onClick, + ) + val caption = audio.mediaInfo.caption + if (caption != null) { + Spacer(modifier = Modifier.height(16.dp)) + Caption(caption) + } + Spacer(modifier = Modifier.height(16.dp)) + ActionIconsRow( + onShareClick = onShareClick, + onDownloadClick = onDownloadClick, + onInfoClick = onInfoClick, + ) + HorizontalDivider() + } +} + +@Composable +private fun FilenameRow( + audio: MediaItem.Audio, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background( + color = ElementTheme.colors.bgSubtleSecondary, + shape = RoundedCornerShape(12.dp), + ) + .clickable { onClick() } + .fillMaxWidth() + .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .background( + color = ElementTheme.colors.bgCanvasDefault, + shape = CircleShape, + ) + .border( + width = 1.dp, + color = ElementTheme.colors.borderInteractiveSecondary, + shape = CircleShape, + ) + .size(36.dp) + .padding(6.dp), + imageVector = CompoundIcons.PlaySolid(), + tint = ElementTheme.colors.iconSecondary, + contentDescription = null, + ) + audio.duration?.let { + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = audio.duration, + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Spacer(modifier = Modifier.width(8.dp)) + val waveform = audio.waveform + if (waveform == null) { + Text( + text = audio.mediaInfo.filename, + modifier = Modifier.weight(1f), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + val formattedSize = audio.mediaInfo.formattedFileSize + if (formattedSize.isNotEmpty()) { + Text( + text = formattedSize.withBrackets(), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) + } + } else { + WaveformPlaybackView( + modifier = Modifier + .weight(1f) + .height(34.dp), + playbackProgress = 0f, + showCursor = false, + waveform = waveform.toPersistentList(), + onSeek = {}, + seekEnabled = false, + ) + } + } +} + +@Composable +private fun Caption(caption: String) { + Text( + modifier = Modifier.fillMaxWidth(), + text = caption, + maxLines = 5, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) +} + +@Composable +private fun ActionIconsRow( + onShareClick: () -> Unit, + onDownloadClick: () -> Unit, + onInfoClick: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + IconButton( + onClick = onShareClick, + ) { + Icon( + imageVector = CompoundIcons.ShareAndroid(), + contentDescription = null, + ) + } + IconButton( + onClick = onDownloadClick, + ) { + Icon( + imageVector = CompoundIcons.Download(), + contentDescription = null, + ) + } + IconButton( + onClick = onInfoClick, + ) { + Icon( + imageVector = CompoundIcons.Info(), + contentDescription = null, + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun AudioItemViewPreview( + @PreviewParameter(MediaItemAudioProvider::class) audio: MediaItem.Audio, +) = ElementPreview { + AudioItemView( + audio = audio, + onClick = {}, + onShareClick = {}, + onDownloadClick = {}, + onInfoClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt new file mode 100644 index 0000000000..4bcdf60d3a --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.core.preview.loremIpsum +import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import kotlinx.collections.immutable.toImmutableList + +class MediaItemAudioProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aMediaItemAudio(), + aMediaItemAudio( + filename = "A long filename that should be truncated.mp3", + caption = "A caption", + ), + aMediaItemAudio( + caption = loremIpsum, + ), + aMediaItemAudio( + waveform = aWaveForm(), + ), + ) +} + +fun aMediaItemAudio( + id: UniqueId = UniqueId("fileId"), + filename: String = "filename", + caption: String? = null, + duration: String? = "1:23", + waveform: List? = null, +): MediaItem.Audio { + return MediaItem.Audio( + id = id, + eventId = null, + mediaInfo = anAudioMediaInfo( + filename = filename, + caption = caption, + ), + mediaSource = MediaSource(""), + duration = duration, + waveform = waveform?.toImmutableList(), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index c17d613e55..ceed35121a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -47,6 +47,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderAvatar = mediaInfo.senderAvatar, dateSent = mediaInfo.dateSent, dateSentFull = mediaInfo.dateSentFull, + waveform = mediaInfo.waveform, ) override fun createFromUri( @@ -65,6 +66,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderAvatar = null, dateSent = null, dateSentFull = null, + waveform = null, ) private fun createFromUri( @@ -78,6 +80,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderAvatar: String?, dateSent: String?, dateSentFull: String?, + waveform: List?, ): LocalMedia { val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream val fileName = name ?: context.getFileName(uri) ?: "" @@ -96,6 +99,7 @@ class AndroidLocalMediaFactory @Inject constructor( senderAvatar = senderAvatar, dateSent = dateSent, dateSentFull = dateSentFull, + waveform = waveform, ) ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 5d0a2993df..1c56c291b4 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -10,10 +10,12 @@ package io.element.android.libraries.mediaviewer.impl.local import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.local.audio.MediaAudioView import io.element.android.libraries.mediaviewer.impl.local.file.MediaFileView import io.element.android.libraries.mediaviewer.impl.local.image.MediaImageView import io.element.android.libraries.mediaviewer.impl.local.pdf.MediaPdfView @@ -48,7 +50,13 @@ fun LocalMediaView( modifier = modifier, onClick = onClick, ) - // TODO handle audio with exoplayer + mimeType.isMimeTypeAudio() -> MediaAudioView( + localMediaViewState = localMediaViewState, + bottomPaddingInPixels = bottomPaddingInPixels, + localMedia = localMedia, + info = mediaInfo, + modifier = modifier, + ) else -> MediaFileView( localMediaViewState = localMediaViewState, uri = localMedia?.uri, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt new file mode 100644 index 0000000000..667aa7156b --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -0,0 +1,380 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.audio + +import android.annotation.SuppressLint +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.GraphicEq +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.common.Player +import androidx.media3.common.Timeline +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.ui.AspectRatioFrameLayout +import androidx.media3.ui.PlayerView +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.toDp +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.OnLifecycleEvent +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState +import io.element.android.libraries.mediaviewer.impl.local.PlayableState +import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState +import io.element.android.libraries.mediaviewer.impl.local.video.ExoPlayerForPreview +import io.element.android.libraries.mediaviewer.impl.local.video.ExoPlayerWrapper +import io.element.android.libraries.mediaviewer.impl.local.video.MediaPlayerControllerState +import io.element.android.libraries.mediaviewer.impl.local.video.MediaPlayerControllerView +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.delay + +@SuppressLint("UnsafeOptInUsageError") +@Composable +fun MediaAudioView( + localMediaViewState: LocalMediaViewState, + bottomPaddingInPixels: Int, + localMedia: LocalMedia?, + info: MediaInfo?, + modifier: Modifier = Modifier, +) { + val exoPlayer = if (LocalInspectionMode.current) { + remember { + ExoPlayerForPreview() + } + } else { + val context = LocalContext.current + remember { + ExoPlayerWrapper.create(context) + } + } + ExoPlayerMediaAudioView( + localMediaViewState = localMediaViewState, + bottomPaddingInPixels = bottomPaddingInPixels, + exoPlayer = exoPlayer, + localMedia = localMedia, + info = info, + modifier = modifier, + ) +} + +@SuppressLint("UnsafeOptInUsageError") +@Composable +private fun ExoPlayerMediaAudioView( + localMediaViewState: LocalMediaViewState, + bottomPaddingInPixels: Int, + exoPlayer: ExoPlayer, + localMedia: LocalMedia?, + info: MediaInfo?, + modifier: Modifier = Modifier, +) { + var mediaPlayerControllerState: MediaPlayerControllerState by remember { + mutableStateOf( + MediaPlayerControllerState( + isVisible = true, + isPlaying = false, + progressInMillis = 0, + durationInMillis = 0, + canMute = false, + isMuted = false, + ) + ) + } + + var metadata: MediaMetadata? by remember { + mutableStateOf(null) + } + + val playableState: PlayableState.Playable by remember { + derivedStateOf { + PlayableState.Playable( + isShowingControls = mediaPlayerControllerState.isVisible, + ) + } + } + + localMediaViewState.playableState = playableState + + val playerListener = object : Player.Listener { + override fun onRenderedFirstFrame() { + localMediaViewState.isReady = true + } + + override fun onIsPlayingChanged(isPlaying: Boolean) { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + isPlaying = isPlaying, + ) + } + + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) { + exoPlayer.duration.takeIf { it >= 0 } + ?.let { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + durationInMillis = it, + ) + } + } + } + + override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { + metadata = mediaMetadata + } + } + + LaunchedEffect(exoPlayer.isPlaying) { + if (exoPlayer.isPlaying) { + while (true) { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + progressInMillis = exoPlayer.currentPosition, + ) + delay(200) + } + } else { + // Ensure we render the final state + mediaPlayerControllerState = mediaPlayerControllerState.copy( + progressInMillis = exoPlayer.currentPosition, + ) + } + } + if (localMedia?.uri != null) { + LaunchedEffect(localMedia.uri) { + val mediaItem = MediaItem.fromUri(localMedia.uri) + exoPlayer.setMediaItem(mediaItem) + } + } else { + exoPlayer.setMediaItems(emptyList()) + } + val context = LocalContext.current + val waveform = info?.waveform + Box( + modifier = modifier + .fillMaxSize() + .background(ElementTheme.colors.bgSubtlePrimary), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + if (LocalInspectionMode.current) { + Text( + modifier = Modifier + .padding(16.dp) + .width(240.dp), + text = "An audio Player may render an image here if the audio file contains some artwork.", + textAlign = TextAlign.Center, + color = ElementTheme.colors.textPrimary, + ) + } else { + AndroidView( + modifier = Modifier + .clip(shape = RoundedCornerShape(12.dp)) + .clipToBounds() + .width(240.dp), + factory = { + PlayerView(context).apply { + player = exoPlayer + resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT + layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + useController = false + } + }, + update = { playerView -> + playerView.isVisible = metadata.hasArtwork() + }, + onRelease = { playerView -> + playerView.player = null + }, + ) + } + if (waveform != null) { + WaveformPlaybackView( + modifier = Modifier + .height(48.dp), + playbackProgress = mediaPlayerControllerState.progressAsFloat, + showCursor = true, + waveform = waveform.toPersistentList(), + onSeek = { + if (exoPlayer.isPlaying.not()) { + exoPlayer.play() + } + exoPlayer.seekTo((it * exoPlayer.duration).toLong()) + }, + seekEnabled = true, + ) + } else { + if (!metadata.hasArtwork()) { + Box( + modifier = Modifier + .size(72.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.onBackground), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Outlined.GraphicEq, + contentDescription = null, + tint = MaterialTheme.colorScheme.background, + modifier = Modifier + .size(32.dp), + ) + } + } + } + } + if (waveform == null) { + // Display the info below the player + AudioInfoView( + info = info, + metadata = metadata, + ) + } + } + MediaPlayerControllerView( + state = mediaPlayerControllerState, + onTogglePlay = { + if (exoPlayer.isPlaying) { + exoPlayer.pause() + } else { + if (exoPlayer.playbackState == Player.STATE_ENDED) { + exoPlayer.seekTo(0) + } else { + exoPlayer.play() + } + } + }, + onSeekChange = { + if (exoPlayer.isPlaying.not()) { + exoPlayer.play() + } + exoPlayer.seekTo(it.toLong()) + }, + onToggleMute = { + // Cannot happen for audio files + }, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(bottom = bottomPaddingInPixels.toDp()), + ) + } + + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_CREATE -> exoPlayer.addListener(playerListener) + Lifecycle.Event.ON_RESUME -> exoPlayer.prepare() + Lifecycle.Event.ON_PAUSE -> exoPlayer.pause() + Lifecycle.Event.ON_DESTROY -> { + exoPlayer.release() + exoPlayer.removeListener(playerListener) + } + else -> Unit + } + } +} + +@Composable +fun ColumnScope.AudioInfoView( + info: MediaInfo?, + metadata: MediaMetadata?, +) { + // Render the info about the file and from the metadata + val metaDataInfo = metadata.buildInfo() + if (metaDataInfo.isNotEmpty()) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = metaDataInfo, + style = ElementTheme.typography.fontBodyMdRegular, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary + ) + } + if (info != null) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = info.filename, + maxLines = 2, + style = ElementTheme.typography.fontBodyLgRegular, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = formatFileExtensionAndSize(info.fileExtension, info.formattedFileSize), + style = ElementTheme.typography.fontBodyMdRegular, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary + ) + } +} + +@PreviewsDayNight +@Composable +internal fun MediaFileViewPreview( + @PreviewParameter(MediaInfoAudioProvider::class) info: MediaInfo +) = ElementPreview { + MediaAudioView( + modifier = Modifier.fillMaxSize(), + bottomPaddingInPixels = 0, + localMediaViewState = rememberLocalMediaViewState(), + info = info, + localMedia = null, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt new file mode 100644 index 0000000000..87f9bc3735 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.audio + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo + +open class MediaInfoAudioProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + anAudioMediaInfo(), + anAudioMediaInfo( + waveForm = aWaveForm(), + ), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt new file mode 100644 index 0000000000..d49559e1df --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.audio + +import androidx.media3.common.MediaMetadata + +fun MediaMetadata?.hasArtwork(): Boolean { + return this?.artworkData != null || this?.artworkUri != null +} + +fun MediaMetadata?.buildInfo(): String { + this ?: return "" + return buildString { + if (artist != null) { + append(artist) + } + if (title != null) { + if (isNotEmpty()) { + append(" - ") + } + append(title) + } + if (recordingYear != null) { + if (isNotEmpty()) { + append(" - ") + } + append(recordingYear) + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt index 980f9eba89..08d906dd98 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt @@ -10,12 +10,10 @@ package io.element.android.libraries.mediaviewer.impl.local.file import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo -import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo open class MediaInfoFileProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aPdfMediaInfo(), - anAudioMediaInfo(), ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt index c4e4b913a7..0316e5088e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt @@ -7,10 +7,16 @@ package io.element.android.libraries.mediaviewer.impl.local.video +import androidx.annotation.FloatRange + data class MediaPlayerControllerState( val isVisible: Boolean, val isPlaying: Boolean, val progressInMillis: Long, val durationInMillis: Long, + val canMute: Boolean, val isMuted: Boolean, -) +) { + @FloatRange(from = 0.0, to = 1.0) + val progressAsFloat = (progressInMillis.toFloat() / durationInMillis.toFloat()).coerceIn(0f, 1f) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt index 78059bd4eb..c26c16513f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt @@ -18,6 +18,9 @@ open class MediaPlayerControllerStateProvider : PreviewParameterProvider aMediaViewerState( mediaBottomSheetState = aMediaDeleteConfirmationState(), ), + anAudioMediaInfo( + waveForm = aWaveForm(), + ).let { + aMediaViewerState( + downloadedMedia = AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + mediaInfo = it, + ) + }, ) } From 45a62a7d8fe3f02588e51424653d5e68839ed963 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:05:57 +0100 Subject: [PATCH 115/203] We do not need ExoPlayerWrapper anymore because we are not displaying the embedded controller. --- .../impl/local/audio/MediaAudioView.kt | 3 +- .../impl/local/video/ExoPlayerWrapper.kt | 39 ------------------- .../impl/local/video/MediaVideoView.kt | 2 +- 3 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerWrapper.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 667aa7156b..073e5b2492 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -69,7 +69,6 @@ import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.video.ExoPlayerForPreview -import io.element.android.libraries.mediaviewer.impl.local.video.ExoPlayerWrapper import io.element.android.libraries.mediaviewer.impl.local.video.MediaPlayerControllerState import io.element.android.libraries.mediaviewer.impl.local.video.MediaPlayerControllerView import kotlinx.collections.immutable.toPersistentList @@ -91,7 +90,7 @@ fun MediaAudioView( } else { val context = LocalContext.current remember { - ExoPlayerWrapper.create(context) + ExoPlayer.Builder(context).build() } } ExoPlayerMediaAudioView( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerWrapper.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerWrapper.kt deleted file mode 100644 index fef307c155..0000000000 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerWrapper.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.local.video - -import android.content.Context -import androidx.media3.common.Player -import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.ExoPlayer - -/** - * Wrapper around ExoPlayer to disable some commands. - * Necessary to hide the settings wheels from the player. - */ -@UnstableApi -class ExoPlayerWrapper(private val exoPlayer: ExoPlayer) : ExoPlayer by exoPlayer { - override fun isCommandAvailable(command: Int): Boolean { - return availableCommands.contains(command) - } - - override fun getAvailableCommands(): Player.Commands { - return exoPlayer.availableCommands - .buildUpon() - .remove(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS) - .build() - } - - companion object { - fun create(context: Context): ExoPlayer { - return ExoPlayerWrapper( - ExoPlayer.Builder(context).build() - ) - } - } -} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 1461cbdcb1..f14fc9c77a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -64,7 +64,7 @@ fun MediaVideoView( } else { val context = LocalContext.current remember { - ExoPlayerWrapper.create(context) + ExoPlayer.Builder(context).build() } } ExoPlayerMediaVideoView( From db8cca7d0cc9a2f8a3204665a51ea2d5232b573a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:07:38 +0100 Subject: [PATCH 116/203] Move some player controller to a dedicated package. --- .../impl/local/{video => player}/ExoPlayerForPreview.kt | 2 +- .../impl/local/{video => player}/MediaPlayerControllerState.kt | 2 +- .../{video => player}/MediaPlayerControllerStateProvider.kt | 2 +- .../impl/local/{video => player}/MediaPlayerControllerView.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/{video => player}/ExoPlayerForPreview.kt (99%) rename libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/{video => player}/MediaPlayerControllerState.kt (89%) rename libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/{video => player}/MediaPlayerControllerStateProvider.kt (94%) rename libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/{video => player}/MediaPlayerControllerView.kt (99%) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerForPreview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt similarity index 99% rename from libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerForPreview.kt rename to libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt index c517637576..0626c78f28 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerForPreview.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt @@ -11,7 +11,7 @@ "DEPRECATION", ) -package io.element.android.libraries.mediaviewer.impl.local.video +package io.element.android.libraries.mediaviewer.impl.local.player import android.annotation.SuppressLint import android.media.AudioDeviceInfo diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt similarity index 89% rename from libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt rename to libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt index 0316e5088e..41f6225ee9 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.libraries.mediaviewer.impl.local.video +package io.element.android.libraries.mediaviewer.impl.local.player import androidx.annotation.FloatRange diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt similarity index 94% rename from libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt rename to libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt index c26c16513f..1528c619c3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.libraries.mediaviewer.impl.local.video +package io.element.android.libraries.mediaviewer.impl.local.player import androidx.compose.ui.tooling.preview.PreviewParameterProvider diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt similarity index 99% rename from libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerView.kt rename to libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt index cd105ec6ec..5873bdc6f0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaPlayerControllerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.libraries.mediaviewer.impl.local.video +package io.element.android.libraries.mediaviewer.impl.local.player import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn From 497e5fdcf75b42a5fc7ac8a036e1f374d08966f6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:12:00 +0100 Subject: [PATCH 117/203] Move fun to a Factory file --- .../impl/local/audio/MediaAudioView.kt | 17 +++-------- .../impl/local/player/ExoPlayerFactory.kt | 28 +++++++++++++++++++ .../impl/local/video/MediaVideoView.kt | 14 +++------- 3 files changed, 36 insertions(+), 23 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 073e5b2492..a9c052be14 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -67,10 +67,10 @@ import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAn import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState +import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerState +import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerView +import io.element.android.libraries.mediaviewer.impl.local.player.rememberExoPlayer import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState -import io.element.android.libraries.mediaviewer.impl.local.video.ExoPlayerForPreview -import io.element.android.libraries.mediaviewer.impl.local.video.MediaPlayerControllerState -import io.element.android.libraries.mediaviewer.impl.local.video.MediaPlayerControllerView import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.delay @@ -83,16 +83,7 @@ fun MediaAudioView( info: MediaInfo?, modifier: Modifier = Modifier, ) { - val exoPlayer = if (LocalInspectionMode.current) { - remember { - ExoPlayerForPreview() - } - } else { - val context = LocalContext.current - remember { - ExoPlayer.Builder(context).build() - } - } + val exoPlayer = rememberExoPlayer() ExoPlayerMediaAudioView( localMediaViewState = localMediaViewState, bottomPaddingInPixels = bottomPaddingInPixels, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt new file mode 100644 index 0000000000..0baf3d7e9d --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.player + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.media3.exoplayer.ExoPlayer + +@Composable +fun rememberExoPlayer(): ExoPlayer { + return if (LocalInspectionMode.current) { + remember { + ExoPlayerForPreview() + } + } else { + val context = LocalContext.current + remember { + ExoPlayer.Builder(context).build() + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index f14fc9c77a..5dcdc0d338 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -45,6 +45,9 @@ import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState +import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerState +import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerView +import io.element.android.libraries.mediaviewer.impl.local.player.rememberExoPlayer import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.seconds @@ -57,16 +60,7 @@ fun MediaVideoView( localMedia: LocalMedia?, modifier: Modifier = Modifier, ) { - val exoPlayer = if (LocalInspectionMode.current) { - remember { - ExoPlayerForPreview() - } - } else { - val context = LocalContext.current - remember { - ExoPlayer.Builder(context).build() - } - } + val exoPlayer = rememberExoPlayer() ExoPlayerMediaVideoView( localMediaViewState = localMediaViewState, bottomPaddingInPixels = bottomPaddingInPixels, From e42846aa3e94febe38cf9046dd71993e57febc2f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:14:13 +0100 Subject: [PATCH 118/203] Remember the listener --- .../impl/local/audio/MediaAudioView.kt | 42 +++++++++-------- .../impl/local/video/MediaVideoView.kt | 46 ++++++++++--------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index a9c052be14..9e42e29ec4 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -131,30 +131,32 @@ private fun ExoPlayerMediaAudioView( localMediaViewState.playableState = playableState - val playerListener = object : Player.Listener { - override fun onRenderedFirstFrame() { - localMediaViewState.isReady = true - } + val playerListener = remember { + object : Player.Listener { + override fun onRenderedFirstFrame() { + localMediaViewState.isReady = true + } - override fun onIsPlayingChanged(isPlaying: Boolean) { - mediaPlayerControllerState = mediaPlayerControllerState.copy( - isPlaying = isPlaying, - ) - } + override fun onIsPlayingChanged(isPlaying: Boolean) { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + isPlaying = isPlaying, + ) + } - override fun onTimelineChanged(timeline: Timeline, reason: Int) { - if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) { - exoPlayer.duration.takeIf { it >= 0 } - ?.let { - mediaPlayerControllerState = mediaPlayerControllerState.copy( - durationInMillis = it, - ) - } + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) { + exoPlayer.duration.takeIf { it >= 0 } + ?.let { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + durationInMillis = it, + ) + } + } } - } - override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { - metadata = mediaMetadata + override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { + metadata = mediaMetadata + } } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 5dcdc0d338..7bddcb9b4d 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -102,31 +102,33 @@ private fun ExoPlayerMediaVideoView( localMediaViewState.playableState = playableState - val playerListener = object : Player.Listener { - override fun onRenderedFirstFrame() { - localMediaViewState.isReady = true - } + val playerListener = remember { + object : Player.Listener { + override fun onRenderedFirstFrame() { + localMediaViewState.isReady = true + } - override fun onIsPlayingChanged(isPlaying: Boolean) { - mediaPlayerControllerState = mediaPlayerControllerState.copy( - isPlaying = isPlaying, - ) - } + override fun onIsPlayingChanged(isPlaying: Boolean) { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + isPlaying = isPlaying, + ) + } - override fun onVolumeChanged(volume: Float) { - mediaPlayerControllerState = mediaPlayerControllerState.copy( - isMuted = volume == 0f, - ) - } + override fun onVolumeChanged(volume: Float) { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + isMuted = volume == 0f, + ) + } - override fun onTimelineChanged(timeline: Timeline, reason: Int) { - if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) { - exoPlayer.duration.takeIf { it >= 0 } - ?.let { - mediaPlayerControllerState = mediaPlayerControllerState.copy( - durationInMillis = it, - ) - } + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) { + exoPlayer.duration.takeIf { it >= 0 } + ?.let { + mediaPlayerControllerState = mediaPlayerControllerState.copy( + durationInMillis = it, + ) + } + } } } } From 7504a5191778e0a20ea06f6d4016e91c234cd6ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:16:40 +0100 Subject: [PATCH 119/203] Extract methods to toggle play and seek the player --- .../impl/local/audio/MediaAudioView.kt | 22 ++++---------- .../impl/local/player/ExoPlayerExtensions.kt | 30 +++++++++++++++++++ .../impl/local/video/MediaVideoView.kt | 17 +++-------- 3 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 9e42e29ec4..edacda6fad 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -69,7 +69,9 @@ import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerView +import io.element.android.libraries.mediaviewer.impl.local.player.seekToEnsurePlaying import io.element.android.libraries.mediaviewer.impl.local.player.rememberExoPlayer +import io.element.android.libraries.mediaviewer.impl.local.player.togglePlay import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.delay @@ -241,10 +243,7 @@ private fun ExoPlayerMediaAudioView( showCursor = true, waveform = waveform.toPersistentList(), onSeek = { - if (exoPlayer.isPlaying.not()) { - exoPlayer.play() - } - exoPlayer.seekTo((it * exoPlayer.duration).toLong()) + exoPlayer.seekToEnsurePlaying((it * exoPlayer.duration).toLong()) }, seekEnabled = true, ) @@ -279,21 +278,10 @@ private fun ExoPlayerMediaAudioView( MediaPlayerControllerView( state = mediaPlayerControllerState, onTogglePlay = { - if (exoPlayer.isPlaying) { - exoPlayer.pause() - } else { - if (exoPlayer.playbackState == Player.STATE_ENDED) { - exoPlayer.seekTo(0) - } else { - exoPlayer.play() - } - } + exoPlayer.togglePlay() }, onSeekChange = { - if (exoPlayer.isPlaying.not()) { - exoPlayer.play() - } - exoPlayer.seekTo(it.toLong()) + exoPlayer.seekToEnsurePlaying(it.toLong()) }, onToggleMute = { // Cannot happen for audio files diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt new file mode 100644 index 0000000000..2de6d62065 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.player + +import androidx.media3.common.Player +import androidx.media3.exoplayer.ExoPlayer + +fun ExoPlayer.togglePlay() { + if (isPlaying) { + pause() + } else { + if (playbackState == Player.STATE_ENDED) { + seekTo(0) + } else { + play() + } + } +} + +fun ExoPlayer.seekToEnsurePlaying(positionMs: Long) { + if (isPlaying.not()) { + play() + } + seekTo(positionMs) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 7bddcb9b4d..9f5317d933 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -47,7 +47,9 @@ import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerView +import io.element.android.libraries.mediaviewer.impl.local.player.seekToEnsurePlaying import io.element.android.libraries.mediaviewer.impl.local.player.rememberExoPlayer +import io.element.android.libraries.mediaviewer.impl.local.player.togglePlay import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.seconds @@ -208,22 +210,11 @@ private fun ExoPlayerMediaVideoView( state = mediaPlayerControllerState, onTogglePlay = { autoHideController++ - if (exoPlayer.isPlaying) { - exoPlayer.pause() - } else { - if (exoPlayer.playbackState == Player.STATE_ENDED) { - exoPlayer.seekTo(0) - } else { - exoPlayer.play() - } - } + exoPlayer.togglePlay() }, onSeekChange = { autoHideController++ - if (exoPlayer.isPlaying.not()) { - exoPlayer.play() - } - exoPlayer.seekTo(it.toLong()) + exoPlayer.seekToEnsurePlaying(it.toLong()) }, onToggleMute = { autoHideController++ From 8809dcd0f58896bfa947726404e4f44cff33e12d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:27:34 +0100 Subject: [PATCH 120/203] Cleanup --- .../impl/local/audio/MediaAudioView.kt | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index edacda6fad..8019f87371 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -14,7 +14,6 @@ import android.widget.FrameLayout import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -69,8 +68,8 @@ import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerView -import io.element.android.libraries.mediaviewer.impl.local.player.seekToEnsurePlaying import io.element.android.libraries.mediaviewer.impl.local.player.rememberExoPlayer +import io.element.android.libraries.mediaviewer.impl.local.player.seekToEnsurePlaying import io.element.android.libraries.mediaviewer.impl.local.player.togglePlay import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import kotlinx.collections.immutable.toPersistentList @@ -270,6 +269,7 @@ private fun ExoPlayerMediaAudioView( if (waveform == null) { // Display the info below the player AudioInfoView( + modifier = Modifier.padding(horizontal = 16.dp), info = info, metadata = metadata, ) @@ -308,40 +308,46 @@ private fun ExoPlayerMediaAudioView( } @Composable -fun ColumnScope.AudioInfoView( +private fun AudioInfoView( info: MediaInfo?, metadata: MediaMetadata?, + modifier: Modifier = Modifier, ) { - // Render the info about the file and from the metadata - val metaDataInfo = metadata.buildInfo() - if (metaDataInfo.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = metaDataInfo, - style = ElementTheme.typography.fontBodyMdRegular, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary - ) - } - if (info != null) { - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = info.filename, - maxLines = 2, - style = ElementTheme.typography.fontBodyLgRegular, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = formatFileExtensionAndSize(info.fileExtension, info.formattedFileSize), - style = ElementTheme.typography.fontBodyMdRegular, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary - ) + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + // Render the info about the file and from the metadata + val metaDataInfo = metadata.buildInfo() + if (metaDataInfo.isNotEmpty()) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = metaDataInfo, + style = ElementTheme.typography.fontBodyMdRegular, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary + ) + } + if (info != null) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = info.filename, + maxLines = 2, + style = ElementTheme.typography.fontBodyLgRegular, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = formatFileExtensionAndSize(info.fileExtension, info.formattedFileSize), + style = ElementTheme.typography.fontBodyMdRegular, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary + ) + } } } From 73f175a9a1e04ba12c8488bcac2d9c639f8a3b9b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:33:09 +0100 Subject: [PATCH 121/203] Rename preview --- .../libraries/mediaviewer/impl/local/audio/MediaAudioView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 8019f87371..05c96cc8af 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -353,7 +353,7 @@ private fun AudioInfoView( @PreviewsDayNight @Composable -internal fun MediaFileViewPreview( +internal fun MediaAudioViewPreview( @PreviewParameter(MediaInfoAudioProvider::class) info: MediaInfo ) = ElementPreview { MediaAudioView( From 9ea08fb61eea9b4e4d427ac6d63345ab97150c75 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2024 17:37:57 +0100 Subject: [PATCH 122/203] Fix test and add tests. --- .../impl/gallery/DefaultEventItemFactoryTest.kt | 17 ++++++++++++++--- .../impl/gallery/MediaItemsPostProcessorTest.kt | 14 ++++++++++++++ .../impl/local/AndroidLocalMediaFactoryTest.kt | 3 ++- .../mediaviewer/test/FakeLocalMediaFactory.kt | 1 + 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt index 36b767e870..3478f8f867 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -50,6 +50,7 @@ import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import org.junit.Test import kotlin.time.Duration.Companion.seconds @@ -163,6 +164,7 @@ class DefaultEventItemFactoryTest { senderAvatar = null, dateSent = "0 Day false", dateSentFull = "0 Full false", + waveform = null, ), mediaSource = MediaSource(""), ) @@ -211,6 +213,7 @@ class DefaultEventItemFactoryTest { senderAvatar = null, dateSent = "0 Day false", dateSentFull = "0 Full false", + waveform = null, ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -242,7 +245,7 @@ class DefaultEventItemFactoryTest { ) ) assertThat(result).isEqualTo( - MediaItem.File( + MediaItem.Audio( id = A_UNIQUE_ID, eventId = AN_EVENT_ID, mediaInfo = MediaInfo( @@ -256,8 +259,11 @@ class DefaultEventItemFactoryTest { senderAvatar = null, dateSent = "0 Day false", dateSentFull = "0 Full false", + waveform = null, ), mediaSource = MediaSource(""), + duration = "7:36", + waveform = null, ) ) } @@ -305,6 +311,7 @@ class DefaultEventItemFactoryTest { senderAvatar = null, dateSent = "0 Day false", dateSentFull = "0 Full false", + waveform = null, ), mediaSource = MediaSource(""), thumbnailSource = null, @@ -333,7 +340,7 @@ class DefaultEventItemFactoryTest { ), details = AudioDetails( duration = 456.seconds, - waveform = persistentListOf(), + waveform = persistentListOf(1f, 2f), ) ) ) @@ -341,7 +348,7 @@ class DefaultEventItemFactoryTest { ) ) assertThat(result).isEqualTo( - MediaItem.File( + MediaItem.Audio( id = A_UNIQUE_ID, eventId = AN_EVENT_ID, mediaInfo = MediaInfo( @@ -355,8 +362,11 @@ class DefaultEventItemFactoryTest { senderAvatar = null, dateSent = "0 Day false", dateSentFull = "0 Full false", + waveform = listOf(1f, 2f).toImmutableList(), ), mediaSource = MediaSource(""), + duration = "7:36", + waveform = listOf(1f, 2f).toImmutableList(), ) ) } @@ -403,6 +413,7 @@ class DefaultEventItemFactoryTest { senderAvatar = null, dateSent = "0 Day false", dateSentFull = "0 Full false", + waveform = null, ), mediaSource = MediaSource(""), thumbnailSource = null, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt index 75a911f1dc..cf32409248 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt @@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemAudio import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemDateSeparator import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage @@ -23,6 +24,9 @@ class MediaItemsPostProcessorTest { private val file1 = aMediaItemFile(id = UniqueId("1")) private val file2 = aMediaItemFile(id = UniqueId("2")) private val file3 = aMediaItemFile(id = UniqueId("3")) + private val audio1 = aMediaItemAudio(id = UniqueId("1")) + private val audio2 = aMediaItemAudio(id = UniqueId("2")) + private val audio3 = aMediaItemAudio(id = UniqueId("3")) private val image1 = aMediaItemImage(id = UniqueId("1")) private val image2 = aMediaItemImage(id = UniqueId("2")) private val image3 = aMediaItemImage(id = UniqueId("3")) @@ -68,6 +72,7 @@ class MediaItemsPostProcessorTest { fun `process will reorder files`() { test( mediaItems = listOf( + audio1, file3, file2, file1, @@ -79,6 +84,7 @@ class MediaItemsPostProcessorTest { file1, file2, file3, + audio1, ), ) } @@ -106,6 +112,7 @@ class MediaItemsPostProcessorTest { fun `process will split images, videos and files`() { test( mediaItems = listOf( + audio1, file1, image1, video1, @@ -119,6 +126,7 @@ class MediaItemsPostProcessorTest { expectedFileItems = listOf( date1, file1, + audio1, ), ) } @@ -155,6 +163,9 @@ class MediaItemsPostProcessorTest { fun `process will handle complex case`() { test( mediaItems = listOf( + audio3, + audio2, + audio1, file1, image1, video1, @@ -178,6 +189,9 @@ class MediaItemsPostProcessorTest { expectedFileItems = listOf( date1, file1, + audio1, + audio2, + audio3, date3, file3, loading1, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index d1f0f745f8..11d8426c09 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -48,7 +48,8 @@ class AndroidLocalMediaFactoryTest { senderName = A_USER_NAME, senderAvatar = null, dateSent = "12:34", - dateSentFull = "full" + dateSentFull = "full", + waveform = null, ) ) } diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index 39014f90cb..f5f28007d4 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -42,6 +42,7 @@ class FakeLocalMediaFactory( senderAvatar = null, dateSent = null, dateSentFull = null, + waveform = null, ) return aLocalMedia(uri, mediaInfo) } From efd745a04202532940b3ee3f58de7afc9eb18107 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 12 Dec 2024 16:57:08 +0000 Subject: [PATCH 123/203] Update screenshots --- ...nsystem.components.media_WaveformPlaybackView_Day_0_en.png | 4 ++-- ...ystem.components.media_WaveformPlaybackView_Night_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 4 ++-- ...s.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en.png | 3 +++ ...s.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en.png | 3 +++ ...mediaviewer.impl.local.audio_MediaAudioView_Night_0_en.png | 3 +++ ...mediaviewer.impl.local.audio_MediaAudioView_Night_1_en.png | 3 +++ ...ies.mediaviewer.impl.local.file_MediaFileView_Day_1_en.png | 3 --- ...s.mediaviewer.impl.local.file_MediaFileView_Night_1_en.png | 3 --- ....impl.local.player_MediaPlayerControllerView_Day_0_en.png} | 0 ....impl.local.player_MediaPlayerControllerView_Day_1_en.png} | 0 ...r.impl.local.player_MediaPlayerControllerView_Day_2_en.png | 3 +++ ...mpl.local.player_MediaPlayerControllerView_Night_0_en.png} | 0 ...mpl.local.player_MediaPlayerControllerView_Night_1_en.png} | 0 ...impl.local.player_MediaPlayerControllerView_Night_2_en.png | 3 +++ ...ibraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png | 3 +++ ...libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png | 4 ++-- 27 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Day_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Night_1_en.png rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_0_en.png => libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_1_en.png => libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en.png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_2_en.png rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_0_en.png => libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_1_en.png => libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en.png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en.png index 3c573bace1..18fe30dd7a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbe0bf5ff3c5128405fe0af2af344140a655d93826bf7591393dbd4732a7b729 -size 8383 +oid sha256:470ea6854c3786db7935c55a852637c907665326a61a5dcf33c66f0710406c09 +size 9650 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en.png index 2acb4b3d25..0ef108d30b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ec9068b2f5b7bdf0bcf6ede6c2ea02040c1a77b1054c1fbf45fe5feb1ab78e5 -size 8147 +oid sha256:1b1ef50d42e57a1465de82d538a573c41a73a133890ecf84bed0317d38e33440 +size 9385 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png new file mode 100644 index 0000000000..dc645dea5e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d07440581dc64c48c047b8895520c7f46213e4806053b6a5fa4b63c15386c8c +size 12410 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png new file mode 100644 index 0000000000..6ec428fbfb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9beef21701df1566b27d7b71808de32a87ae439b5213f7e081d821e60add5b36 +size 16302 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png new file mode 100644 index 0000000000..aa05357dd7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0adeab82d5197daf0eb408f66ee605e0ae3862ac08de0c6a5093a957f724718c +size 39939 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png new file mode 100644 index 0000000000..a45dd44c3c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:835cdc9b99cdd8eedfe2f59032344d07681cd8bbab39a0f15e340abd80c035ff +size 10979 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png new file mode 100644 index 0000000000..0fba373d86 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1813f6e96713fc8c11eca36475ac51b3162f88c71bbe4e9fe7cb0c9a58314ab4 +size 11719 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png new file mode 100644 index 0000000000..87028a2acd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1df8cd0cbef387b05959a423c20cf92605e1c671404aef4ccbb79048d4fc4a6f +size 15518 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png new file mode 100644 index 0000000000..12949183c1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de63930be672d83e9bc508c6a84911f6a309c2dbac7b608c3bfef5e8a37fc49f +size 38349 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png new file mode 100644 index 0000000000..d80a5bcf08 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:836b153cee837cb5c0ba375b587f6bfa6e34eb9f88e4198483f956ebff91048a +size 10234 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png index 7a192f89f4..2731706a84 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce8fec32ec1a902edb12d9580ca73d0cc5a9838f91d31dbbf8329326b9fa1a68 -size 39676 +oid sha256:237067f8a59de7f88c313d78716116c5dfe35d9112ad3d42a61f172de2637415 +size 40888 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png index 41f7c7f5fd..c560e17b09 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:946183ccbaf14471579f3b48861715d04ed45d6352d36126a419de7e6f362bfd -size 37632 +oid sha256:9304dc29a88d7829aebfb6de1bab40fde20fcaef0fb92efdf9135d4df92c4442 +size 38694 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en.png new file mode 100644 index 0000000000..70d447adcd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c7d4201ed9aa37995f4ab8ac982404f59e77374f316a057685886f14e698c35 +size 24680 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en.png new file mode 100644 index 0000000000..41b0cc2f9a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b94fd31b7ed71eacfe8f136bfd59405b85d31a0fe557800311794f4ba7006271 +size 22749 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en.png new file mode 100644 index 0000000000..876fab066d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6520b0faf9c22ee1d9b1a088b29fc07e3d8004db5a342cd3bff189d844aace6e +size 24649 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en.png new file mode 100644 index 0000000000..50727c043f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dc15cba85c8cc376b780df648b4fa265c054de07a4bab426569ac26be03fec1 +size 22784 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Day_1_en.png deleted file mode 100644 index c4d024965e..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5307e90428957819812269b9b3e0c6e9d59238141d54cd959aa5506290797a35 -size 11587 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Night_1_en.png deleted file mode 100644 index 37f1c2ed22..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.file_MediaFileView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52354bcf471b14e38e582cc29f73407c8ca65026b2b7c6db3d3b28ec94950679 -size 11413 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_1_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_2_en.png new file mode 100644 index 0000000000..b5b75d1b63 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6d04e0ee068682ebb0a3842ba73407855f2b83b7389d26fa0f3e2ec20d42dc8 +size 7389 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_1_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_2_en.png new file mode 100644 index 0000000000..336b57074a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1211fac03e492532b1e90608fd931b8fcc15dac695ef5610069bf512c2a5fef1 +size 7548 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png new file mode 100644 index 0000000000..e18fc2c87b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a6a599e68f0955d84ea737603f0db83be412433691f7b7b3729a01999808830 +size 25827 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png index 54fda061bc..a155681e56 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c786dc4f2ec41fc334cd67febc105670c999da57cec1ea263daff2358ff5c766 -size 14406 +oid sha256:c7642e9e20a551e59f4529c52fa7fbe5b3f4dfcb8c26caeb716ccb2bdfc63dde +size 27176 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png index 8e2a70f3ce..bd1487d158 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f74e1b75f3e5a03df1b6b0e4509a4bf5da033c4f68cf88705ed136b471ae38c -size 14691 +oid sha256:1fd53c24dd38a12b4ceefa54e1d6d096d8f443d1cdd1d1f1cc71f92c1d603a51 +size 27419 From 54c26857bdc7aeea98a3582c29fe59bb20636953 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Dec 2024 10:43:59 +0100 Subject: [PATCH 124/203] Ensure waveform is not null for voice message. --- .../libraries/mediaviewer/impl/gallery/EventItemFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index 4baa1d0ee1..ede4c491ea 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -196,7 +196,7 @@ class EventItemFactory @Inject constructor( senderAvatar = event.senderProfile.getAvatarUrl(), dateSent = dateSent, dateSentFull = dateSentFull, - waveform = type.details?.waveform, + waveform = type.details?.waveform.orEmpty(), ), mediaSource = type.source, duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), From f74258d794196b5703e4fd96b312722a85655f1b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Dec 2024 15:30:24 +0100 Subject: [PATCH 125/203] Extract voice message player to its own module --- features/messages/impl/build.gradle.kts | 1 + .../event/TimelineItemEventContentView.kt | 2 +- .../components/event/TimelineItemVoiceView.kt | 6 +- .../di/FakeTimelineItemPresenterFactories.kt | 4 +- .../composer/VoiceMessageComposerPresenter.kt | 2 +- .../timeline/VoiceMessagePresenter.kt | 107 +-------------- .../VoiceMessageComposerPresenterTest.kt | 2 +- libraries/voiceplayer/api/build.gradle.kts | 23 ++++ .../voiceplayer/api}/VoiceMessageEvents.kt | 2 +- .../voiceplayer/api}/VoiceMessageException.kt | 6 +- .../api/VoiceMessagePresenterFactory.kt | 23 ++++ .../voiceplayer/api}/VoiceMessageState.kt | 2 +- .../api}/VoiceMessageStateProvider.kt | 2 +- libraries/voiceplayer/impl/build.gradle.kts | 43 ++++++ .../DefaultVoiceMessagePresenterFactory.kt | 50 +++++++ .../impl}/VoiceMessageMediaRepo.kt | 2 +- .../voiceplayer/impl}/VoiceMessagePlayer.kt | 2 +- .../voiceplayer/impl/VoiceMessagePresenter.kt | 124 ++++++++++++++++++ .../impl}/DefaultVoiceMessageMediaRepoTest.kt | 2 +- .../impl}/DefaultVoiceMessagePlayerTest.kt | 2 +- .../impl}/FakeVoiceMessageMediaRepo.kt | 2 +- .../impl}/VoiceMessagePresenterTest.kt | 52 ++++---- .../kotlin/extension/DependencyHandleScope.kt | 1 + 23 files changed, 321 insertions(+), 141 deletions(-) create mode 100644 libraries/voiceplayer/api/build.gradle.kts rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api}/VoiceMessageEvents.kt (80%) rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages => libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api}/VoiceMessageException.kt (82%) create mode 100644 libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api}/VoiceMessageState.kt (86%) rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api}/VoiceMessageStateProvider.kt (95%) create mode 100644 libraries/voiceplayer/impl/build.gradle.kts create mode 100644 libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl}/VoiceMessageMediaRepo.kt (98%) rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl}/VoiceMessagePlayer.kt (98%) create mode 100644 libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt rename {features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl}/DefaultVoiceMessageMediaRepoTest.kt (98%) rename {features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl}/DefaultVoiceMessagePlayerTest.kt (99%) rename {features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl}/FakeVoiceMessageMediaRepo.kt (89%) rename {features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline => libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl}/VoiceMessagePresenterTest.kt (85%) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index e87d90cbdf..f5b6520996 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation(projects.libraries.permissions.api) implementation(projects.libraries.preferences.api) implementation(projects.libraries.roomselect.api) + implementation(projects.libraries.voiceplayer.api) implementation(projects.libraries.voicerecorder.api) implementation(projects.libraries.mediaplayer.api) implementation(projects.libraries.uiUtils) 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 35a8cda293..3fa786f902 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 @@ -29,8 +29,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent -import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.voiceplayer.api.VoiceMessageState @Composable fun TimelineItemEventContentView( 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 f5cee592e2..365b97f9fc 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,9 +40,6 @@ 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.TimelineItemVoiceContentProvider -import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents -import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState -import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageStateProvider import io.element.android.libraries.androidutils.accessibility.isScreenReaderEnabled import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView import io.element.android.libraries.designsystem.preview.ElementPreview @@ -52,6 +49,9 @@ 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.ui.strings.CommonStrings +import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider import kotlinx.coroutines.delay @Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt index 28a0ff094f..47b2b4eba5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt @@ -8,9 +8,9 @@ package io.element.android.features.messages.impl.timeline.di import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent -import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState -import io.element.android.features.messages.impl.voicemessages.timeline.aVoiceMessageState import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import io.element.android.libraries.voiceplayer.api.aVoiceMessageState /** * A fake [TimelineItemPresenterFactories] for screenshot tests. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt index cc6d134802..68ebead3b6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt @@ -21,7 +21,6 @@ import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import im.vector.app.features.analytics.plan.Composer import io.element.android.features.messages.api.MessageComposerContext -import io.element.android.features.messages.impl.voicemessages.VoiceMessageException import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.permissions.api.PermissionsEvents @@ -29,6 +28,7 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState +import io.element.android.libraries.voiceplayer.api.VoiceMessageException import io.element.android.libraries.voicerecorder.api.VoiceRecorder import io.element.android.libraries.voicerecorder.api.VoiceRecorderState import io.element.android.services.analytics.api.AnalyticsService diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt index 038de6730d..f515fca9d6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt @@ -8,11 +8,6 @@ package io.element.android.features.messages.impl.voicemessages.timeline import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import com.squareup.anvil.annotations.ContributesTo import dagger.Binds import dagger.Module @@ -23,17 +18,10 @@ import dagger.multibindings.IntoMap import io.element.android.features.messages.impl.timeline.di.TimelineItemEventContentKey import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent -import io.element.android.features.messages.impl.voicemessages.VoiceMessageException -import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runUpdatingState -import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.ui.utils.time.formatShort -import io.element.android.services.analytics.api.AnalyticsService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.milliseconds +import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory +import io.element.android.libraries.voiceplayer.api.VoiceMessageState @Module @ContributesTo(RoomScope::class) @@ -45,9 +33,7 @@ interface VoiceMessagePresenterModule { } class VoiceMessagePresenter @AssistedInject constructor( - voiceMessagePlayerFactory: VoiceMessagePlayer.Factory, - private val analyticsService: AnalyticsService, - private val scope: CoroutineScope, + voiceMessagePresenterFactory: VoiceMessagePresenterFactory, @Assisted private val content: TimelineItemVoiceContent, ) : Presenter { @AssistedFactory @@ -55,97 +41,16 @@ class VoiceMessagePresenter @AssistedInject constructor( override fun create(content: TimelineItemVoiceContent): VoiceMessagePresenter } - private val player = voiceMessagePlayerFactory.create( + private val presenter = voiceMessagePresenterFactory.createVoiceMessagePresenter( eventId = content.eventId, mediaSource = content.mediaSource, mimeType = content.mimeType, filename = content.filename, + duration = content.duration, ) - private val play = mutableStateOf>(AsyncData.Uninitialized) - @Composable override fun present(): VoiceMessageState { - val playerState by player.state.collectAsState( - VoiceMessagePlayer.State( - isReady = false, - isPlaying = false, - isEnded = false, - currentPosition = 0L, - duration = null - ) - ) - - val button by remember { - derivedStateOf { - when { - content.eventId == null -> VoiceMessageState.Button.Disabled - playerState.isPlaying -> VoiceMessageState.Button.Pause - play.value is AsyncData.Loading -> VoiceMessageState.Button.Downloading - play.value is AsyncData.Failure -> VoiceMessageState.Button.Retry - else -> VoiceMessageState.Button.Play - } - } - } - val duration by remember { - derivedStateOf { playerState.duration ?: content.duration.inWholeMilliseconds } - } - val progress by remember { - derivedStateOf { - playerState.currentPosition / duration.toFloat() - } - } - val time by remember { - derivedStateOf { - when { - playerState.isReady && !playerState.isEnded -> playerState.currentPosition - playerState.currentPosition > 0 -> playerState.currentPosition - else -> duration - }.milliseconds.formatShort() - } - } - val showCursor by remember { - derivedStateOf { - !play.value.isUninitialized() && !playerState.isEnded - } - } - - fun eventSink(event: VoiceMessageEvents) { - when (event) { - is VoiceMessageEvents.PlayPause -> { - if (playerState.isPlaying) { - player.pause() - } else if (playerState.isReady) { - player.play() - } else { - scope.launch { - play.runUpdatingState( - errorTransform = { - analyticsService.trackError( - VoiceMessageException.PlayMessageError("Error while trying to play voice message", it) - ) - it - }, - ) { - player.prepare().flatMap { - runCatching { player.play() } - } - } - } - } - } - is VoiceMessageEvents.Seek -> { - player.seekTo((event.percentage * duration).toLong()) - } - } - } - - return VoiceMessageState( - button = button, - progress = progress, - time = time, - showCursor = showCursor, - eventSink = { eventSink(it) }, - ) + return presenter.present() } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index a7cbaaffd2..f1389962b0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -18,7 +18,6 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Composer import io.element.android.features.messages.impl.messagecomposer.aReplyMode -import io.element.android.features.messages.impl.voicemessages.VoiceMessageException import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.media.AudioInfo @@ -36,6 +35,7 @@ import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState +import io.element.android.libraries.voiceplayer.api.VoiceMessageException import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule diff --git a/libraries/voiceplayer/api/build.gradle.kts b/libraries/voiceplayer/api/build.gradle.kts new file mode 100644 index 0000000000..5beb8ebbc0 --- /dev/null +++ b/libraries/voiceplayer/api/build.gradle.kts @@ -0,0 +1,23 @@ +import extension.setupAnvil + +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.libraries.voiceplayer.api" +} + +setupAnvil() + +dependencies { + implementation(libs.androidx.annotationjvm) + implementation(libs.coroutines.core) + implementation(projects.libraries.matrix.api) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageEvents.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt similarity index 80% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageEvents.kt rename to libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt index d124e57dcc..4ea61b8547 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageEvents.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.api sealed interface VoiceMessageEvents { data object PlayPause : VoiceMessageEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt similarity index 82% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt rename to libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt index ff3c5542f6..c35ec0b14c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt @@ -5,17 +5,19 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages +package io.element.android.libraries.voiceplayer.api -internal sealed class VoiceMessageException : Exception() { +sealed class VoiceMessageException : Exception() { data class FileException( override val message: String?, override val cause: Throwable? = null ) : VoiceMessageException() + data class PermissionMissing( override val message: String?, override val cause: Throwable? ) : VoiceMessageException() + data class PlayMessageError( override val message: String?, override val cause: Throwable? diff --git a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt new file mode 100644 index 0000000000..1e5c706b10 --- /dev/null +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.voiceplayer.api + +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaSource +import kotlin.time.Duration + +interface VoiceMessagePresenterFactory { + fun createVoiceMessagePresenter( + eventId: EventId?, + mediaSource: MediaSource, + mimeType: String?, + filename: String?, + duration: Duration, + ): Presenter +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt similarity index 86% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt rename to libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt index a7d0c15c13..5200614d57 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.api data class VoiceMessageState( val button: Button, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt similarity index 95% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt rename to libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt index 75d00240a2..a06181a4ee 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.api import androidx.compose.ui.tooling.preview.PreviewParameterProvider diff --git a/libraries/voiceplayer/impl/build.gradle.kts b/libraries/voiceplayer/impl/build.gradle.kts new file mode 100644 index 0000000000..155190e3bb --- /dev/null +++ b/libraries/voiceplayer/impl/build.gradle.kts @@ -0,0 +1,43 @@ +import extension.setupAnvil + +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.libraries.voiceplayer.impl" +} + +setupAnvil() + +dependencies { + api(projects.libraries.voiceplayer.api) + + implementation(projects.libraries.core) + implementation(projects.libraries.di) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.mediaplayer.api) + implementation(projects.libraries.uiUtils) + implementation(projects.services.analytics.api) + + implementation(libs.androidx.annotationjvm) + implementation(libs.coroutines.core) + + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.junit) + testImplementation(libs.test.truth) + testImplementation(libs.test.mockk) + testImplementation(libs.test.turbine) + testImplementation(libs.coroutines.core) + testImplementation(libs.coroutines.test) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.mediaplayer.test) + testImplementation(projects.services.analytics.test) + testImplementation(projects.tests.testutils) +} diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt new file mode 100644 index 0000000000..48807f5027 --- /dev/null +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.voiceplayer.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope +import javax.inject.Inject +import kotlin.time.Duration + +@ContributesBinding(RoomScope::class) +class DefaultVoiceMessagePresenterFactory @Inject constructor( + private val analyticsService: AnalyticsService, + private val scope: CoroutineScope, + private val voiceMessagePlayerFactory: VoiceMessagePlayer.Factory, +) : VoiceMessagePresenterFactory { + override fun createVoiceMessagePresenter( + eventId: EventId?, + mediaSource: MediaSource, + mimeType: String?, + filename: String?, + duration: Duration, + ): Presenter { + val player = voiceMessagePlayerFactory.create( + eventId = eventId, + mediaSource = mediaSource, + mimeType = mimeType, + filename = filename, + ) + + return VoiceMessagePresenter( + analyticsService = analyticsService, + scope = scope, + player = player, + eventId = eventId, + duration = duration, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt similarity index 98% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt rename to libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt index f1d8e5f987..71357a2559 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.impl import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt similarity index 98% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt rename to libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt index aa339e3365..308edd0a51 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.mimetype.MimeTypes diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt new file mode 100644 index 0000000000..0786d2d7ed --- /dev/null +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.voiceplayer.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runUpdatingState +import io.element.android.libraries.core.extensions.flatMap +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.ui.utils.time.formatShort +import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents +import io.element.android.libraries.voiceplayer.api.VoiceMessageException +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +class VoiceMessagePresenter( + private val analyticsService: AnalyticsService, + private val scope: CoroutineScope, + private val player: VoiceMessagePlayer, + private val eventId: EventId?, + private val duration: Duration, +) : Presenter { + private val play = mutableStateOf>(AsyncData.Uninitialized) + + @Composable + override fun present(): VoiceMessageState { + val playerState by player.state.collectAsState( + VoiceMessagePlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + currentPosition = 0L, + duration = null + ) + ) + + val button by remember { + derivedStateOf { + when { + eventId == null -> VoiceMessageState.Button.Disabled + playerState.isPlaying -> VoiceMessageState.Button.Pause + play.value is AsyncData.Loading -> VoiceMessageState.Button.Downloading + play.value is AsyncData.Failure -> VoiceMessageState.Button.Retry + else -> VoiceMessageState.Button.Play + } + } + } + val duration by remember { + derivedStateOf { playerState.duration ?: duration.inWholeMilliseconds } + } + val progress by remember { + derivedStateOf { + playerState.currentPosition / duration.toFloat() + } + } + val time by remember { + derivedStateOf { + when { + playerState.isReady && !playerState.isEnded -> playerState.currentPosition + playerState.currentPosition > 0 -> playerState.currentPosition + else -> duration + }.milliseconds.formatShort() + } + } + val showCursor by remember { + derivedStateOf { + !play.value.isUninitialized() && !playerState.isEnded + } + } + + fun eventSink(event: VoiceMessageEvents) { + when (event) { + is VoiceMessageEvents.PlayPause -> { + if (playerState.isPlaying) { + player.pause() + } else if (playerState.isReady) { + player.play() + } else { + scope.launch { + play.runUpdatingState( + errorTransform = { + analyticsService.trackError( + VoiceMessageException.PlayMessageError("Error while trying to play voice message", it) + ) + it + }, + ) { + player.prepare().flatMap { + runCatching { player.play() } + } + } + } + } + } + is VoiceMessageEvents.Seek -> { + player.seekTo((event.percentage * duration).toLong()) + } + } + } + + return VoiceMessageState( + button = button, + progress = progress, + time = time, + showCursor = showCursor, + eventSink = { eventSink(it) }, + ) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt similarity index 98% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt rename to libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt index fcf1998097..4c7b176fa5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.impl import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.mimetype.MimeTypes diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt similarity index 99% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt rename to libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt index 9a82b46776..fdc9b2ee50 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.impl import app.cash.turbine.TurbineTestContext import app.cash.turbine.test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt similarity index 89% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt rename to libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt index 8d2f5b88ac..8867af8287 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt @@ -5,7 +5,7 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.impl import io.element.android.tests.testutils.simulateLongTask import java.io.File diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenterTest.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt similarity index 85% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenterTest.kt rename to libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt index ceedf0948f..59b1891962 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenterTest.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt @@ -5,21 +5,25 @@ * Please see LICENSE in the repository root for full details. */ -package io.element.android.features.messages.impl.voicemessages.timeline +package io.element.android.libraries.voiceplayer.impl import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent -import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent -import io.element.android.features.messages.impl.voicemessages.VoiceMessageException +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer +import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents +import io.element.android.libraries.voiceplayer.api.VoiceMessageException +import io.element.android.libraries.voiceplayer.api.VoiceMessageState import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds class VoiceMessagePresenterTest { @@ -41,7 +45,7 @@ class VoiceMessagePresenterTest { fun `pressing play downloads and plays`() = runTest { val presenter = createVoiceMessagePresenter( mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), - content = aTimelineItemVoiceContent(duration = 2_000.milliseconds), + duration = 2_000.milliseconds, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -79,7 +83,7 @@ class VoiceMessagePresenterTest { mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), voiceMessageMediaRepo = FakeVoiceMessageMediaRepo().apply { shouldFail = true }, analyticsService = analyticsService, - content = aTimelineItemVoiceContent(duration = 2_000.milliseconds), + duration = 2_000.milliseconds, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -115,7 +119,7 @@ class VoiceMessagePresenterTest { fun `pressing pause while playing pauses`() = runTest { val presenter = createVoiceMessagePresenter( mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), - content = aTimelineItemVoiceContent(duration = 2_000.milliseconds), + duration = 2_000.milliseconds, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -147,7 +151,7 @@ class VoiceMessagePresenterTest { @Test fun `content with null eventId shows disabled button`() = runTest { val presenter = createVoiceMessagePresenter( - content = aTimelineItemVoiceContent(eventId = null), + eventId = null, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -164,7 +168,7 @@ class VoiceMessagePresenterTest { fun `seeking before play`() = runTest { val presenter = createVoiceMessagePresenter( mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), - content = aTimelineItemVoiceContent(duration = 10_000.milliseconds), + duration = 10_000.milliseconds, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -188,7 +192,7 @@ class VoiceMessagePresenterTest { @Test fun `seeking after play`() = runTest { val presenter = createVoiceMessagePresenter( - content = aTimelineItemVoiceContent(duration = 10_000.milliseconds), + duration = 10_000.milliseconds, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -224,19 +228,23 @@ fun TestScope.createVoiceMessagePresenter( mediaPlayer: FakeMediaPlayer = FakeMediaPlayer(), voiceMessageMediaRepo: VoiceMessageMediaRepo = FakeVoiceMessageMediaRepo(), analyticsService: AnalyticsService = FakeAnalyticsService(), - content: TimelineItemVoiceContent = aTimelineItemVoiceContent(), + eventId: EventId? = EventId("\$anEventId"), + filename: String = "filename doesn't really matter for a voice message", + duration: Duration = 61_000.milliseconds, + contentUri: String = "mxc://matrix.org/1234567890abcdefg", + mimeType: String = MimeTypes.Ogg, + mediaSource: MediaSource = MediaSource(contentUri), ) = VoiceMessagePresenter( - voiceMessagePlayerFactory = { eventId, mediaSource, mimeType, filename -> - DefaultVoiceMessagePlayer( - mediaPlayer = mediaPlayer, - voiceMessageMediaRepoFactory = { _, _, _ -> voiceMessageMediaRepo }, - eventId = eventId, - mediaSource = mediaSource, - mimeType = mimeType, - filename = filename - ) - }, analyticsService = analyticsService, scope = this, - content = content, + player = DefaultVoiceMessagePlayer( + mediaPlayer = mediaPlayer, + voiceMessageMediaRepoFactory = { _, _, _ -> voiceMessageMediaRepo }, + eventId = eventId, + mediaSource = mediaSource, + mimeType = mimeType, + filename = filename + ), + eventId = eventId, + duration = duration, ) diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index f54cdb81ca..4d24ffebfd 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -83,6 +83,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:textcomposer:impl")) implementation(project(":libraries:roomselect:impl")) implementation(project(":libraries:cryptography:impl")) + implementation(project(":libraries:voiceplayer:impl")) implementation(project(":libraries:voicerecorder:impl")) implementation(project(":libraries:mediaplayer:impl")) implementation(project(":libraries:mediaviewer:impl")) From 29eca97533eb9d5ef315caf2efc384865f430e6c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Dec 2024 15:47:35 +0100 Subject: [PATCH 126/203] kotlin 2.1.0 --- .idea/kotlinc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index c224ad564b..bb4493707f 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file From b236389ccb267a1e2c97b246e4f0e30f1bf2886f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Dec 2024 16:00:18 +0100 Subject: [PATCH 127/203] Reorder imports. --- .../libraries/mediaviewer/impl/local/video/MediaVideoView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 9f5317d933..6a39bae136 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -47,8 +47,8 @@ import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerState import io.element.android.libraries.mediaviewer.impl.local.player.MediaPlayerControllerView -import io.element.android.libraries.mediaviewer.impl.local.player.seekToEnsurePlaying import io.element.android.libraries.mediaviewer.impl.local.player.rememberExoPlayer +import io.element.android.libraries.mediaviewer.impl.local.player.seekToEnsurePlaying import io.element.android.libraries.mediaviewer.impl.local.player.togglePlay import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import kotlinx.coroutines.delay From ba8d57f2112f2f2fdca32c049b66f223c0beace9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Dec 2024 18:14:19 +0100 Subject: [PATCH 128/203] Improve title and subtitle for empty states in the gallery. --- .../impl/gallery/MediaGalleryView.kt | 25 ++++++++++++++----- .../src/main/res/values-cs/translations.xml | 4 +-- .../src/main/res/values-de/translations.xml | 4 +-- .../src/main/res/values-et/translations.xml | 6 +++-- .../src/main/res/values-fr/translations.xml | 4 +-- .../src/main/res/values-hu/translations.xml | 4 +-- .../src/main/res/values-it/translations.xml | 4 +-- .../src/main/res/values-ru/translations.xml | 6 +++-- .../impl/src/main/res/values/localazy.xml | 6 +++-- 9 files changed, 41 insertions(+), 22 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index a178887fdd..a24560d9d0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter @@ -213,7 +214,11 @@ private fun MediaGalleryImages( onItemClick: (MediaItem.Event) -> Unit, ) { if (imagesAndVideos.isEmpty()) { - EmptyContent() + EmptyContent( + titleRes = R.string.screen_media_browser_media_empty_state_title, + subtitleRes = R.string.screen_media_browser_media_empty_state_subtitle, + icon = CompoundIcons.Image(), + ) } else { MediaGalleryImageGrid( imagesAndVideos = imagesAndVideos, @@ -230,7 +235,11 @@ private fun MediaGalleryFiles( onItemClick: (MediaItem.Event) -> Unit, ) { if (files.isEmpty()) { - EmptyContent() + EmptyContent( + titleRes = R.string.screen_media_browser_files_empty_state_title, + subtitleRes = R.string.screen_media_browser_files_empty_state_subtitle, + icon = CompoundIcons.Files(), + ) } else { MediaGalleryFilesList( files = files, @@ -394,7 +403,11 @@ private fun ErrorContent(error: Throwable) { } @Composable -private fun EmptyContent() { +fun EmptyContent( + titleRes: Int, + subtitleRes: Int, + icon: ImageVector, +) { Box( modifier = Modifier.fillMaxSize(), ) { @@ -403,9 +416,9 @@ private fun EmptyContent() { .fillMaxWidth() .padding(top = 44.dp) .padding(24.dp), - title = stringResource(R.string.screen_media_browser_empty_state_title), - iconStyle = BigIcon.Style.Default(CompoundIcons.Image()), - subtitle = stringResource(R.string.screen_media_browser_empty_state_subtitle), + title = stringResource(titleRes), + iconStyle = BigIcon.Style.Default(icon), + subtitle = stringResource(subtitleRes), ) } } diff --git a/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml index 79f3b646ee..9fc10afbad 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml @@ -1,10 +1,10 @@ - "Obrázky a videa nahraná do této místnosti budou zobrazeny zde." - "Zatím nebyla nahrána žádná média" "Načítání souborů…" "Načítání médií…" "Soubory" "Média" + "Obrázky a videa nahraná do této místnosti budou zobrazeny zde." + "Zatím nebyla nahrána žádná média" "Média a soubory" diff --git a/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml index 07438166f6..813d358ec0 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-de/translations.xml @@ -1,11 +1,11 @@ - "In diesen Chatroom hochgeladene Bilder und Videos werden hier angezeigt." - "Noch keine Medien hochgeladen" "Dateien werden geladen…" "Medien werden geladen…" "Dateien" "Medien" + "In diesen Chatroom hochgeladene Bilder und Videos werden hier angezeigt." + "Noch keine Medien hochgeladen" "Medien und Dateien" "Dateiformat" "Dateiname" diff --git a/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml index 396138c100..85285375e1 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-et/translations.xml @@ -2,15 +2,17 @@ "Järgnevaga eemaldame selle faili jututoast ka tema liikmed enam ei pääse failile ligi." "Kas kustutame faili?" - "Antud jututuppa üleslaaditud pildid ja videod kuvatakse siin." - "Mitte keegi pole veel meediat üles laadinud" "Laadime faile…" "Laadime meediat…" "Failid" "Meedia" + "Antud jututuppa üleslaaditud pildid ja videod kuvatakse siin." + "Mitte keegi pole veel meediat üles laadinud" "Meedia ja failid" "Failivorming" "Failinimi" + "Järgnevaga eemaldame selle faili jututoast ja tema liikmed enam ei pääse failile ligi." + "Kas kustutame faili?" "Üleslaadija" "Üleslaaditud" diff --git a/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml index bd961fb941..d12293ad43 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml @@ -2,12 +2,12 @@ "Ce fichier sera supprimé du salon et les membres n’y auront plus accès." "Supprimer le fichier ?" - "Les images et vidéos envoyées dans ce salon seront affichées ici." - "Aucun média n’a encore été envoyé dans ce salon" "Chargement des fichiers…" "Chargement des médias…" "Fichiers" "Média" + "Les images et vidéos envoyées dans ce salon seront affichées ici." + "Aucun média n’a encore été envoyé dans ce salon" "Médias et fichiers" "Format du fichier" "Nom du fichier" diff --git a/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml index 1fcc528dc5..8336caec01 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml @@ -2,12 +2,12 @@ "Ez a fájl el lesz távolítva a szobából, és a tagok nem férhetnek hozzá." "Törli a fájlt?" - "Az ebbe a szobába feltöltött képek és videók itt jelennek meg." - "Még nincs feltöltött média" "Fájlok betöltése…" "Média betöltése…" "Fájlok" "Média" + "Az ebbe a szobába feltöltött képek és videók itt jelennek meg." + "Még nincs feltöltött média" "Média és fájlok" "Fájlformátum" "Fájlnév" diff --git a/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml index 45d160f3d2..237209e0d7 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-it/translations.xml @@ -1,8 +1,8 @@ - "Le immagini e i video caricati in questa stanza verranno mostrati qui." - "Nessun file multimediale ancora caricato" "File" "Contenuti multimediali" + "Le immagini e i video caricati in questa stanza verranno mostrati qui." + "Nessun file multimediale ancora caricato" "File e contenuti multimediali" diff --git a/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml index 713b748617..cc0f2e573f 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-ru/translations.xml @@ -2,15 +2,17 @@ "Этот файл будет удален из комнаты и участники не будут иметь к нему доступ." "Удалить файл?" - "Здесь будут показаны изображения и видео, загруженные в данную комнату." - "Пока что нет загруженных медиафайлов" "Загрузка файлов…" "Загрузка медиа…" "Файлы" "Медиа" + "Здесь будут показаны изображения и видео, загруженные в данную комнату." + "Пока что нет загруженных медиафайлов" "Медиа и файлы" "Формат файла" "Имя файла" + "Этот файл будет удален из комнаты и у участников не будет к нему доступа." + "Удалить файл?" "Загружено" "Загружено на" diff --git a/libraries/mediaviewer/impl/src/main/res/values/localazy.xml b/libraries/mediaviewer/impl/src/main/res/values/localazy.xml index b35a4819f1..6072c56db8 100644 --- a/libraries/mediaviewer/impl/src/main/res/values/localazy.xml +++ b/libraries/mediaviewer/impl/src/main/res/values/localazy.xml @@ -2,12 +2,14 @@ "This file will be removed from the room and members won’t have access to it." "Delete file?" - "Images and videos uploaded to this room will be shown here." - "No media uploaded yet" + "Documents, audio files, and voice messages uploaded to this room will be shown here." + "No files uploaded yet" "Loading files…" "Loading media…" "Files" "Media" + "Images and videos uploaded to this room will be shown here." + "No media uploaded yet" "Media and files" "File format" "File name" From 8d04e6e0ac9d0fee43c3791a05ffa8209ef2f73c Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 13 Dec 2024 17:43:00 +0000 Subject: [PATCH 129/203] Update screenshots --- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png index 18144ddccb..38be3c256b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b7055ce0a8214e7c445c2d1b7d30ad5ffc63617c9f9f837661dfbd057198681 -size 26092 +oid sha256:d2d9ef7383d17436738abf01c450ad189ebffebcfbee12de253fdc92f6feda93 +size 28593 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png index aca9e44958..b9a56226c4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f07b50b154c426638c38897fca296f5d52fb0054ad459eadf8fbe344c9b0526 -size 25475 +oid sha256:bfe8f753f87d3b67ad3ffde246dba0438120bb2a28efe033cbfd2204664fee95 +size 27633 From 93e86b0fe1328b45a2ea013660db593f61f4b655 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Dec 2024 18:32:31 +0100 Subject: [PATCH 130/203] Private fun --- .../libraries/mediaviewer/impl/gallery/MediaGalleryView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index a24560d9d0..eaf6e534ac 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -403,7 +403,7 @@ private fun ErrorContent(error: Throwable) { } @Composable -fun EmptyContent( +private fun EmptyContent( titleRes: Int, subtitleRes: Int, icon: ImageVector, From dfb5362394b57f8b2667ae5d6933328c516d58e7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Dec 2024 18:02:35 +0100 Subject: [PATCH 131/203] knock requests : branch the api in presenters --- .../knockrequests/impl/KnockRequest.kt | 45 ------ .../banner/KnockRequestsBannerPresenter.kt | 74 ++++++++-- .../impl/banner/KnockRequestsBannerState.kt | 8 +- .../KnockRequestsBannerStateProvider.kt | 16 +-- .../impl/banner/KnockRequestsBannerView.kt | 114 +++++++++------ .../impl/data/KnockRequestFixture.kt | 27 ++++ .../impl/data/KnockRequestPresentable.kt | 36 +++++ .../impl/data/KnockRequestWrapper.kt | 34 +++++ .../impl/data/KnockRequestsService.kt | 135 ++++++++++++++++++ .../impl/list/KnockRequestsListEvents.kt | 12 +- .../impl/list/KnockRequestsListPresenter.kt | 83 ++++++++--- .../impl/list/KnockRequestsListState.kt | 21 +-- .../list/KnockRequestsListStateProvider.kt | 26 +++- .../impl/list/KnockRequestsListView.kt | 113 ++++++++------- .../libraries/matrix/api/room/MatrixRoom.kt | 3 + .../matrix/api/room/knock/KnockRequest.kt | 3 + .../matrix/impl/room/RustMatrixRoom.kt | 6 +- .../impl/room/knock/RustKnockRequest.kt | 3 + .../matrix/test/room/FakeMatrixRoom.kt | 3 + 19 files changed, 555 insertions(+), 207 deletions(-) delete mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt deleted file mode 100644 index 1014d13e58..0000000000 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.knockrequests.impl - -import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.matrix.api.core.UserId - -data class KnockRequest( - val userId: UserId, - val displayName: String?, - val avatarUrl: String?, - val reason: String?, - val formattedDate: String?, -) - -fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( - id = userId.value, - name = displayName, - url = avatarUrl, - size = size, -) - -fun KnockRequest.getBestName(): String { - return displayName?.takeIf { it.isNotEmpty() } ?: userId.value -} - -fun aKnockRequest( - userId: UserId = UserId("@jacob_ross:example.com"), - displayName: String? = "Jacob Ross", - avatarUrl: String? = null, - reason: String? = "Hi, I would like to get access to this room please.", - formattedDate: String = "20 Nov 2024", -) = KnockRequest( - userId = userId, - displayName = displayName, - avatarUrl = avatarUrl, - reason = reason, - formattedDate = formattedDate, -) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index a61236bbb0..c95fca6a5f 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -8,35 +8,89 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import io.element.android.libraries.architecture.AsyncAction +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable +import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.Presenter -import kotlinx.collections.immutable.persistentListOf +import io.element.android.libraries.core.coroutine.mapState +import io.element.android.libraries.core.extensions.firstIfSingle +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.ui.room.canInviteAsState +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import javax.inject.Inject -class KnockRequestsBannerPresenter @Inject constructor() : Presenter { +private const val ACCEPT_ERROR_DISPLAY_DURATION = 1500L + +class KnockRequestsBannerPresenter @Inject constructor( + private val room: MatrixRoom, + private val knockRequestsService: KnockRequestsService, + private val appCoroutineScope: CoroutineScope, +) : Presenter { @Composable override fun present(): KnockRequestsBannerState { - var shouldShowBanner by remember { mutableStateOf(false) } + val knockRequests by remember { + knockRequestsService.knockRequestsFlow.mapState { knockRequests -> + knockRequests.dataOrNull().orEmpty() + .filter { !it.isSeen } + .toImmutableList() + } + }.collectAsState() + + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() + val canAccept by room.canInviteAsState(syncUpdateFlow.value) + val showAcceptError = remember { mutableStateOf(false) } + + val shouldShowBanner by remember { + derivedStateOf { + knockRequests.isNotEmpty() + } + } fun handleEvents(event: KnockRequestsBannerEvents) { when (event) { - is KnockRequestsBannerEvents.AcceptSingleRequest -> Unit + is KnockRequestsBannerEvents.AcceptSingleRequest -> { + appCoroutineScope.acceptSingleKnockRequest( + knockRequests = knockRequests, + displayAcceptError = showAcceptError, + ) + } is KnockRequestsBannerEvents.Dismiss -> { - shouldShowBanner = false + appCoroutineScope.launch { + knockRequestsService.markAllKnockRequestsAsSeen() + } } } } return KnockRequestsBannerState( - knockRequests = persistentListOf(), - acceptAction = AsyncAction.Uninitialized, - canAccept = false, + knockRequests = knockRequests, + displayAcceptError = showAcceptError.value, + canAccept = canAccept, isVisible = shouldShowBanner, eventSink = ::handleEvents, ) } + + private fun CoroutineScope.acceptSingleKnockRequest( + knockRequests: List, + displayAcceptError: MutableState, + ) = launch { + val knockRequest = knockRequests.firstIfSingle() + if (knockRequest != null) { + knockRequestsService.acceptKnockRequest(knockRequest, optimistic = true) + .onFailure { + displayAcceptError.value = true + delay(ACCEPT_ERROR_DISPLAY_DURATION) + displayAcceptError.value = false + } + } + } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index e65cd2fb26..9d53181e82 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -10,17 +10,15 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.features.knockrequests.impl.R -import io.element.android.features.knockrequests.impl.getBestName -import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.extensions.firstIfSingle import kotlinx.collections.immutable.ImmutableList data class KnockRequestsBannerState( val isVisible: Boolean, - val knockRequests: ImmutableList, - val acceptAction: AsyncAction, + val knockRequests: ImmutableList, + val displayAcceptError: Boolean, val canAccept: Boolean, val eventSink: (KnockRequestsBannerEvents) -> Unit, ) { diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt index c0f4cc0961..460d1c7462 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -8,9 +8,8 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.knockrequests.impl.KnockRequest -import io.element.android.features.knockrequests.impl.aKnockRequest -import io.element.android.libraries.architecture.AsyncAction +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable +import io.element.android.features.knockrequests.impl.data.aKnockRequest import kotlinx.collections.immutable.toImmutableList class KnockRequestsBannerStateProvider : PreviewParameterProvider { @@ -44,10 +43,7 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider = listOf(aKnockRequest()), - acceptAction: AsyncAction = AsyncAction.Uninitialized, + knockRequests: List = listOf(aKnockRequest()), + displayAcceptError: Boolean = false, canAccept: Boolean = true, isVisible: Boolean = true, eventSink: (KnockRequestsBannerEvents) -> Unit = {} ) = KnockRequestsBannerState( knockRequests = knockRequests.toImmutableList(), - acceptAction = acceptAction, + displayAcceptError = displayAcceptError, canAccept = canAccept, isVisible = isVisible, eventSink = eventSink, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 9cf7144f5e..69dfa268a6 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -20,9 +20,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset @@ -37,9 +40,11 @@ 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.knockrequests.impl.KnockRequest import io.element.android.features.knockrequests.impl.R -import io.element.android.features.knockrequests.impl.getAvatarData +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable +import io.element.android.libraries.designsystem.components.async.AsyncIndicator +import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost +import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview @@ -52,6 +57,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList +import timber.log.Timber private const val MAX_AVATAR_COUNT = 3 @@ -61,22 +67,42 @@ fun KnockRequestsBannerView( onViewRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { - AnimatedVisibility( - visible = state.isVisible, - enter = expandVertically(), - exit = shrinkVertically(), - modifier = modifier, - ) { - Surface( - shape = MaterialTheme.shapes.small, - color = ElementTheme.colors.bgCanvasDefaultLevel1, - shadowElevation = 24.dp, - modifier = Modifier.padding(16.dp), + Box(modifier = modifier) { + AnimatedVisibility( + visible = state.isVisible, + enter = expandVertically(), + exit = shrinkVertically(), ) { - KnockRequestsBannerContent( - state = state, - onViewRequestsClick = onViewRequestsClick, - ) + Surface( + shape = MaterialTheme.shapes.small, + color = ElementTheme.colors.bgCanvasDefaultLevel1, + shadowElevation = 24.dp, + modifier = Modifier.padding(16.dp), + ) { + KnockRequestsBannerContent( + state = state, + onViewRequestsClick = onViewRequestsClick, + ) + } + } + KnockRequestsAcceptErrorView(displayError = state.displayAcceptError) + } +} + +@Composable +private fun KnockRequestsAcceptErrorView( + displayError: Boolean, + modifier: Modifier = Modifier, +) { + val asyncIndicatorState = rememberAsyncIndicatorState() + AsyncIndicatorHost(modifier = modifier.statusBarsPadding(), state = asyncIndicatorState) + LaunchedEffect(displayError) { + if (displayError) { + asyncIndicatorState.enqueue { + AsyncIndicator.Custom(text = stringResource(CommonStrings.error_unknown)) + } + } else { + asyncIndicatorState.clear() } } } @@ -96,9 +122,9 @@ private fun KnockRequestsBannerContent( } Column( - modifier - .fillMaxWidth() - .padding(all = 16.dp) + modifier + .fillMaxWidth() + .padding(all = 16.dp) ) { Row { KnockRequestAvatarView( @@ -122,13 +148,15 @@ private fun KnockRequestsBannerContent( ) } } + Spacer(modifier = Modifier.width(4.dp)) Icon( modifier = Modifier.clickable(onClick = ::onDismissClick), imageVector = CompoundIcons.Close(), contentDescription = stringResource(CommonStrings.action_close) ) } - if (state.reason != null) { + val reason = state.reason + if (!reason.isNullOrEmpty()) { Spacer(modifier = Modifier.height(16.dp)) Text( text = state.reason, @@ -169,7 +197,7 @@ private fun KnockRequestsBannerContent( @Composable private fun KnockRequestAvatarView( - knockRequests: ImmutableList, + knockRequests: ImmutableList, modifier: Modifier = Modifier, ) { Box(modifier) { @@ -183,7 +211,7 @@ private fun KnockRequestAvatarView( @Composable private fun KnockRequestAvatarListView( - knockRequests: ImmutableList, + knockRequests: ImmutableList, modifier: Modifier = Modifier, ) { val avatarSize = AvatarSize.KnockRequestBanner.dp @@ -198,27 +226,27 @@ private fun KnockRequestAvatarListView( smallReversedList.forEachIndexed { index, knockRequest -> Avatar( modifier = Modifier - .padding(start = avatarSize / 2 * (lastItemIndex - index)) - .graphicsLayer { - compositingStrategy = CompositingStrategy.Offscreen - } - .drawWithContent { - // Draw content and clear the pixels for the avatar on the left. - drawContent() - if (index < lastItemIndex) { - drawCircle( - color = Color.Black, - center = Offset( - x = 0f, - y = size.height / 2, - ), - radius = avatarSize.toPx() / 2, - blendMode = BlendMode.Clear, - ) + .padding(start = avatarSize / 2 * (lastItemIndex - index)) + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithContent { + // Draw content and clear the pixels for the avatar on the left. + drawContent() + if (index < lastItemIndex) { + drawCircle( + color = Color.Black, + center = Offset( + x = 0f, + y = size.height / 2, + ), + radius = avatarSize.toPx() / 2, + blendMode = BlendMode.Clear, + ) + } } - } - .size(size = avatarSize) - .padding(2.dp), + .size(size = avatarSize) + .padding(2.dp), avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt new file mode 100644 index 0000000000..5fbc2bb047 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UserId + +fun aKnockRequest( + eventId: EventId = EventId("\$eventId"), + userId: UserId = UserId("@jacob_ross:example.com"), + displayName: String? = "Jacob Ross", + avatarUrl: String? = null, + reason: String? = "Hi, I would like to get access to this room please.", + formattedDate: String? = "20 Nov 2024", +) = object : KnockRequestPresentable { + override val eventId: EventId = eventId + override val userId: UserId = userId + override val displayName: String? = displayName + override val avatarUrl: String? = avatarUrl + override val reason: String? = reason + override val formattedDate: String? = formattedDate +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt new file mode 100644 index 0000000000..6716908a56 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +import androidx.compose.runtime.Immutable +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UserId + +@Immutable +interface KnockRequestPresentable { + val eventId: EventId + val userId: UserId + val displayName: String? + val avatarUrl: String? + val reason: String? + val formattedDate: String? + + fun getAvatarData(size: AvatarSize) = AvatarData( + id = userId.value, + name = displayName, + url = avatarUrl, + size = size, + ) + + fun getBestName(): String { + return displayName?.takeIf { it.isNotEmpty() } ?: userId.value + } + +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt new file mode 100644 index 0000000000..56bcc34422 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.knock.KnockRequest + +class KnockRequestWrapper( + private val knockRequest: KnockRequest, + dateFormatter: (Long?) -> String? = { null } +) : KnockRequestPresentable { + override val eventId: EventId = knockRequest.eventId + override val userId: UserId = knockRequest.userId + override val displayName: String? = knockRequest.displayName + override val avatarUrl: String? = knockRequest.avatarUrl + override val reason: String? = knockRequest.reason?.trim() + override val formattedDate: String? = dateFormatter(knockRequest.timestamp) + + val isSeen: Boolean = knockRequest.isSeen + + suspend fun accept(): Result = knockRequest.accept() + + suspend fun decline(reason: String?): Result = knockRequest.decline(reason) + + suspend fun declineAndBan(reason: String?): Result = knockRequest.declineAndBan(reason) + + suspend fun markAsSeen(): Result = knockRequest.markAsSeen() +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt new file mode 100644 index 0000000000..653930b16b --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.room.MatrixRoom +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.supervisorScope +import javax.inject.Inject + +@SingleIn(RoomScope::class) +class KnockRequestsService @Inject constructor(room: MatrixRoom) { + + // Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them. + private val handledKnockRequestIds = MutableStateFlow>(emptySet()) + + val knockRequestsFlow = combine( + room.wrappedKnockRequestsFlow(), + handledKnockRequestIds, + ) { knockRequests, handledKnockIds -> + val presentableKnockRequests = knockRequests + .filter { it.eventId !in handledKnockIds } + .toImmutableList() + AsyncData.Success(presentableKnockRequests) + }.stateIn(room.roomCoroutineScope, SharingStarted.Lazily, AsyncData.Loading()) + + private fun knockRequestsList() = knockRequestsFlow.value.dataOrNull().orEmpty() + + private fun getKnockRequestById(eventId: EventId): KnockRequestWrapper? { + return knockRequestsList().find { it.eventId == eventId } + } + + /** + * Accept a knock request. + * @param knockRequest The knock request to accept. + * @param optimistic If true, the request will be marked as handled before the server responds. + */ + suspend fun acceptKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result { + val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult() + return handleKnockRequest(wrapped, optimistic) { accept() } + } + + /** + * Decline a knock request. + * @param knockRequest The knock request to decline. + * @param optimistic If true, the request will be marked as handled before the server responds. + */ + suspend fun declineKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result { + val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult() + return handleKnockRequest(wrapped, optimistic) { decline(null) } + } + + /** + * Decline a knock request by banning the user. + * @param knockRequest The knock request to decline. + * @param optimistic If true, the request will be marked as handled before the server responds. + */ + suspend fun declineAndBanKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result { + val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult() + return handleKnockRequest(wrapped, optimistic) { declineAndBan(null) } + } + + /** + * Accept all currently known knock requests. + * @param optimistic If true, the requests will be marked as handled before the server responds. + */ + suspend fun acceptAllKnockRequests(optimistic: Boolean = false): Result = supervisorScope { + val results = knockRequestsList() + .map { knockRequest -> + async { + acceptKnockRequest(knockRequest, optimistic = optimistic) + } + } + .awaitAll() + if (results.all { it.isSuccess }) { + Result.success(Unit) + } else { + Result.failure(IllegalStateException("Failed to accept all knock requests")) + } + } + + /** + * Mark all currently known knock requests as seen. + */ + suspend fun markAllKnockRequestsAsSeen() = supervisorScope { + knockRequestsList() + .map { knockRequest -> + async { knockRequest.markAsSeen() } + } + .awaitAll() + } + + private suspend fun handleKnockRequest( + knockRequest: KnockRequestWrapper, + optimistic: Boolean, + action: suspend (KnockRequestWrapper.() -> Result) + ): Result { + if (optimistic) { + handledKnockRequestIds.getAndUpdate { it + knockRequest.eventId } + } + return action(knockRequest) + .onFailure { + if (optimistic) { + handledKnockRequestIds.getAndUpdate { it - knockRequest.eventId } + } + } + .onSuccess { + if (!optimistic) { + handledKnockRequestIds.getAndUpdate { it + knockRequest.eventId } + } + } + } + + private fun knockRequestNotFoundResult() = Result.failure(IllegalArgumentException("Knock request not found")) + + private fun MatrixRoom.wrappedKnockRequestsFlow() = knockRequestsFlow.map { knockRequests -> + knockRequests.map { KnockRequestWrapper(it) } + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt index 132c137ce2..23b1025ce2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -7,12 +7,14 @@ package io.element.android.features.knockrequests.impl.list -import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable sealed interface KnockRequestsListEvents { - data class Accept(val knockRequest: KnockRequest) : KnockRequestsListEvents - data class Decline(val knockRequest: KnockRequest) : KnockRequestsListEvents - data class DeclineAndBan(val knockRequest: KnockRequest) : KnockRequestsListEvents + data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents + data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents + data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents data object AcceptAll : KnockRequestsListEvents - data object DismissCurrentAction : KnockRequestsListEvents + data object ResetCurrentAction : KnockRequestsListEvents + data object RetryCurrentAction : KnockRequestsListEvents + data object ConfirmCurrentAction : KnockRequestsListEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index fcafc5428b..04bc48dd4b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -11,70 +11,109 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.features.knockrequests.impl.data.KnockRequestsService 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.architecture.runUpdatingState import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.ui.room.canBanAsState import io.element.android.libraries.matrix.ui.room.canInviteAsState import io.element.android.libraries.matrix.ui.room.canKickAsState -import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor( private val room: MatrixRoom, + private val knockRequestsService: KnockRequestsService, ) : Presenter { @Composable override fun present(): KnockRequestsListState { - val currentAction = remember { mutableStateOf(KnockRequestsCurrentAction.None) } + val asyncAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + var actionTarget by remember { mutableStateOf(KnockRequestsActionTarget.None) } + var targetActionConfirmed by remember { mutableStateOf(false) } + var retryCount by remember { mutableIntStateOf(0) } + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canBan by room.canBanAsState(syncUpdateFlow.value) val canDecline by room.canKickAsState(syncUpdateFlow.value) val canAccept by room.canInviteAsState(syncUpdateFlow.value) + val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState() + fun handleEvents(event: KnockRequestsListEvents) { when (event) { KnockRequestsListEvents.AcceptAll -> { - currentAction.value = KnockRequestsCurrentAction.AcceptAll(AsyncAction.Uninitialized) + actionTarget = KnockRequestsActionTarget.AcceptAll } is KnockRequestsListEvents.Accept -> { - currentAction.value = KnockRequestsCurrentAction.Accept(event.knockRequest, AsyncAction.Uninitialized) + actionTarget = KnockRequestsActionTarget.Accept(event.knockRequest) } is KnockRequestsListEvents.Decline -> { - currentAction.value = KnockRequestsCurrentAction.Decline(event.knockRequest, AsyncAction.Uninitialized) + actionTarget = KnockRequestsActionTarget.Decline(event.knockRequest) } is KnockRequestsListEvents.DeclineAndBan -> { - currentAction.value = KnockRequestsCurrentAction.DeclineAndBan(event.knockRequest, AsyncAction.Uninitialized) + actionTarget = KnockRequestsActionTarget.DeclineAndBan(event.knockRequest) + } + KnockRequestsListEvents.ResetCurrentAction -> { + actionTarget = KnockRequestsActionTarget.None } - KnockRequestsListEvents.DismissCurrentAction -> { - currentAction.value = KnockRequestsCurrentAction.None + KnockRequestsListEvents.RetryCurrentAction -> { + retryCount++ + } + KnockRequestsListEvents.ConfirmCurrentAction -> { + targetActionConfirmed = true } } } - LaunchedEffect(currentAction) { - when (currentAction.value) { - is KnockRequestsCurrentAction.Accept -> { - // Accept the knock request + LaunchedEffect(actionTarget, targetActionConfirmed, retryCount) { + when (val action = actionTarget) { + is KnockRequestsActionTarget.Accept -> { + runUpdatingState(asyncAction) { + knockRequestsService.acceptKnockRequest(action.knockRequest) + } + } + is KnockRequestsActionTarget.Decline -> { + if (targetActionConfirmed) { + runUpdatingState(asyncAction) { + knockRequestsService.declineKnockRequest(action.knockRequest) + } + } else { + asyncAction.value = AsyncAction.ConfirmingNoParams + } } - is KnockRequestsCurrentAction.Decline -> { - // Decline the knock request + is KnockRequestsActionTarget.DeclineAndBan -> { + if (targetActionConfirmed) { + runUpdatingState(asyncAction) { + knockRequestsService.declineAndBanKnockRequest(action.knockRequest) + } + } else { + asyncAction.value = AsyncAction.ConfirmingNoParams + } } - is KnockRequestsCurrentAction.DeclineAndBan -> { - // Decline and ban the user + is KnockRequestsActionTarget.AcceptAll -> { + if (targetActionConfirmed) { + runUpdatingState(asyncAction) { + knockRequestsService.acceptAllKnockRequests() + } + } else { + asyncAction.value = AsyncAction.ConfirmingNoParams + } } - is KnockRequestsCurrentAction.AcceptAll -> { - // Accept all knock requests + KnockRequestsActionTarget.None -> { + targetActionConfirmed = false + asyncAction.value = AsyncAction.Uninitialized } - KnockRequestsCurrentAction.None -> Unit } } return KnockRequestsListState( - knockRequests = AsyncData.Success(persistentListOf()), - currentAction = currentAction.value, + knockRequests = knockRequests, + actionTarget = actionTarget, + asyncAction = asyncAction.value, canAccept = canAccept, canDecline = canDecline, canBan = canBan, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 3ba10e9302..763305eca2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -8,27 +8,28 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Immutable -import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import kotlinx.collections.immutable.ImmutableList data class KnockRequestsListState( - val knockRequests: AsyncData>, - val currentAction: KnockRequestsCurrentAction, + val knockRequests: AsyncData>, + val actionTarget: KnockRequestsActionTarget, + val asyncAction: AsyncAction, val canAccept: Boolean, val canDecline: Boolean, val canBan: Boolean, val eventSink: (KnockRequestsListEvents) -> Unit, ) { - val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 + val canAcceptAll = canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1 } @Immutable -sealed interface KnockRequestsCurrentAction { - data object None : KnockRequestsCurrentAction - data class Accept(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction - data class Decline(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction - data class DeclineAndBan(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction - data class AcceptAll(val async: AsyncAction) : KnockRequestsCurrentAction +sealed interface KnockRequestsActionTarget { + data object None : KnockRequestsActionTarget + data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsActionTarget + data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsActionTarget + data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsActionTarget + data object AcceptAll : KnockRequestsActionTarget } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 476bf556e1..e7207cced1 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -8,8 +8,8 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.knockrequests.impl.KnockRequest -import io.element.android.features.knockrequests.impl.aKnockRequest +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable +import io.element.android.features.knockrequests.impl.data.aKnockRequest import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId @@ -64,7 +64,17 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), - currentAction: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + knockRequests: AsyncData> = AsyncData.Success(persistentListOf()), + actionTarget: KnockRequestsActionTarget = KnockRequestsActionTarget.None, + asyncAction: AsyncAction = AsyncAction.Uninitialized, canAccept: Boolean = true, canDecline: Boolean = true, canBan: Boolean = true, eventSink: (KnockRequestsListEvents) -> Unit = {}, ) = KnockRequestsListState( knockRequests = knockRequests, - currentAction = currentAction, + actionTarget = actionTarget, + asyncAction = asyncAction, canAccept = canAccept, canDecline = canDecline, canBan = canBan, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 41b5553438..58ac4984f4 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -46,23 +46,25 @@ 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.knockrequests.impl.KnockRequest import io.element.android.features.knockrequests.impl.R -import io.element.android.features.knockrequests.impl.getAvatarData -import io.element.android.features.knockrequests.impl.getBestName +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator 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.OutlinedButton @@ -70,6 +72,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -100,14 +103,18 @@ private fun KnockRequestsListContent( state: KnockRequestsListState, modifier: Modifier = Modifier, ) { - fun onAcceptClick(knockRequest: KnockRequest) { + fun onAcceptClick(knockRequest: KnockRequestPresentable) { state.eventSink(KnockRequestsListEvents.Accept(knockRequest)) } - fun onDeclineClick(knockRequest: KnockRequest) { + fun onDeclineClick(knockRequest: KnockRequestPresentable) { state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) } + fun onBanClick(knockRequest: KnockRequestPresentable) { + state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequest)) + } + var bottomPaddingInPixels by remember { mutableIntStateOf(0) } Box(modifier.fillMaxSize()) { @@ -124,16 +131,30 @@ private fun KnockRequestsListContent( canBan = state.canBan, onAcceptClick = ::onAcceptClick, onDeclineClick = ::onDeclineClick, + onBanClick = ::onBanClick, contentPadding = PaddingValues(bottom = bottomPaddingInPixels.toDp()), ) } } + is AsyncData.Loading -> { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center), + color = ElementTheme.colors.iconPrimary, + ) + } else -> Unit } KnockRequestsActionsView( - actions = state.currentAction, + actionTarget = state.actionTarget, + asyncAction = state.asyncAction, + onConfirm = { + state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + }, + onRetry = { + state.eventSink(KnockRequestsListEvents.RetryCurrentAction) + }, onDismiss = { - state.eventSink(KnockRequestsListEvents.DismissCurrentAction) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) }, ) if (state.canAcceptAll) { @@ -152,53 +173,45 @@ private fun KnockRequestsListContent( @Composable private fun KnockRequestsActionsView( - actions: KnockRequestsCurrentAction, + actionTarget: KnockRequestsActionTarget, + asyncAction: AsyncAction, + onConfirm: () -> Unit, onDismiss: () -> Unit, + onRetry: () -> Unit, modifier: Modifier = Modifier, ) { Box(modifier) { - when (actions) { - is KnockRequestsCurrentAction.AcceptAll -> { - AsyncActionView( - async = actions.async, - onSuccess = {}, - onErrorDismiss = onDismiss, - ) - } - is KnockRequestsCurrentAction.Accept -> { - AsyncActionView( - async = actions.async, - onSuccess = {}, - onErrorDismiss = onDismiss, + AsyncActionView( + async = asyncAction, + onSuccess = { onDismiss() }, + onErrorDismiss = onDismiss, + confirmationDialog = { + ConfirmationDialog( + title = "Confirmation", + content = "Are you sure?", + onSubmitClick = onConfirm, + onDismiss = onDismiss, ) - } - is KnockRequestsCurrentAction.Decline -> { - AsyncActionView( - async = actions.async, - onSuccess = {}, - onErrorDismiss = onDismiss, - ) - } - is KnockRequestsCurrentAction.DeclineAndBan -> { - AsyncActionView( - async = actions.async, - onSuccess = {}, - onErrorDismiss = onDismiss, + }, + progressDialog = { + ProgressDialog( + text = "Loading", ) - } - KnockRequestsCurrentAction.None -> Unit - } + }, + onRetry = onRetry, + ) } } @Composable private fun KnockRequestsList( - knockRequests: ImmutableList, + knockRequests: ImmutableList, canAccept: Boolean, canDecline: Boolean, canBan: Boolean, - onAcceptClick: (KnockRequest) -> Unit, - onDeclineClick: (KnockRequest) -> Unit, + onAcceptClick: (KnockRequestPresentable) -> Unit, + onDeclineClick: (KnockRequestPresentable) -> Unit, + onBanClick: (KnockRequestPresentable) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(0.dp), ) { @@ -214,6 +227,7 @@ private fun KnockRequestsList( canDecline = canDecline, canAccept = canAccept, onDeclineClick = onDeclineClick, + onBanClick = onBanClick, ) if (index != knockRequests.size - 1) { HorizontalDivider() @@ -224,12 +238,13 @@ private fun KnockRequestsList( @Composable private fun KnockRequestItem( - knockRequest: KnockRequest, + knockRequest: KnockRequestPresentable, canAccept: Boolean, canDecline: Boolean, canBan: Boolean, - onAcceptClick: (KnockRequest) -> Unit, - onDeclineClick: (KnockRequest) -> Unit, + onAcceptClick: (KnockRequestPresentable) -> Unit, + onDeclineClick: (KnockRequestPresentable) -> Unit, + onBanClick: (KnockRequestPresentable) -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -252,10 +267,11 @@ private fun KnockRequestItem( color = MaterialTheme.colorScheme.primary, style = ElementTheme.typography.fontBodyLgMedium, ) - if (!knockRequest.formattedDate.isNullOrEmpty()) { + val formattedDate = knockRequest.formattedDate + if (!formattedDate.isNullOrEmpty()) { Spacer(modifier = Modifier.width(8.dp)) Text( - text = knockRequest.formattedDate, + text = formattedDate, color = MaterialTheme.colorScheme.secondary, style = ElementTheme.typography.fontBodySmRegular, ) @@ -272,7 +288,8 @@ private fun KnockRequestItem( ) } // Reason - if (!knockRequest.reason.isNullOrBlank()) { + val reason = knockRequest.reason + if (!reason.isNullOrBlank()) { Spacer(modifier = Modifier.height(12.dp)) var isExpanded by rememberSaveable(knockRequest.userId) { mutableStateOf(false) } var isExpandable by rememberSaveable(knockRequest.userId) { mutableStateOf(false) } @@ -283,7 +300,7 @@ private fun KnockRequestItem( .clickable(enabled = isExpandable) { isExpanded = !isExpanded } ) { Text( - text = knockRequest.reason, + text = reason, style = ElementTheme.typography.fontBodyMdRegular, maxLines = if (isExpanded) Int.MAX_VALUE else 3, onTextLayout = { result -> @@ -336,7 +353,7 @@ private fun KnockRequestItem( TextButton( text = stringResource(R.string.screen_knock_requests_list_decline_and_ban_action_title), onClick = { - onAcceptClick(knockRequest) + onBanClick(knockRequest) }, destructive = true, size = ButtonSize.Small, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 82ea24e7ff..d028c609ed 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -33,6 +33,7 @@ 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.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import java.io.Closeable @@ -53,6 +54,8 @@ interface MatrixRoom : Closeable { val activeMemberCount: Long val joinedMemberCount: Long + val roomCoroutineScope: CoroutineScope + val roomInfoFlow: Flow val roomTypingMembersFlow: Flow> val identityStateChangesFlow: Flow> diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt index f5b25b5f2c..e7a0488245 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt @@ -7,14 +7,17 @@ package io.element.android.libraries.matrix.api.room.knock +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId interface KnockRequest { + val eventId: EventId val userId: UserId val displayName: String? val avatarUrl: String? val reason: String? val timestamp: Long? + val isSeen: Boolean suspend fun accept(): Result diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 44f588bd83..588927f434 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -79,7 +79,7 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener import org.matrix.rustcomponents.sdk.JoinRequest -import org.matrix.rustcomponents.sdk.RequestsToJoinListener +import org.matrix.rustcomponents.sdk.JoinRequestsListener import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener import org.matrix.rustcomponents.sdk.RoomListItem @@ -162,7 +162,7 @@ class RustMatrixRoom( } override val knockRequestsFlow: Flow> = mxCallbackFlow { - innerRoom.subscribeToJoinRequests(object : RequestsToJoinListener { + innerRoom.subscribeToJoinRequests(object : JoinRequestsListener { override fun call(joinRequests: List) { val knockRequests = joinRequests.map { RustKnockRequest(it) } channel.trySend(knockRequests) @@ -176,7 +176,7 @@ class RustMatrixRoom( // ...except getMember methods as it could quickly fill the roomDispatcher... private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8) - private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId") + override val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId") private val _syncUpdateFlow = MutableStateFlow(0L) private val roomMemberListFetcher = RoomMemberListFetcher(innerRoom, roomMembersDispatcher) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt index a3d133d599..8f7b075511 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.impl.room.knock +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.knock.KnockRequest import org.matrix.rustcomponents.sdk.JoinRequest @@ -14,11 +15,13 @@ import org.matrix.rustcomponents.sdk.JoinRequest class RustKnockRequest( private val inner: JoinRequest, ) : KnockRequest { + override val eventId: EventId = EventId(inner.eventId) override val userId: UserId = UserId(inner.userId) override val displayName: String? = inner.displayName override val avatarUrl: String? = inner.avatarUrl override val reason: String? = inner.reason override val timestamp: Long? = inner.timestamp?.toLong() + override val isSeen: Boolean = inner.isSeen override suspend fun accept(): Result = runCatching { inner.actions.accept() diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index cfb73e267e..18b804455d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -49,12 +49,14 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.test.TestScope import java.io.File class FakeMatrixRoom( @@ -73,6 +75,7 @@ class FakeMatrixRoom( override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), override val liveTimeline: Timeline = FakeTimeline(), + override val roomCoroutineScope: CoroutineScope = TestScope(), private var roomPermalinkResult: () -> Result = { lambdaError() }, private var eventPermalinkResult: (EventId) -> Result = { lambdaError() }, private val sendCallNotificationIfNeededResult: () -> Result = { lambdaError() }, From f95678408c5214ba7be36228502881dd1b047a3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 18:21:57 +0000 Subject: [PATCH 132/203] Update dependencyAnalysis to v2.6.1 --- 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 0544d7bb0e..0f51b61091 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,7 @@ wysiwyg = "2.37.14" telephoto = "0.14.0" # Dependency analysis -dependencyAnalysis = "2.6.0" +dependencyAnalysis = "2.6.1" # DI dagger = "2.53.1" From ff3fbe9cbf12794faaf0aeb23a7e9fab3faa9cbc Mon Sep 17 00:00:00 2001 From: bmarty <3940906+bmarty@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:28:36 +0000 Subject: [PATCH 133/203] Sync Strings from Localazy --- .../src/main/res/values-cs/translations.xml | 11 + .../src/main/res/values-el/translations.xml | 4 + .../src/main/res/values-fi/translations.xml | 25 + .../src/main/res/values-fr/translations.xml | 11 + .../src/main/res/values-hu/translations.xml | 11 + .../impl/src/main/res/values/localazy.xml | 11 + .../src/main/res/values-fi/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 1 + .../src/main/res/values-el/translations.xml | 2 + .../src/main/res/values-fi/translations.xml | 4 +- .../src/main/res/values-fr/translations.xml | 1 + .../src/main/res/values-hu/translations.xml | 1 + .../impl/src/main/res/values/localazy.xml | 1 + .../src/main/res/values-fi/translations.xml | 2 +- .../src/main/res/values-fr/translations.xml | 4 +- .../src/main/res/values-fi/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 5 + .../src/main/res/values-el/translations.xml | 4 + .../src/main/res/values-et/translations.xml | 4 + .../src/main/res/values-hu/translations.xml | 5 + .../src/main/res/values-cs/translations.xml | 10 + .../src/main/res/values-el/translations.xml | 18 + .../src/main/res/values-fi/translations.xml | 14 + .../src/main/res/values-fr/translations.xml | 4 + .../src/main/res/values-hu/translations.xml | 4 + .../src/main/res/values-fi/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 19 + .../src/main/res/values-el/translations.xml | 21 + .../src/main/res/values-et/translations.xml | 2 + .../src/main/res/values-fi/translations.xml | 12 + .../src/main/res/values-fr/translations.xml | 19 + .../src/main/res/values-hu/translations.xml | 19 + .../src/main/res/values-ru/translations.xml | 2 + .../src/main/res/values/localazy.xml | 17 + ...anner_KnockRequestsBannerView_Day_0_de.png | 3 + ...anner_KnockRequestsBannerView_Day_1_de.png | 3 + ...anner_KnockRequestsBannerView_Day_2_de.png | 3 + ...anner_KnockRequestsBannerView_Day_3_de.png | 3 + ...anner_KnockRequestsBannerView_Day_4_de.png | 3 + ...anner_KnockRequestsBannerView_Day_5_de.png | 3 + ...anner_KnockRequestsBannerView_Day_6_de.png | 3 + ...anner_KnockRequestsBannerView_Day_7_de.png | 3 + ...pl.list_KnockRequestsListView_Day_1_de.png | 4 +- ...onlist_ActionListViewContent_Day_10_de.png | 4 +- ...onlist_ActionListViewContent_Day_11_de.png | 4 +- ...onlist_ActionListViewContent_Day_12_de.png | 4 +- ...ionlist_ActionListViewContent_Day_2_de.png | 4 +- ...ionlist_ActionListViewContent_Day_3_de.png | 4 +- ...ionlist_ActionListViewContent_Day_4_de.png | 4 +- ...ionlist_ActionListViewContent_Day_5_de.png | 4 +- ...ionlist_ActionListViewContent_Day_6_de.png | 4 +- ...ionlist_ActionListViewContent_Day_7_de.png | 4 +- ...ionlist_ActionListViewContent_Day_8_de.png | 4 +- ...ionlist_ActionListViewContent_Day_9_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_0_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_10_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_11_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_12_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_13_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_1_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_2_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_3_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_4_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_5_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_6_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_7_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_8_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_9_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_0_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_10_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_11_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_12_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_13_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_1_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_2_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_3_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_4_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_5_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_6_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_7_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_8_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_9_de.png | 4 +- ...pl.previews_DateFormatterModeView_0_de.png | 3 + ...pl.previews_DateFormatterModeView_1_de.png | 3 + ...pl.previews_DateFormatterModeView_2_de.png | 3 + ...pl.previews_DateFormatterModeView_3_de.png | 3 + ...pl.previews_DateFormatterModeView_4_de.png | 3 + ...DeleteConfirmationBottomSheet_Day_0_de.png | 3 + ...tails_MediaDetailsBottomSheet_Day_0_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_0_de.png | 3 + ...mpl.gallery_MediaGalleryView_Day_10_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_1_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_2_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_3_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_4_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_5_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_6_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_7_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_8_de.png | 3 + ...impl.gallery_MediaGalleryView_Day_9_de.png | 3 + ...ewer.impl.viewer_MediaViewerView_11_de.png | 3 + ...ewer.impl.viewer_MediaViewerView_12_de.png | 3 + ...iewer.impl.viewer_MediaViewerView_2_de.png | 4 +- screenshots/html/data.js | 1403 +++++++++-------- 104 files changed, 1159 insertions(+), 765 deletions(-) create mode 100644 features/knockrequests/impl/src/main/res/values-fi/translations.xml create mode 100644 libraries/dateformatter/impl/src/main/res/values-cs/translations.xml create mode 100644 libraries/dateformatter/impl/src/main/res/values-el/translations.xml create mode 100644 libraries/dateformatter/impl/src/main/res/values-et/translations.xml create mode 100644 libraries/dateformatter/impl/src/main/res/values-hu/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-el/translations.xml create mode 100644 libraries/mediaviewer/impl/src/main/res/values-fi/translations.xml create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png create mode 100644 screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_de.png create mode 100644 screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_0_de.png create mode 100644 screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_1_de.png create mode 100644 screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_2_de.png create mode 100644 screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_3_de.png create mode 100644 screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_4_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png create mode 100644 screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png diff --git a/features/knockrequests/impl/src/main/res/values-cs/translations.xml b/features/knockrequests/impl/src/main/res/values-cs/translations.xml index b8c4f6f125..c6aa055cd1 100644 --- a/features/knockrequests/impl/src/main/res/values-cs/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-cs/translations.xml @@ -4,15 +4,26 @@ "Opravdu chcete přijmout všechny žádosti o vstup?" "Přijmout všechny požadavky" "Přijmout vše" + "Nemohli jsme přijmout všechny žádosti. Chcete to zkusit znovu?" + "Nepodařilo se přijmout všechny žádosti" + "Přijímání všech žádostí o vstup" + "Tuto žádost jsme nemohli přijmout. Chcete to zkusit znovu?" + "Žádost se nepodařilo přijmout" + "Přijímání žádosti o vstup" "Ano, odmítnout a vykázat" "Opravdu chcete odmítnout a vykázat %1$s? Tento uživatel nebude moci znovu požádat o vstup do této místnosti." "Odmítnout a zakázat vstup" + "Odmítání vstupu a vykázání" "Ano, odmítnout" "Opravdu chcete odmítnout %1$s žádost o vstup do této místnosti?" "Odmítnout vstup" "Odmítnout a vykázat" + "Tuto žádost jsme nemohli odmítnout. Chcete to zkusit znovu?" + "Žádost se nepodařilo odmítnout" + "Odmítání žádosti o vstup" "Když někdo požádá o vstup do místnosti, uvidíte jeho žádost zde." "Žádná čekající žádost o vstup" + "Načítání žádostí o vstup…" "Žádosti o vstup" "%1$s +%2$d další chce vstoupit do této místnosti" diff --git a/features/knockrequests/impl/src/main/res/values-el/translations.xml b/features/knockrequests/impl/src/main/res/values-el/translations.xml index a59b6757db..326227df48 100644 --- a/features/knockrequests/impl/src/main/res/values-el/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-el/translations.xml @@ -4,13 +4,17 @@ "Σίγουρα θες να αποδεχτείς όλα τα αιτήματα συμμετοχής;" "Αποδοχή όλων των αιτημάτων" "Αποδοχή όλων" + "Αποδοχή όλων των αιτημάτων συμμετοχής" + "Γίνεται αποδοχή αιτήματος συμμετοχής" "Ναι, απόρριψη και αποκλεισμός" "Σίγουρα θες να απορρίψειε και να αποκλείσεις τον χρήστη %1$s; Αυτός ο χρήστης δεν θα μπορεί να ζητήσει πρόσβαση για να συμμετάσχει ξανά σε αυτό το δωμάτιο." "Απόρριψη και αποκλεισμός πρόσβασης" + "Γίνεται απόρριψη και αποκλεισμός πρόσβασης" "Ναι, απόρριψη" "Σίγουρα θες να απορρίψεις το αίτημα του χρήστη %1$s να συμμετάσχει στο δωμάτιο;" "Απόρριψη πρόσβασης" "Απόρριψη και αποκλεισμός" + "Γίνεται απόρριψη αιτήματος συμμετοχής" "Όταν κάποιος θα ζητήσει να συμμετάσχει στο δωμάτιο, θα μπορείς να δεις το αίτημά του εδώ." "Δεν υπάρχει εκκρεμές αίτημα συμμετοχής" "Αιτήματα συμμετοχής" diff --git a/features/knockrequests/impl/src/main/res/values-fi/translations.xml b/features/knockrequests/impl/src/main/res/values-fi/translations.xml new file mode 100644 index 0000000000..08b8509d31 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-fi/translations.xml @@ -0,0 +1,25 @@ + + + "Kyllä, hyväksy kaikki" + "Haluatko varmasti hyväksyä kaikki liittymispyynnöt?" + "Hyväksy kaikki pyynnöt" + "Hyväksy kaikki" + "Kyllä, hylkää ja anna porttikielto" + "Haluatko varmasti hylätä käyttäjän %1$s pyynnön liittyä huoneeseen ja antaa hänelle porttikiellon? Hän ei voi enää pyytää lupaa liittyä tähän huoneeseen." + "Hylkää ja anna porttikielto" + "Kyllä, hylkää" + "Haluatko varmasti hylätä käyttäjän %1$s pyynnön liittyä tähän huoneeseen?" + "Hylkää pyyntö" + "Hylkää ja anna porttikielto" + "Kun joku pyytää liittyä huoneeseen, näet hänen pyyntönsä täällä." + "Ei odottavia liittymispyyntöjä" + "Liittymispyynnöt" + + "%1$s +%2$d muu haluavat liittyä tähän huoneeseen" + "%1$s +%2$d muuta haluavat liittyä tähän huoneeseen" + + "Näytä kaikki" + "Hyväksy" + "%1$s haluaa liittyä tähän huoneeseen" + "Näytä" + diff --git a/features/knockrequests/impl/src/main/res/values-fr/translations.xml b/features/knockrequests/impl/src/main/res/values-fr/translations.xml index 39bc75f5c1..85da9d9f50 100644 --- a/features/knockrequests/impl/src/main/res/values-fr/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-fr/translations.xml @@ -4,15 +4,26 @@ "Êtes-vous sûr de vouloir accepter toutes les demandes pour rejoindre le salon ?" "Tout accepter" "Tout accepter" + "Toutes les demandes n’ont pas pu être acceptées. Voulez-vous réessayer ?" + "Toutes les demandes n’ont pas été acceptées" + "Accepter toutes les demandes à rejoindre" + "La demande n’a pas pu être acceptée. Voulez-vous réessayer ?" + "Impossible d’accepter la demande" + "Accepter la demande à rejoindre" "Oui, rejeter et bannir" "Êtes-vous sûr de vouloir rejeter la demande et bannir %1$s ? Cet utilisateur ne pourra pas demander à nouveau à rejoindre ce salon." "Refuser et interdire l’accès" + "En cours de traitement…" "Oui, refuser" "Êtes-vous sûr de vouloir refuser la demande de %1$s à rejoindre le salon ?" "Refuser l’accès" "Refuser et bannir" + "Nous n’avons pas pu refuser cette demande. Voulez-vous réessayer ?" + "Echec" + "Traitement en cours…" "Lorsque quelqu’un demandera à rejoindre le salon, vous pourrez voir sa demande ici." "Personne ne demande à rejoindre le salon" + "Chargement…" "Demandes en attente" "%1$s et %2$d autre personne souhaitent rejoindre ce salon" diff --git a/features/knockrequests/impl/src/main/res/values-hu/translations.xml b/features/knockrequests/impl/src/main/res/values-hu/translations.xml index ba1aa620c6..2ae4a79857 100644 --- a/features/knockrequests/impl/src/main/res/values-hu/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-hu/translations.xml @@ -4,15 +4,26 @@ "Biztos, hogy elfogadja az összes csatlakozási kérelmet?" "Minden kérés elfogadása" "Összes elfogadása" + "Nem sikerült az összes kérés fogadása. Újra megpróbálja?" + "Nem sikerült az összes kérés elfogadása" + "Összes csatlakozási kérés elfogadása" + "Nem sikerült elfogadni a kérést. Megpróbálja újra?" + "Nem sikerült elfogadni a kérést" + "Csatlakozási kérés elfogadása" "Igen, elutasítás és kitiltás" "Biztos, hogy elutasítja %1$s kérését és ki is tiltja? Többé nem fogja tudni azt kérni, hogy csatlakozhasson ehhez a szobához." "A hozzáférés elutasítása és kitiltás" + "A hozzáférés megtagadása és kitiltás" "Igen, elutasítás" "Biztos, hogy elutasítja %1$s kérését, hogy csatlakozzon a szobához?" "Hozzáférés elutasítása" "Elutasítás és kitiltás" + "Nem sikerült elutasítani a kérést. Megpróbálja újra?" + "Nem sikerült elutasítani a kérést" + "Csatlakozási kérés elutasítása" "Ha valaki csatlakozni kíván a szobához, itt láthatja a kérését." "Nincs függőben lévő csatlakozási kérelem" + "Csatlakozási kérések betöltése…" "Csatlakozási kérelmek" "%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához" diff --git a/features/knockrequests/impl/src/main/res/values/localazy.xml b/features/knockrequests/impl/src/main/res/values/localazy.xml index d2e9dc5d14..454bb8e193 100644 --- a/features/knockrequests/impl/src/main/res/values/localazy.xml +++ b/features/knockrequests/impl/src/main/res/values/localazy.xml @@ -4,15 +4,26 @@ "Are you sure you want to accept all requests to join?" "Accept all requests" "Accept all" + "We couldn’t accept all requests. Would you like to try again?" + "Failed to accept all requests" + "Accepting all requests to join" + "We couldn’t accept this request. Would you like to try again?" + "Failed to accept request" + "Accepting request to join" "Yes, decline and ban" "Are you sure you want to decline and ban %1$s? This user won’t be able to request access to join this room again." "Decline and ban from accessing" + "Declining and banning access" "Yes, decline" "Are you sure you want to decline %1$s request to join this room?" "Decline access" "Decline and ban" + "We couldn’t decline this request. Would you like to try again?" + "Failed to decline request" + "Declining request to join" "When somebody will ask to join the room, you’ll be able to see their request here." "No pending request to join" + "Loading requests to join…" "Requests to join" "%1$s +%2$d other want to join this room" diff --git a/features/lockscreen/impl/src/main/res/values-fi/translations.xml b/features/lockscreen/impl/src/main/res/values-fi/translations.xml index 6ed4ea81be..ae2abef6e8 100644 --- a/features/lockscreen/impl/src/main/res/values-fi/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-fi/translations.xml @@ -3,6 +3,7 @@ "biometrinen tunnistus" "biometrinen tunnistus" "Avaa biometrisellä" + "Vahvista biometrinen tunniste" "Unohtuiko PIN-koodi?" "Vaihda PIN-koodi" "Salli biometrinen tunnistus" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index 65218dda97..2946530857 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -57,6 +57,7 @@ "Žádosti o vstup" "Role a oprávnění" "Název místnosti" + "Zabezpečení a soukromí" "Zabezpečení" "Sdílet místnost" "Informace o místnosti" diff --git a/features/roomdetails/impl/src/main/res/values-el/translations.xml b/features/roomdetails/impl/src/main/res/values-el/translations.xml index 23d2705afa..88d651a83a 100644 --- a/features/roomdetails/impl/src/main/res/values-el/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-el/translations.xml @@ -49,6 +49,7 @@ "Πρόσκληση ατόμων" "Αποχώρηση από τη συζήτηση" "Αποχώρηση από το δωμάτιο" + "Πολυμέσα και αρχεία" "Προσαρμοσμένο" "Προεπιλογή" "Ειδοποιήσεις" @@ -56,6 +57,7 @@ "Αιτήματα συμμετοχής" "Ρόλοι και δικαιώματα" "Όνομα δωματίου" + "Ασφάλεια & απόρρητο" "Ασφάλεια" "Κοινή χρήση δωματίου" "Πληροφορίες δωματίου" diff --git a/features/roomdetails/impl/src/main/res/values-fi/translations.xml b/features/roomdetails/impl/src/main/res/values-fi/translations.xml index 6a397173f3..7aa3a105e1 100644 --- a/features/roomdetails/impl/src/main/res/values-fi/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fi/translations.xml @@ -49,10 +49,12 @@ "Kutsu ihmisiä" "Poistu keskustelusta" "Poistu huoneesta" + "Media ja tiedostot" "Mukautettu" "Oletus" "Ilmoitukset" "Kiinnitetyt viestit" + "Liittymispyynnöt" "Roolit ja oikeudet" "Huoneen nimi" "Turvallisuus" @@ -80,7 +82,7 @@ "Näytä profiili" "Porttikiellot" "Jäsenet" - "Kutsutut" + "Kutsuttu" "Poistetaan käyttäjää %1$s huoneesta…" "Ylläpitäjä" "Valvoja" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index 4a28b4b563..2e28e6e110 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -57,6 +57,7 @@ "Demandes en attente" "Rôles et autorisations" "Nom du salon" + "Sécurité & confidentialité" "Sécurité" "Partager le salon" "Informations du salon" diff --git a/features/roomdetails/impl/src/main/res/values-hu/translations.xml b/features/roomdetails/impl/src/main/res/values-hu/translations.xml index b88519c880..be27d0fb32 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -57,6 +57,7 @@ "Csatlakozási kérelem" "Szerepkörök és jogosultságok" "Szoba neve" + "Biztonság és adatvédelem" "Biztonság" "Szoba megosztása" "Szobainformációk" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 50c2d639be..bd602d3ed3 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -57,6 +57,7 @@ "Requests to join" "Roles and permissions" "Room name" + "Security & privacy" "Security" "Share room" "Room info" diff --git a/features/securebackup/impl/src/main/res/values-fi/translations.xml b/features/securebackup/impl/src/main/res/values-fi/translations.xml index 8d0c57cb7e..7daf180c6b 100644 --- a/features/securebackup/impl/src/main/res/values-fi/translations.xml +++ b/features/securebackup/impl/src/main/res/values-fi/translations.xml @@ -9,7 +9,7 @@ "Salli avainten säilytys" "Vaihda palautusavain" "Palauta kryptografinen identiteettisi ja viestihistoriasi palautusavaimella, jos olet menettänyt kaikki nykyiset laitteesi." - "Käytä palautusavainta" + "Syötä palautusavain" "Avainten säilytys ei ole tällä hetkellä synkronoitu." "Ota palautus käyttöön" "Pääset käsiksi salattuihin viesteihisi, jos menetät kaikki laitteesi tai olet kirjautunut ulos %1$s -sovelluksesta kaikkialla." diff --git a/features/securebackup/impl/src/main/res/values-fr/translations.xml b/features/securebackup/impl/src/main/res/values-fr/translations.xml index 8684fac185..3b3f2094fe 100644 --- a/features/securebackup/impl/src/main/res/values-fr/translations.xml +++ b/features/securebackup/impl/src/main/res/values-fr/translations.xml @@ -12,7 +12,7 @@ "Utiliser la clé de récupération" "Le stockage de vos clés est actuellement désynchronisé." "Configurer la sauvegarde" - "Accédez à vos messages chiffrés si vous perdez tous vos appareils ou que vous êtes déconnectés de %1$s partout." + "Accédez à vos messages chiffrés si vous perdez tous vos appareils ou que vous êtes déconnecté de %1$s partout." "Ouvrez %1$s sur un ordinateur" "Connectez-vous à nouveau à votre compte" "Lorsque vous devrez vérifier la session, choisissez %1$s" @@ -31,7 +31,7 @@ "Êtes-vous certain de vouloir désactiver la sauvegarde ?" "Désactiver la sauvegarde supprimera votre clé de récupération actuelle et désactivera d’autres mesures de sécurité. Dans ce cas :" "Pas d’accès à l’historique des discussions chiffrées sur vos nouveaux appareils" - "Perte de l’accès à vos messages chiffrés si vous êtes déconnectés de %1$s partout" + "Perte de l’accès à vos messages chiffrés si vous êtes déconnecté de %1$s partout" "Êtes-vous certain de vouloir désactiver la sauvegarde ?" "Obtenez une nouvelle clé de récupération dans le cas où vous avez oublié l’ancienne. Après le changement, l’ancienne clé ne sera plus utilisable." "Générer une nouvelle clé" diff --git a/features/verifysession/impl/src/main/res/values-fi/translations.xml b/features/verifysession/impl/src/main/res/values-fi/translations.xml index ec67f48d3a..c011022eb7 100644 --- a/features/verifysession/impl/src/main/res/values-fi/translations.xml +++ b/features/verifysession/impl/src/main/res/values-fi/translations.xml @@ -16,7 +16,7 @@ "Varmista, että alla olevat numerot vastaavat toisessa istunnossa näkyviä numeroita." "Vertaa numeroita" "Uusi kirjautumisesi on nyt vahvistettu. Sillä on pääsy salattuihin viesteihisi, ja muut käyttäjät näkevät sen luotettuna." - "Käytä palautusavainta" + "Syötä palautusavain" "Joko pyyntö aikakatkaistiin, pyyntö hylättiin tai vahvistus ei täsmännyt." "Vahvista, että se olet sinä, jotta näet aiemmat salatut viestisi." "Avaa laite, jossa olet jo kirjautuneena" diff --git a/libraries/dateformatter/impl/src/main/res/values-cs/translations.xml b/libraries/dateformatter/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..578211b754 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s v %2$s" + "Tento měsíc" + diff --git a/libraries/dateformatter/impl/src/main/res/values-el/translations.xml b/libraries/dateformatter/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..3b337d1e29 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,4 @@ + + + "Αυτό το μήνα" + diff --git a/libraries/dateformatter/impl/src/main/res/values-et/translations.xml b/libraries/dateformatter/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..896610a602 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,4 @@ + + + "Sel kuul" + diff --git a/libraries/dateformatter/impl/src/main/res/values-hu/translations.xml b/libraries/dateformatter/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..33778d84f1 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s itt: %2$s" + "Ebben a hónapban" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml index 9fc10afbad..a987f8957c 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-cs/translations.xml @@ -1,5 +1,9 @@ + "Tento soubor bude odstraněn z místnosti a členové k němu nebudou mít přístup." + "Smazat soubor?" + "Zde se zobrazí dokumenty, zvukové soubory a hlasové zprávy nahrané do této místnosti." + "Zatím nebyly nahrány žádné soubory" "Načítání souborů…" "Načítání médií…" "Soubory" @@ -7,4 +11,10 @@ "Obrázky a videa nahraná do této místnosti budou zobrazeny zde." "Zatím nebyla nahrána žádná média" "Média a soubory" + "Formát souboru" + "Název souboru" + "Tento soubor bude odstraněn z místnosti a členové k němu nebudou mít přístup." + "Smazat soubor?" + "Nahrál(a)" + "Nahráno" diff --git a/libraries/mediaviewer/impl/src/main/res/values-el/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..8452eb9158 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,18 @@ + + + "Αυτό το αρχείο θα αφαιρεθεί από την αίθουσα και τα μέλη δεν θα έχουν πρόσβαση σε αυτό." + "Διαγραφή αρχείου;" + "Φόρτωση αρχείων…" + "Φόρτωση πολυμέσων…" + "Αρχεία" + "Πολυμέσα" + "Εικόνες και βίντεο που μεταφορτώνονται σε αυτό το δωμάτιο θα εμφανίζονται εδώ." + "Δεν έχουν μεταφορτωθεί ακόμα πολυμέσα" + "Πολυμέσα και αρχεία" + "Μορφή αρχείου" + "Όνομα αρχείου" + "Αυτό το αρχείο θα αφαιρεθεί από το δωμάτιο και τα μέλη δεν θα έχουν πρόσβαση σε αυτό." + "Διαγραφή αρχείου;" + "Μεταφορτώθηκε από" + "Μεταφορτώθηκε στις" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-fi/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-fi/translations.xml new file mode 100644 index 0000000000..43cee7b95f --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-fi/translations.xml @@ -0,0 +1,14 @@ + + + "Ladataan tiedostoja…" + "Ladataan mediaa…" + "Tiedostot" + "Media" + "Tähän huoneeseen lähetetyt kuvat ja videot näytetään täällä." + "Mediaa ei ole vielä lähetetty" + "Media ja tiedostot" + "Tiedostomuoto" + "Tiedostonimi" + "Lähettäjä:" + "Lähetetty" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml index d12293ad43..aeaec0a9b0 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-fr/translations.xml @@ -2,6 +2,8 @@ "Ce fichier sera supprimé du salon et les membres n’y auront plus accès." "Supprimer le fichier ?" + "Les documents, les fichiers audio et les messages vocaux envoyés dans ce salon seront affichés ici." + "Aucun fichier n’a encore été envoyé" "Chargement des fichiers…" "Chargement des médias…" "Fichiers" @@ -11,6 +13,8 @@ "Médias et fichiers" "Format du fichier" "Nom du fichier" + "Ce fichier sera supprimé du salon et les membres n’y auront plus accès." + "Supprimer le fichier ?" "Envoyé par" "Envoyé le" diff --git a/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml index 8336caec01..087ab1b4c4 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-hu/translations.xml @@ -2,6 +2,8 @@ "Ez a fájl el lesz távolítva a szobából, és a tagok nem férhetnek hozzá." "Törli a fájlt?" + "A szobába feltöltött dokumentumok, hangfájlok és hangüzenetek itt jelennek meg." + "Még nincsenek fájlok feltöltve" "Fájlok betöltése…" "Média betöltése…" "Fájlok" @@ -11,6 +13,8 @@ "Média és fájlok" "Fájlformátum" "Fájlnév" + "Ez a fájl el lesz távolítva a szobából, és a tagok nem férhetnek hozzá." + "Törli a fájlt?" "Feltöltötte:" "Feltöltve:" diff --git a/libraries/textcomposer/impl/src/main/res/values-fi/translations.xml b/libraries/textcomposer/impl/src/main/res/values-fi/translations.xml index 4e5661dce0..a7d2913937 100644 --- a/libraries/textcomposer/impl/src/main/res/values-fi/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-fi/translations.xml @@ -4,7 +4,7 @@ "Numeroimaton luettelo päälle/pois" "Sulje muotoiluasetukset" "Koodilohko päälle/pois" - "Valinnainen kuvateksti…" + "Lisää kuvateksti" "Viesti…" "Luo linkki" "Muokkaa linkkiä" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 9479eb43c4..babe41a142 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -129,6 +129,7 @@ "Zobrazit na časové ose" "Zobrazit zdroj" "Ano" + "Ano, zkusit znovu" "O aplikaci" "Zásady používání" "Přidání titulku" @@ -150,6 +151,7 @@ "ID zařízení" "Přímý chat" "Znovu nezobrazovat" + "Stahování" "(upraveno)" "Úpravy" "Úprava titulku" @@ -333,6 +335,22 @@ Důvod: %1$s." "Zobrazit vše" "Chat" "Žádost o vstup odeslána" + "Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout." + "Požádat o vstup" + "Ano, povolit šifrování" + "Po aktivaci nelze šifrování místnosti deaktivovat. Historie zpráv bude viditelná pouze pro členy místnosti od doby, kdy byli pozváni nebo od té doby, co do místnosti vstoupili. +Nikdo kromě členů místnosti nebude moci číst zprávy. To může bránit správnému fungování robotů a propojení. +Nedoporučujeme povolovat šifrování pro místnosti, které může kdokoli najít a vstoupit do nich." + "Povolit šifrování?" + "Jakmile je povoleno, šifrování nelze zakázat." + "Šifrování" + "Povolit koncové šifrování" + "Každý může najít a vstoupit" + "Kdokoliv" + "Lidé mohou vstoupit, pouze pokud jsou pozváni" + "Pouze pro zvané" + "Přístup do místnosti" + "Zabezpečení a soukromí" "Sdílet polohu" "Sdílet moji polohu" "Otevřít v Mapách Apple" @@ -346,6 +364,7 @@ Důvod: %1$s." "Verze: %1$s (%2$s)" "en" "Historické zprávy nejsou na tomto zařízení k dispozici" + "Nemáte přístup k této zprávě" "Nelze dešifrovat zprávu" "Tato zpráva byla zablokována buď proto, že jste neověřili své zařízení, nebo proto, že odesílatel potřebuje ověřit vaši totožnost." diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index c05000f3a5..5ee3c84a84 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -148,6 +148,7 @@ "ID συσκευής" "Άμεση συνομιλία" "Να μην εμφανιστεί ξανά" + "Γίνεται λήψη" "(επεξεργάστηκε)" "Επεξεργάζεται" "Η λεζάντα επεξεργάζεται" @@ -328,6 +329,22 @@ "Προβολή Όλων" "Συνομιλία" "Το αίτημα συμμετοχής στάλθηκε" + "Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στο δωμάτιο, αλλά κάποιος διαχειριστής ή συντονιστής θα πρέπει να αποδεχθεί το αίτημα." + "Αίτημα συμμετοχής" + "Ναι, ενεργοποιήστε την κρυπτογράφηση" + "Μόλις ενεργοποιηθεί, η κρυπτογράφηση για ένα δωμάτιο δεν μπορεί να απενεργοποιηθεί. Το ιστορικό μηνυμάτων θα είναι ορατό μόνο για τα μέλη του δωματίου από τότε που προσκλήθηκαν ή από τότε που εντάχθηκαν στην αίθουσα. +Κανείς εκτός από τα μέλη του δωματίου δεν θα μπορεί να διαβάσει μηνύματα. Αυτό μπορεί να αποτρέψει τη σωστή λειτουργία των bots και των γεφυρών. +Δεν συνιστούμε να ενεργοποιήσεις την κρυπτογράφηση για δωμάτια στα οποία μπορεί κανείς να βρει και να συμμετάσχει." + "Ενεργοποίηση κρυπτογράφησης;" + "Μόλις ενεργοποιηθεί, η κρυπτογράφηση δεν μπορεί να απενεργοποιηθεί." + "Κρυπτογράφηση" + "Ενεργοποίηση κρυπτογράφησης από άκρο σε άκρο" + "Οποιοσδήποτε μπορεί να βρει και να συμμετάσχει" + "Οποιοσδήποτε" + "Τα άτομα μπορούν να συμμετάσχουν μόνο εάν έχουν προσκληθεί" + "Μόνο πρόσκληση" + "Πρόσβαση δωματίου" + "Ασφάλεια & απόρρητο" "Κοινή χρήση τοποθεσίας" "Κοινή χρήση της τοποθεσίας μου" "Άνοιγμα στο Apple Maps" @@ -340,4 +357,8 @@ "Τοποθεσία" "Έκδοση: %1$s (%2$s)" "el" + "Τα ιστορικά μηνύματα δεν είναι διαθέσιμα σε αυτήν τη συσκευή" + "Δεν έχεις πρόσβαση σε αυτό το μήνυμα" + "Δεν είναι δυνατή η αποκρυπτογράφηση μηνύματος" + "Αυτό το μήνυμα αποκλείστηκε είτε επειδή δεν επαλήθευσες τη συσκευή σου είτε επειδή ο αποστολέας πρέπει να επαληθεύσει την ταυτότητά σου." diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index 0d7f51baef..7baf58a5be 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -148,6 +148,7 @@ "Seadme tunnus" "Otsevestlus" "Ära enam näita seda uuesti" + "Laadime alla" "(muudetud)" "Muutmine" "Muudame selgitust" @@ -341,6 +342,7 @@ Põhjus: %1$s." "Versioon: %1$s (%2$s)" "et" "Vanu sõnumeid ei saa selles seadmes näha" + "Sul puudub ligipääs sellele sõnumile" "Sõnumi dekrüptimine ei õnnestu" "Kuna seade on verifitseerimata või saatja pole sind verifitseerinud, siis sõnumi näitamine on blokeeritud." diff --git a/libraries/ui-strings/src/main/res/values-fi/translations.xml b/libraries/ui-strings/src/main/res/values-fi/translations.xml index 2cc06a4271..34643e42d4 100644 --- a/libraries/ui-strings/src/main/res/values-fi/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fi/translations.xml @@ -32,6 +32,7 @@ "Nauhoita ääniviesti." "Lopeta nauhoittaminen" "Hyväksy" + "Lisää kuvateksti" "Lisää aikajanalle" "Takaisin" "Soita" @@ -45,8 +46,10 @@ "Vahvista salasana" "Jatka" "Kopioi" + "Kopioi kuvateksti" "Kopioi linkki" "Kopioi linkki viestiin" + "Kopioi teksti" "Luo" "Luo huone" "Deaktivoi" @@ -57,6 +60,7 @@ "Hylkää" "Valmis" "Muokkaa" + "Muokkaa kuvatekstiä" "Muokkaa kyselyä" "Ota käyttöön" "Lopeta kysely" @@ -91,6 +95,8 @@ "Reagoi" "Hylkää" "Poista" + "Poista kuvateksti" + "Poista viesti" "Vastaa" "Vastaa ketjuun" "Ilmoita virheestä" @@ -123,6 +129,7 @@ "Kyllä" "Tietoa" "Hyväksyttävän käytön käytäntö" + "Lisätään kuvatekstiä" "Edistyneet asetukset" "Analytiikka" "Ulkoasu" @@ -143,6 +150,7 @@ "Älä näytä tätä uudelleen" "(muokattu)" "Muokataan viestiä" + "Muokataan kuvatekstiä" "* %1$s %2$s" "Salaus" "Salaus käytössä" @@ -292,6 +300,7 @@ Syy: %1$s." "%1$s Android" "Raivostunut ravistaminen ilmoittaa virheestä" "Median valinta epäonnistui, yritä uudelleen." + "Kuvatekstit eivät välttämättä näy ihmisille, jotka käyttävät vanhempia sovelluksia." "Median käsittely epäonnistui, yritä uudelleen." "Median lähettäminen epäonnistui, yritä uudelleen." "Paina viestiä ja valitse “%1$s” lisätäksesi sen tänne." @@ -331,4 +340,7 @@ Syy: %1$s." "Sijainti" "Versio: %1$s (%2$s)" "fi" + "Viestihistoria ei ole saatavilla tällä laitteella" + "Viestin salauksen purkaminen ei onnistu" + "Tämä viesti estettiin, koska laitettasi ei ole vahvistettu tai koska lähettäjän on vahvistettava identiteettisi." diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index ecf513c269..3d75daa029 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -127,6 +127,7 @@ "Voir dans la discussion" "Afficher la source" "Oui" + "Oui, réessayez" "À propos" "Politique d’utilisation acceptable" "Ajout d’une légende" @@ -148,6 +149,7 @@ "Identifiant de session" "Discussion à deux" "Ne plus afficher" + "En cours de téléchargement" "(modifié)" "Édition" "Modification de la légende" @@ -328,6 +330,22 @@ Raison : %1$s." "Voir tout" "Discussion" "Demande d’adhésion envoyée" + "N’importe qui peut demander à rejoindre le salon, mais un administrateur ou un modérateur devra accepter la demande." + "Demander à rejoindre" + "Oui, activer le chiffrement" + "Une fois activé, le chiffrement d’un salon ne peut pas être désactivé. L’historique des messages ne sera visible que pour les membres depuis qu’ils ont été invités ou depuis qu’ils ont rejoint le salon. +Personne d’autre que les membres du salon ne pourra lire les messages. Cela peut empêcher les bots et les bridges de fonctionner correctement. +Nous ne recommandons pas d’activer le chiffrement pour les salons que tout le monde peut trouver et rejoindre." + "Activer le chiffrement ?" + "Une fois activé, le chiffrement ne peut pas être désactivé." + "Chiffrement" + "Activer le chiffrement de bout en bout" + "Tout le monde peut le trouver et le rejoindre" + "Tout le monde" + "Le salon ne peut être joint que par les personnes invitées" + "Sur invitation uniquement" + "Accès au salon" + "Sécurité & confidentialité" "Partage de position" "Partager ma position" "Ouvrir dans Apple Maps" @@ -341,6 +359,7 @@ Raison : %1$s." "Version : %1$s ( %2$s )" "fr" "Les anciens messages ne sont pas disponibles sur cet appareil" + "Vous n’avez pas accès à ce message" "Impossible de déchiffrer le message" "Ce message a été bloqué soit parce que vous n’avez pas vérifié votre session, soit parce que l’expéditeur doit vérifier votre identité." diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index 0f1bb7507f..4fe2418fa6 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -127,6 +127,7 @@ "Megtekintés az idővonalon" "Forrás megtekintése" "Igen" + "Igen, újrapróbálkozás" "Névjegy" "Elfogadható használatra vonatkozó szabályzat" "Felirat hozzáadása" @@ -148,6 +149,7 @@ "Eszközazonosító" "Közvetlen csevegés" "Ne jelenjen meg többé" + "Letöltés" "(szerkesztve)" "Szerkesztés" "Felirat szerkesztése" @@ -328,6 +330,22 @@ Ok: %1$s." "Összes megtekintése" "Csevegés" "Csatlakozási kérés elküldve" + "Bárki kérheti, hogy csatlakozzon a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést." + "Csatlakozás kérése" + "Igen, engedélyezze a titkosítást" + "Az engedélyezés után a szoba titkosítása nem tiltható le. Az üzenetek előzményei csak a szobatagok számára láthatók, amikor meghívást kaptak, vagy mióta csatlakoztak a szobához. +A szobatagokon kívül senki sem tudja olvasni az üzeneteket. Ez megakadályozhatja a botok és a hidak megfelelő működését. +Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket bárki megtalálhat és csatlakozhat." + "Engedélyezi a titkosítást?" + "Engedélyezés után a titkosítás nem tiltható le." + "Titkosítás" + "Végpontok közötti titkosítás engedélyezése" + "Bárki megtalálhatja és csatlakozhat" + "Bárki" + "Az emberek csak akkor csatlakozhatnak, ha meghívást kapnak" + "Csak meghívással" + "Szobahozzáférés" + "Biztonság és adatvédelem" "Hely megosztása" "Saját hely megosztása" "Megnyitás az Apple Mapsben" @@ -341,6 +359,7 @@ Ok: %1$s." "Verzió: %1$s (%2$s)" "hu" "A korábbi üzenetek nem érhetők el ezen az eszközön" + "Nincs hozzáférése ehhez az üzenethez" "Nem sikerült visszafejteni az üzenetet" "Ez az üzenet azért lett blokkolva, mert vagy nem ellenőrizte az eszközt, vagy a feladónak ellenőriznie kell az Ön személyazonosságát." 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 b2a05f452c..2b18c44044 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -150,6 +150,7 @@ "Идентификатор устройства" "Личный чат" "Не показывать больше" + "Загрузка" "(изменено)" "Редактирование" "Редактирование подписи" @@ -346,6 +347,7 @@ "Версия: %1$s (%2$s)" "ru" "На этом устройстве недоступна история сообщений" + "У вас нет доступа к этому сообщению" "Не удалось расшифровать сообщение" "Это сообщение было заблокировано по причине того, что вы не подтвердили свое устройство, либо отправителю необходимо подтвердить вашу личность." diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index b571a23230..a15deb9fab 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -127,6 +127,7 @@ "View in timeline" "View source" "Yes" + "Yes, try again" "About" "Acceptable use policy" "Adding caption" @@ -329,6 +330,22 @@ Reason: %1$s." "View All" "Chat" "Request to join sent" + "Anyone can ask to join the room but an administrator or moderator will have to accept the request." + "Ask to join" + "Yes, enable encryption" + "Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room. +No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly. +We do not recommend enabling encryption for rooms that anyone can find and join." + "Enable encryption?" + "Once enabled, encryption cannot be disabled." + "Encryption" + "Enable end-to-end encryption" + "Anyone can find and join" + "Anyone" + "People can only join if they are invited" + "Invite only" + "Room access" + "Security & privacy" "Share location" "Share my location" "Open in Apple Maps" diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png new file mode 100644 index 0000000000..037c8ce9a1 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8f78e95438b2d7fe2712683ffb692c8d7069f1395b28f9c87f4bef82f17d5a7 +size 32384 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png new file mode 100644 index 0000000000..3b0faba911 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcdd1ec52cbe0db11941ce6a7a533053ab532b5acc0e6ea1b2f69a744dc4e0ac +size 37972 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png new file mode 100644 index 0000000000..34c04cbfd4 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc19b12b6617d7d8a2209cf80b27b4183c53a83b232060c0c8aef7cd4e3df25c +size 21093 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png new file mode 100644 index 0000000000..5c01877a84 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34a6e5bee161e910ff97c7925f49e52c416480abbd5bd19228a27fa8b0d917ce +size 21802 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png new file mode 100644 index 0000000000..5688c7d06a --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a1c367f94edbdac729a3ed3bb98dca74ec5ce5264b25f8d102a2be0bc2ae9c3 +size 29342 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png new file mode 100644 index 0000000000..037c8ce9a1 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8f78e95438b2d7fe2712683ffb692c8d7069f1395b28f9c87f4bef82f17d5a7 +size 32384 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png new file mode 100644 index 0000000000..037c8ce9a1 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8f78e95438b2d7fe2712683ffb692c8d7069f1395b28f9c87f4bef82f17d5a7 +size 32384 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_de.png new file mode 100644 index 0000000000..12c9aaa2d6 --- /dev/null +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9b9a318514fcbf0bb6de4fd8c45f7d30d0044a085db23972cfd05c96cca1350 +size 42207 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png index db76d16a1b..cf7480bdac 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f88e93f921e81c8b57a9a6d37a11798df9b550d1ad4224f5605403940f752df4 -size 28956 +oid sha256:d2660a313aa5910965ea16ed0f522aa6b3d9ba338ad1982b6f56d8ffbc0540b8 +size 29320 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png index b0bf21949f..7e30e444ff 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85b0a307f751b991e16ec61e27222fb3a51808c1ea81869eb3513925c18977ee -size 27513 +oid sha256:ef6458d23f92e73f47d54f82ac38ec090f2f7663e353972bdb4581fd69911711 +size 30604 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png index 10051a39ee..cfe31bc8fe 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ab5fd552ac94ea96da1403099877e171751d8b4521f425041407c59f3d659cd -size 52022 +oid sha256:3b03b6a9c407472a2680e86ea89fb8a1839fccf694e2038b419e3b304b2a7340 +size 54205 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png index fff94dc871..4cb71805e1 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc3877eccf0d9fb26445089ead8d8b69ef836929735ca15354be68549690e555 -size 52146 +oid sha256:5b1350554c526392f766812801a90d6e18e74b02429263ad7b3e54adcd298a0f +size 54371 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png index 7f7a3a82b8..fd49a2148d 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:906b08c68d09d8c5ca41f518928c92239eb90ab30153e827c37f9d0036daca87 -size 43970 +oid sha256:fa5a8d5c71a816f4ab1e2b7170cf261eb28cad216621a5f4104749d8aef6c8bc +size 46166 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png index 6ff70baa53..b4e80404ff 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa19b5cd2d27493f0f93820d80649681fda843cc87dab6adf5db6437cc7c1d67 -size 48970 +oid sha256:7425b161321252c543f232c93e411a35cd1a767b02021678455a24b38c759168 +size 50410 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png index a63acd006b..8f225b0f65 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cac29a125e8ea11e65d6fdbe1282c0525f12f8aeaf251fa11103ca6abbe79222 -size 46501 +oid sha256:b4164e30426cd4ea70302c4cb7c0b66bbba87f88780fd5e07fd6a21d184a4ea3 +size 48830 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png index ec2c3e8463..26be22d138 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93f885fa860add5822c93522ff3a85c241f236f514007c9e6be4151842c39848 -size 41845 +oid sha256:4afee7f68d5a9ab85842af93654a5cafd7650994124e8b2bd01637b3994500a8 +size 44096 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png index 8fbc79fcf3..fb77e99ad9 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:989f76fb00e26b75827c9e28887db83d27eaa955a26fde20cc4a900f17cba0d8 -size 46727 +oid sha256:dcc47ba80c3085e81a71fd1111270d5f9995de541d7247bf98929e87ec979bec +size 49094 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png index b56d48d004..614d3a058d 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6eb5f6d73426f470d55b81dc83f2b3148c57bd358299775910c2a882ae1a1825 -size 42920 +oid sha256:768bf1f4ab4f3c672ccacf874c1e2ec97c527881cda62cf03e9fc7fc00b19fc8 +size 45264 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png index 8d10d0b0fb..36778ac3b3 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5995b1b0090fcc93144f5305a75b8071e9a77610b9e6d2f2f69a392832386921 -size 45944 +oid sha256:bc98594298bcdf743dbd1a11308ac189ceafedf28b63998f8b10829b770e8ebb +size 48309 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png index 0fda154e00..ea02c9e4ca 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a71b6ebe1727f31bd147963247ed16c25ba18379e8f98c3ca8ef6ba3c119f9b -size 35606 +oid sha256:3bfd6a17ab1a2ab55ea020afb2a3463801892d0f6532bff1391a57ff1c288148 +size 38050 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png index d8024b6d6b..8871186de7 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cdb41e5b33d52884986643249d7a42e93636a4e93b632138f4d3f774ddbca96 -size 44333 +oid sha256:d425759ce4b53ac6d22c6d7e4d7247475c27c8215f9921dacc6841a53a9d1142 +size 45124 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png index c7f71897e3..b3545619f2 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd9287cf3ac81b3b4a6540680fc300de0eff1b340cbb266f777f08da95771061 -size 46477 +oid sha256:8f75f0bebda631a579af2e2134c6607c7c4f17bb754103efed4635a018e01a9c +size 43055 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png index 416513ad1d..5dedf20373 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c9d5236b9d015df5c1b7d552bf8581e08b323c6fda64486f317f0d128d2dd8a -size 44965 +oid sha256:cefb929363fd99af735a72dbabaea5739070d0650a9d8246a8a5d5692e3048f5 +size 41528 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png index 620f6be4cd..c9950f17fb 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f7700caa63e98fda360e728203e9335e854b91c2b7f81216c07797d3c934c52 -size 48711 +oid sha256:8ce4ddfc7fc5631cb8e29fef0b16304945880eb168ab189d7f39b32e256a66fe +size 45310 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png index 741fa7213e..4759f565fa 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03095ef49ba0cbde8b47c9a847dd289b4c1a24e172462525916ae11a210ee40f -size 46947 +oid sha256:492b29d4ef8c74cf034b69bd275e3b3f347dbbaa2721da562333931e88ae32c0 +size 43536 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png index 208bdd75f0..6e76284a39 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1b09c9d1731e44795d1e1bf223e5cf36a788871da45db0fff15926b232ae386 -size 42841 +oid sha256:0b858541821efc8d2af82d187f690104835d6f160f54a4e2ccda679365fc2631 +size 45065 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png index 7ba1f6f69a..9fe459ab48 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb4b0c46733c0a85e2b8bb8988c65d1921beac559059db298bb68a53eaf6e916 -size 43872 +oid sha256:ab0f0595b3e5d938385fa7deca3c8659d61a341609d3d4abb4c1710a14dba9f8 +size 41639 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png index 93378213f2..8c705242a4 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90c3d0bcc4c19fd9bde289a9ecf1be62f138a100bb778566834e586b1f851514 -size 41954 +oid sha256:498fbafd36c7937ad4dee2aae3ff62dad5308e9e4d652cfcc752382e643d5c05 +size 43963 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png index aa2e03e152..9ff5b22ea1 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c005f4f95aefeee9ba0c377ed9fdbdfddfe08de46c84b39ed5040fc7ce150ff -size 47300 +oid sha256:ad50323615fac194d9b9f4eedc5af23c6bbf4e93115f9c248e2c2d8576f2e548 +size 41655 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png index 7304223f0b..9fa130f77c 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:961b01e9819ec6cfe9cb9a2f9d886f31522fc074f242ba46d68425c58d2e9fc4 -size 44652 +oid sha256:cf47366437b0584743b500130c053492c8a5949a2460b970f05d4c159dd85e4d +size 40602 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png index 770f470eca..b6629b1245 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44b04195c62acb148b2141aca28b196da98d16d69d157c74799ce6448244069b -size 42321 +oid sha256:0122d333057a6bd11a8656a829b4802763ce4888c7162cb59a748cc4983f1249 +size 44306 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png index 7e512a91dd..5ba62eefa6 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:509c49c39b91b08574895a3eef883d144be39875eb8b73e2dc1a134f800aaa6b -size 47996 +oid sha256:29d478297b24260081d2d8cfd6f0c831dd10b9034898ad51d4ef20e00e5650f1 +size 44589 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png index 8679141165..9cb36c5613 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74c22cae39733ed87399930e280a0b0e8349214c421cd1b4dd3ca8c627701026 -size 46847 +oid sha256:9f3b9914340665a33055e3ad3818d95c19f02494ca0dc595ad5609418c8fa351 +size 43433 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png index 748fb71ce6..c97daa7de0 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a9c496e14d257d6a795746e63a49a51e38acd681da5536a802ead4a07e10d93 -size 45841 +oid sha256:1bb4a925033aefb5040fcc86b955de736103ec01cc1843de7a866f4a67fc3c26 +size 42412 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png index 4b10e12a57..34aa4b52aa 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25756397b0b0c680f2dec68cabe3fd3ab4143e3bbb51c6a5a74fc9e4d4a1eb1d -size 45462 +oid sha256:82f1b321eebce911ed8a370ba7157dca53d88ff9ddcfe67d5e0e9cd6fe537c9b +size 46322 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png index 63928bced5..2a00f84266 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc88d4575e116f6140be3dda7671ab31f7cb01361599b316a35abc181cd712b3 -size 47694 +oid sha256:cab57b99da9930b37a85d1f47ccaba5ead9f3f766b69195e21d24840f7cf7801 +size 44146 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png index 96e46836ac..d5a95d0d2f 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a7bd3b8e62ee11c28e60b843f02e0eb979c1542162dad0c744875261bf0351d -size 46149 +oid sha256:ac06406bf6ccb21d213f54703c86f56912868e22c53f3b25512cfa1e5857ab4d +size 42578 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png index e0a94ffc9e..d042837947 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bc966615b5fdfa6e175e9f8719d2165432979650da63e49b266a2b4c6476676 -size 49509 +oid sha256:11d76f61037c90160b3a8907c4658a953864a3f70b1dee01d51ead2fba2b8304 +size 45989 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png index 30905e6234..6068dbf315 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54b8b26f2e613f47ff8d61e9d95cf0c8276d581e055318087612d65e2d6b3906 -size 48172 +oid sha256:b768e0eed672cd9f44e893e8dffaf65172b53a326c82ef92d0bf91e6dc3b590a +size 44623 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png index 72918f3603..8b4b53ce1c 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23ed6de2bdb340fbe1cc1df828c1e9aab94a6fc4dc2ecc065e62ea6221fd8c6a -size 44072 +oid sha256:4e0c782561e7908ccf850999535a033e5d5335e9bf803914ff5628e73d967e3f +size 46464 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png index 0dfe364e10..9f5b039858 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d8e3b3e577734855df04202b71d14b67305b6ac2ee94ad76463bf566f2ce0b3 -size 45163 +oid sha256:71a92f09bbc7154847e18913efb257832f8c923e16d22cbe2f5192da8b79f3c2 +size 42980 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png index 8642af7f91..37f890e3a9 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4b4cd38658310e85e9ddf998d11ba71dc6f86f283b12d7f17ccfd0e8c9e2a8c -size 42936 +oid sha256:4d757b721581c9af6f514fe77706f5754b55fadc1f1741745e790ce106cb80eb +size 45046 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png index ab024d6a8a..f2e0cbba03 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2a6b836c92ec82cca629e3a6eedb88ff54a4d6835fe2273bec70b4976552b47 -size 48531 +oid sha256:a0f33ff72cf22c76a287240b478199b22fe9aac47396afc9977420aa6635ce3e +size 42663 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png index 6f64e91347..49d25686a4 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba73053e9cae0bd22110a56105b6ccb9e7a031c2d0f2426713b524ae204589ed -size 45844 +oid sha256:90c91b006f581e53ca7dad338541ca11e54c28e5f727a762724bfb8b55af336e +size 41574 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png index c74aa90e56..86b61a41d8 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97e2145171681c40026f49f2c36d5375b679c6b01482d83aaa433bbbe95a5bbe -size 43594 +oid sha256:084e692b87ce3e1bca84b4b523472b008470d1186943cfb9edd517bce203fee2 +size 45628 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png index 7120d62d2b..90a268259d 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee8d7ab50be5c2b2b7edf00dffea3049719c3da73446e2e357c9b9ef73542129 -size 49312 +oid sha256:75a2dfa1a15c2934aff43378aae990f1bcb31fff5c0fdfae2e422f7a8c1c6657 +size 45787 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png index f531ecc171..43c1408518 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a21b5087be13fc6a3eb484e61b3b6a7f82a998db5a0637211aa6182bb4c5e130 -size 48121 +oid sha256:70ca007b72b292a47043ff15aa5b993c58debd875ce18fb27838f8e741771a75 +size 44579 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png index 1291a4b1f4..2a007ada50 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c9ff7395ff777f0ef5589c284da266d1bd5507c237661023a8b392b03bf4210 -size 47017 +oid sha256:9e592ea37d4e5a434bb485ee3a1cfc447cb30e087dd6f59190f82c76a3f26d67 +size 43467 diff --git a/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_0_de.png b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_0_de.png new file mode 100644 index 0000000000..acd5520e3f --- /dev/null +++ b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f68e4700488f385b2f087d6146dc824a8c8658d0e319ed885824758253b68b3 +size 98799 diff --git a/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_1_de.png b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_1_de.png new file mode 100644 index 0000000000..eff9f40e2c --- /dev/null +++ b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:872452fc3d6cf65c479fcbcdceaad74454abf4edb6955207526a72f1284076dd +size 83442 diff --git a/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_2_de.png b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_2_de.png new file mode 100644 index 0000000000..57fdb84910 --- /dev/null +++ b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d765335160d7fd0037ff32c5c98dcd0bcabb73e019e5c45be738359a90bc6890 +size 87846 diff --git a/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_3_de.png b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_3_de.png new file mode 100644 index 0000000000..c22f29397c --- /dev/null +++ b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9fc51b69b66dfdb1c0a5ae3608b054897cdbbc6ee4652c62b99707ccd2b6183 +size 78073 diff --git a/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_4_de.png b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_4_de.png new file mode 100644 index 0000000000..d9940453cb --- /dev/null +++ b/screenshots/de/libraries.dateformatter.impl.previews_DateFormatterModeView_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47e7e518ab182ebc202089f4d0c00bb9bc66c792c98d5ac9d3c304a49149eebc +size 75659 diff --git a/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png new file mode 100644 index 0000000000..82873fa1af --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f99c07228385b0f6e55c56c646313f71704527bdb6fd0bd6db1e3de2023c1453 +size 31736 diff --git a/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png new file mode 100644 index 0000000000..58e74b79a2 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da37a12f69987fb0da647a554b19003d8848dcb031b47ec6fc805ac1c3545bcb +size 37871 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png new file mode 100644 index 0000000000..70de94855c --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b11021336c578ecac2aa14aa6eb46e4e90f8e5d669a93972f03d310d7ca4117d +size 17960 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png new file mode 100644 index 0000000000..f85206c5a4 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11caaa5e8211c15870ec43394e5caee37c53aa2c6694ff80a3abe168e12d95fe +size 15233 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png new file mode 100644 index 0000000000..70de94855c --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b11021336c578ecac2aa14aa6eb46e4e90f8e5d669a93972f03d310d7ca4117d +size 17960 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png new file mode 100644 index 0000000000..78189ef5a7 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76835141c32686a88d2079f6fd29e13f4138e96d65a787c8e2bc3d09efe8003b +size 31487 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png new file mode 100644 index 0000000000..2e4cf5001b --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ddea5e17f11507e89971ae989ccf086be5f96befb1ca415f906353547739294 +size 18915 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png new file mode 100644 index 0000000000..10b88c4bb9 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16f1fe0273366dfef31dbe47130f15ba8ba815c3bcd9b3bd1082dc2fd6a10f09 +size 18076 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png new file mode 100644 index 0000000000..10b88c4bb9 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16f1fe0273366dfef31dbe47130f15ba8ba815c3bcd9b3bd1082dc2fd6a10f09 +size 18076 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png new file mode 100644 index 0000000000..ae2864d0d4 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66bd49d3b004205daf1e85398c1b879137f7a88367355c079e3fe9ee942ffe2a +size 29252 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png new file mode 100644 index 0000000000..3baca8477d --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:274279303004121b7716cc292272ff7164e772ad6949c322d2e34843f633f1d6 +size 41516 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png new file mode 100644 index 0000000000..14bf073dd5 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ba2a2125d1e3bf3f7bf4209714ab90b09319e2a6fd4cf3689ae489f1c9e49dd +size 44970 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png new file mode 100644 index 0000000000..7f5b267ac5 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77e765f8752cf596ba76ed5d4a351968ed272069b1c508c041ab32d1b61d5289 +size 15165 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png new file mode 100644 index 0000000000..3d3b373fdd --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d202f768dd0df5f48579764515ff73dd43ef5f61697e2127b4d22d23c514bf7c +size 38644 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png new file mode 100644 index 0000000000..3c2bb54f73 --- /dev/null +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f472dc0688242ec7d398b69ab8933019c740ee2a717dc503b1098e1f7d10474 +size 32802 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png index 013fa9a110..7453125930 100644 --- a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4d2b461afe45b703554689435ca19adab44e3190e23b1a71435644164deaba1 -size 71628 +oid sha256:e016fcaccf00e8bb0e09b9e9fd0243769b5096a3fec4300a0fcaafce0236f941 +size 72153 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index 698b0d466d..f32931e67e 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,59 +1,59 @@ // Generated file, do not edit export const screenshots = [ ["en","en-dark","de",], -["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20063,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20070,], ["features.invite.impl.response_AcceptDeclineInviteView_Day_0_en","features.invite.impl.response_AcceptDeclineInviteView_Night_0_en",0,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",20063,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",20063,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",20063,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",20063,], -["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20063,], -["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20063,], -["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20063,], -["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20063,], -["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20063,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",20070,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",20070,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",20070,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",20070,], +["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20070,], +["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20070,], +["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20070,], +["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20070,], +["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20070,], ["features.login.impl.accountprovider_AccountProviderView_Day_0_en","features.login.impl.accountprovider_AccountProviderView_Night_0_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_1_en","features.login.impl.accountprovider_AccountProviderView_Night_1_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_2_en","features.login.impl.accountprovider_AccountProviderView_Night_2_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_3_en","features.login.impl.accountprovider_AccountProviderView_Night_3_en",0,], ["features.messages.impl.actionlist_ActionListViewContent_Day_0_en","features.messages.impl.actionlist_ActionListViewContent_Night_0_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20070,], ["features.messages.impl.actionlist_ActionListViewContent_Day_1_en","features.messages.impl.actionlist_ActionListViewContent_Night_1_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20063,], -["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20063,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20063,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20063,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20063,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20063,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",20063,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",20063,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",20063,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",20063,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en",20063,], -["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20063,], -["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20063,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20063,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20063,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20063,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20063,], +["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20070,], +["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20070,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20070,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20070,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20070,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20070,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",20070,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",20070,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",20070,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",20070,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en",20070,], +["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20070,], +["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20070,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20070,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20070,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20070,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20070,], ["libraries.designsystem.components.async_AsyncActionView_Day_0_en","libraries.designsystem.components.async_AsyncActionView_Night_0_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20063,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20070,], ["libraries.designsystem.components.async_AsyncActionView_Day_2_en","libraries.designsystem.components.async_AsyncActionView_Night_2_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20063,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20070,], ["libraries.designsystem.components.async_AsyncActionView_Day_4_en","libraries.designsystem.components.async_AsyncActionView_Night_4_en",0,], -["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20063,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20070,], ["libraries.designsystem.components.async_AsyncIndicatorFailure_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorFailure_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncIndicatorLoading_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorLoading_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncLoading_Day_0_en","libraries.designsystem.components.async_AsyncLoading_Night_0_en",0,], -["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20063,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20070,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_0_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_0_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_1_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_1_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_2_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_2_en",0,], @@ -63,13 +63,17 @@ export const screenshots = [ ["libraries.matrix.ui.components_AttachmentThumbnail_Day_6_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_6_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_7_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_7_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_8_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_8_en",0,], -["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20063,], -["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20063,], -["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20063,], -["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20063,], +["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20070,], +["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20070,], +["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20070,], +["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20070,], ["features.messages.impl.attachments.preview_AttachmentsView_4_en","",0,], -["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20063,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20063,], +["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20070,], +["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en",0,], +["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en",0,], +["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en",0,], +["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en",0,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20070,], ["libraries.designsystem.components.avatar_Avatar_Avatars_0_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_10_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_11_en","",0,], @@ -149,19 +153,25 @@ export const screenshots = [ ["libraries.designsystem.components.avatar_Avatar_Avatars_79_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_7_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_80_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_81_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_82_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_83_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_84_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_85_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_86_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_8_en","",0,], ["libraries.designsystem.components.avatar_Avatar_Avatars_9_en","",0,], ["libraries.designsystem.components.button_BackButton_Buttons_en","",0,], ["libraries.designsystem.components_Badge_Day_0_en","libraries.designsystem.components_Badge_Night_0_en",0,], ["libraries.designsystem.components_BigCheckmark_Day_0_en","libraries.designsystem.components_BigCheckmark_Night_0_en",0,], ["libraries.designsystem.components_BigIcon_Day_0_en","libraries.designsystem.components_BigIcon_Night_0_en",0,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20063,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20063,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20063,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20063,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20063,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20063,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20063,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20070,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20070,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20070,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20070,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20070,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20070,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20070,], ["libraries.designsystem.components_BloomInitials_Day_0_en","libraries.designsystem.components_BloomInitials_Night_0_en",0,], ["libraries.designsystem.components_BloomInitials_Day_1_en","libraries.designsystem.components_BloomInitials_Night_1_en",0,], ["libraries.designsystem.components_BloomInitials_Day_2_en","libraries.designsystem.components_BloomInitials_Night_2_en",0,], @@ -172,116 +182,123 @@ export const screenshots = [ ["libraries.designsystem.components_BloomInitials_Day_7_en","libraries.designsystem.components_BloomInitials_Night_7_en",0,], ["libraries.designsystem.components_Bloom_Day_0_en","libraries.designsystem.components_Bloom_Night_0_en",0,], ["libraries.designsystem.theme.components_BottomSheetDragHandle_Day_0_en","libraries.designsystem.theme.components_BottomSheetDragHandle_Night_0_en",0,], -["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20063,], -["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20063,], -["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20063,], -["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20063,], -["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20063,], +["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20070,], +["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20070,], +["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20070,], +["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20070,], +["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20070,], ["libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.molecules_ButtonRowMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonRowMolecule_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_0_en","features.messages.impl.timeline.components_CallMenuItem_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_1_en","features.messages.impl.timeline.components_CallMenuItem_Night_1_en",0,], -["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20063,], -["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20063,], +["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20070,], +["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20070,], ["features.messages.impl.timeline.components_CallMenuItem_Day_4_en","features.messages.impl.timeline.components_CallMenuItem_Night_4_en",0,], ["features.call.impl.ui_CallScreenPipView_Day_0_en","features.call.impl.ui_CallScreenPipView_Night_0_en",0,], ["features.call.impl.ui_CallScreenPipView_Day_1_en","features.call.impl.ui_CallScreenPipView_Night_1_en",0,], ["features.call.impl.ui_CallScreenView_Day_0_en","features.call.impl.ui_CallScreenView_Night_0_en",0,], -["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20063,], -["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20063,], -["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20063,], -["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20066,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",20063,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20063,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20063,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20070,], +["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20070,], +["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20070,], +["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20070,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20070,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20070,], ["features.login.impl.changeserver_ChangeServerView_Day_0_en","features.login.impl.changeserver_ChangeServerView_Night_0_en",0,], -["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20063,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20063,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20070,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20070,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20063,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20070,], ["libraries.designsystem.theme.components_Checkboxes_Toggles_en","",0,], ["libraries.designsystem.theme.components_CircularProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components_ClickableLinkText_Text_en","",0,], ["libraries.designsystem.theme_ColorAliases_Day_0_en","libraries.designsystem.theme_ColorAliases_Night_0_en",0,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20063,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20063,], -["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20063,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20070,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20070,], +["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20070,], ["libraries.textcomposer_ComposerModeView_Day_1_en","libraries.textcomposer_ComposerModeView_Night_1_en",0,], ["libraries.textcomposer_ComposerModeView_Day_2_en","libraries.textcomposer_ComposerModeView_Night_2_en",0,], ["libraries.textcomposer_ComposerModeView_Day_3_en","libraries.textcomposer_ComposerModeView_Night_3_en",0,], ["libraries.textcomposer.components_ComposerOptionsButton_Day_0_en","libraries.textcomposer.components_ComposerOptionsButton_Night_0_en",0,], ["libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en","",0,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20063,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20063,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20070,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20070,], ["features.preferences.impl.developer.tracing_ConfigureTracingView_Day_0_en","features.preferences.impl.developer.tracing_ConfigureTracingView_Night_0_en",0,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20063,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20063,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20063,], -["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20063,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20070,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20070,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20070,], +["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20070,], ["libraries.designsystem.components.dialogs_ConfirmationDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ConfirmationDialog_Day_0_en","libraries.designsystem.components.dialogs_ConfirmationDialog_Night_0_en",0,], ["features.networkmonitor.api.ui_ConnectivityIndicatorView_Day_0_en","features.networkmonitor.api.ui_ConnectivityIndicatorView_Night_0_en",0,], -["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20063,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20063,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20063,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20063,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20063,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20063,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20063,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20063,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20063,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20063,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20063,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20063,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20063,], -["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",20063,], -["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",20063,], -["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",20063,], -["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",20063,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20063,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20063,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20070,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20070,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20070,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20070,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20070,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20070,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20070,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20070,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20070,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20070,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20070,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20070,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20070,], +["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",20070,], +["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",20070,], +["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",20070,], +["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",20070,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20073,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20073,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20073,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20073,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20073,], +["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en",0,], +["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en",0,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20070,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20070,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_0_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_0_en",0,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20063,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20063,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20063,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20070,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20070,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20070,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_4_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_4_en",0,], -["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20063,], -["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20063,], -["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",20063,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20070,], +["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20070,], +["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",20070,], ["features.licenses.impl.details_DependenciesDetailsView_Day_0_en","features.licenses.impl.details_DependenciesDetailsView_Night_0_en",0,], -["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20063,], -["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20063,], -["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20063,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20063,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20063,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20063,], -["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",20063,], +["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20070,], +["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20070,], +["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20070,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20070,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20070,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20070,], +["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",20070,], ["libraries.designsystem.theme.components_DialogWithDestructiveButton_Dialog_with_destructive_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithOnlyMessageAndOkButton_Dialog_with_only_message_and_ok_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithThirdButton_Dialog_with_third_button_Dialogs_en","",0,], @@ -293,12 +310,12 @@ export const screenshots = [ ["libraries.designsystem.text_DpScale_1_0f__en","",0,], ["libraries.designsystem.text_DpScale_1_5f__en","",0,], ["libraries.designsystem.theme.components_DropdownMenuItem_Menus_en","",0,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20063,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20063,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20063,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20063,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20063,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20063,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20070,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20070,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20070,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20070,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20070,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20070,], ["libraries.matrix.ui.components_EditableAvatarView_Day_0_en","libraries.matrix.ui.components_EditableAvatarView_Night_0_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_1_en","libraries.matrix.ui.components_EditableAvatarView_Night_1_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_2_en","libraries.matrix.ui.components_EditableAvatarView_Night_2_en",0,], @@ -308,11 +325,14 @@ export const screenshots = [ ["libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiItem_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiItem_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiPicker_Night_0_en",0,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20063,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20063,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20063,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20070,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20070,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20070,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], +["libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en",0,], +["libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en",0,], +["libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en","libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en",0,], ["libraries.designsystem.theme.components_FilledButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_FilledButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_FilledButtonMediumLowPadding_Buttons_en","",0,], @@ -325,15 +345,15 @@ export const screenshots = [ ["libraries.designsystem.theme.components_FloatingActionButton_Floating_Action_Buttons_en","",0,], ["libraries.designsystem.atomic.pages_FlowStepPage_Day_0_en","libraries.designsystem.atomic.pages_FlowStepPage_Night_0_en",0,], ["features.messages.impl.timeline.focus_FocusRequestStateView_Day_0_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_0_en",0,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20063,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20063,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20063,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20070,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20070,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20070,], ["libraries.textcomposer.components_FormattingOption_Day_0_en","libraries.textcomposer.components_FormattingOption_Night_0_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_0_en","features.messages.impl.forward_ForwardMessagesView_Night_0_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_1_en","features.messages.impl.forward_ForwardMessagesView_Night_1_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_2_en","features.messages.impl.forward_ForwardMessagesView_Night_2_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20063,], -["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20063,], +["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20070,], +["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20070,], ["libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Night_0_en",0,], ["libraries.designsystem.components.button_GradientFloatingActionButton_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButton_Night_0_en",0,], ["features.messages.impl.timeline.components.group_GroupHeaderView_Day_0_en","features.messages.impl.timeline.components.group_GroupHeaderView_Night_0_en",0,], @@ -345,8 +365,8 @@ export const screenshots = [ ["libraries.designsystem.atomic.molecules_IconTitlePlaceholdersRowMolecule_Day_0_en","libraries.designsystem.atomic.molecules_IconTitlePlaceholdersRowMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.molecules_IconTitleSubtitleMolecule_Day_0_en","libraries.designsystem.atomic.molecules_IconTitleSubtitleMolecule_Night_0_en",0,], ["libraries.designsystem.theme.components_IconToggleButton_Toggles_en","",0,], -["appicon.element_Icon_en","",0,], ["appicon.enterprise_Icon_en","",0,], +["appicon.element_Icon_en","",0,], ["libraries.designsystem.icons_IconsCompound_Day_0_en","libraries.designsystem.icons_IconsCompound_Night_0_en",0,], ["libraries.designsystem.icons_IconsCompound_Day_1_en","libraries.designsystem.icons_IconsCompound_Night_1_en",0,], ["libraries.designsystem.icons_IconsCompound_Day_2_en","libraries.designsystem.icons_IconsCompound_Night_2_en",0,], @@ -355,63 +375,72 @@ export const screenshots = [ ["libraries.designsystem.icons_IconsCompound_Day_5_en","libraries.designsystem.icons_IconsCompound_Night_5_en",0,], ["libraries.designsystem.icons_IconsOther_Day_0_en","libraries.designsystem.icons_IconsOther_Night_0_en",0,], ["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_0_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_0_en",0,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20063,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20063,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20070,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20070,], +["libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_0_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_10_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_10_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_11_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_11_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_1_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_1_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_2_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_2_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_3_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_3_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20063,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20070,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_5_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_5_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_6_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_6_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_7_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_7_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20063,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20070,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_9_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_9_en",0,], -["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20063,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20063,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20070,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20070,], ["libraries.designsystem.atomic.molecules_InfoListItemMolecule_Day_0_en","libraries.designsystem.atomic.molecules_InfoListItemMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.organisms_InfoListOrganism_Day_0_en","libraries.designsystem.atomic.organisms_InfoListOrganism_Night_0_en",0,], -["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20063,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20070,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20063,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20063,], +["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20070,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20070,], ["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",0,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20066,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20066,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20073,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en",20073,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20070,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20070,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], ["features.leaveroom.api_LeaveRoomView_Day_0_en","features.leaveroom.api_LeaveRoomView_Night_0_en",0,], -["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",20063,], -["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",20063,], -["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",20063,], -["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",20063,], -["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",20063,], -["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",20063,], +["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",20070,], +["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",20070,], +["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",20070,], +["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",20070,], +["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",20070,], +["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",20070,], ["libraries.designsystem.background_LightGradientBackground_Day_0_en","libraries.designsystem.background_LightGradientBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_LinearProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en","",0,], @@ -468,29 +497,29 @@ export const screenshots = [ ["libraries.designsystem.theme.components_ListSupportingTextSmallPadding_List_supporting_text_-_small_padding_List_sections_en","",0,], ["libraries.textcomposer.components_LiveWaveformView_Day_0_en","libraries.textcomposer.components_LiveWaveformView_Night_0_en",0,], ["appnav.room.joined_LoadingRoomNodeView_Day_0_en","appnav.room.joined_LoadingRoomNodeView_Night_0_en",0,], -["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20063,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20063,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20063,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20063,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20070,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20070,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20070,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20070,], ["appnav.loggedin_LoggedInView_Day_0_en","appnav.loggedin_LoggedInView_Night_0_en",0,], -["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20063,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20063,], -["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20063,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20063,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20063,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20063,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20063,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20063,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20063,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20063,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20063,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20063,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20063,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20063,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20063,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20063,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20070,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20070,], +["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20070,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20070,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20070,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20070,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20070,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20070,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20070,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20070,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20070,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20070,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20070,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20070,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20070,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20070,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20063,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20070,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNeutral_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNeutral_Night_0_en",0,], @@ -500,16 +529,34 @@ export const screenshots = [ ["libraries.matrix.ui.components_MatrixUserHeader_Day_1_en","libraries.matrix.ui.components_MatrixUserHeader_Night_1_en",0,], ["libraries.matrix.ui.components_MatrixUserRow_Day_0_en","libraries.matrix.ui.components_MatrixUserRow_Night_0_en",0,], ["libraries.matrix.ui.components_MatrixUserRow_Day_1_en","libraries.matrix.ui.components_MatrixUserRow_Night_1_en",0,], +["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en",0,], +["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en",0,], +["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20073,], +["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20073,], ["libraries.mediaviewer.impl.local.file_MediaFileView_Day_0_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_0_en",0,], -["libraries.mediaviewer.impl.local.file_MediaFileView_Day_1_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_1_en",0,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20073,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20073,], ["libraries.mediaviewer.impl.local.image_MediaImageView_Day_0_en","libraries.mediaviewer.impl.local.image_MediaImageView_Night_0_en",0,], -["libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_0_en",0,], -["libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.impl.local.video_MediaPlayerControllerView_Night_1_en",0,], +["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en",0,], +["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en",0,], +["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_2_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_2_en",0,], ["libraries.mediaviewer.impl.local.video_MediaVideoView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaVideoView_Night_0_en",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_0_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_10_en","",0,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20073,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20073,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_13_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20066,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20070,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_3_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_4_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_5_en","",0,], @@ -521,7 +568,7 @@ export const screenshots = [ ["libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en","libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en",0,], ["libraries.designsystem.theme.components.previews_Menu_Menus_en","",0,], ["features.messages.impl.messagecomposer_MessageComposerViewVoice_Day_0_en","features.messages.impl.messagecomposer_MessageComposerViewVoice_Night_0_en",0,], -["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20063,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20070,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_0_en","features.messages.impl.timeline.components_MessageEventBubble_Night_0_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_10_en","features.messages.impl.timeline.components_MessageEventBubble_Night_10_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_11_en","features.messages.impl.timeline.components_MessageEventBubble_Night_11_en",0,], @@ -538,7 +585,7 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessageEventBubble_Day_7_en","features.messages.impl.timeline.components_MessageEventBubble_Night_7_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_8_en","features.messages.impl.timeline.components_MessageEventBubble_Night_8_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_9_en","features.messages.impl.timeline.components_MessageEventBubble_Night_9_en",0,], -["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20063,], +["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20070,], ["features.messages.impl.timeline.components_MessageStateEventContainer_Day_0_en","features.messages.impl.timeline.components_MessageStateEventContainer_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonAdd_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonAdd_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonExtra_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonExtra_Night_0_en",0,], @@ -546,23 +593,23 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessagesReactionButton_Day_1_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_1_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_2_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_2_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_3_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_3_en",0,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20063,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20063,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20063,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20063,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20063,], -["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20063,], -["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20063,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20063,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20063,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20063,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20063,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20063,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20063,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20063,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20063,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20070,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20070,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20070,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20070,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20070,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20070,], +["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20070,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20070,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20070,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20070,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20070,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20070,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20070,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20070,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20070,], ["features.migration.impl_MigrationView_Day_0_en","features.migration.impl_MigrationView_Night_0_en",0,], -["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20063,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20070,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom_Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom_Sheets_en","",0,], ["appicon.element_MonochromeIcon_en","",0,], @@ -571,29 +618,29 @@ export const screenshots = [ ["libraries.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_Multiple_selection_List_item_-_selection_in_trailing_content_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItemSelected_Multiple_selection_List_item_-_selection_in_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItem_Multiple_selection_List_item_-_no_selection_List_items_en","",0,], -["features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Day_0_en","features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Night_0_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20063,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20063,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20063,], +["features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Day_0_en","features.roomlist.impl.components_NativeSlidingSyncMigrationBanner_Night_0_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20070,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20070,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20070,], ["libraries.oidc.impl.webview_OidcView_Day_0_en","libraries.oidc.impl.webview_OidcView_Night_0_en",0,], ["libraries.oidc.impl.webview_OidcView_Day_1_en","libraries.oidc.impl.webview_OidcView_Night_1_en",0,], ["libraries.designsystem.atomic.pages_OnBoardingPage_Day_0_en","libraries.designsystem.atomic.pages_OnBoardingPage_Night_0_en",0,], -["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",20063,], -["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",20063,], -["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",20063,], -["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",20063,], -["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",20063,], +["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",20070,], +["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",20070,], +["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",20070,], +["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",20070,], +["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",20070,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_OutlinedButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], @@ -607,65 +654,65 @@ export const screenshots = [ ["libraries.designsystem.components_PageTitleWithIconFull_Day_4_en","libraries.designsystem.components_PageTitleWithIconFull_Night_4_en",0,], ["libraries.designsystem.components_PageTitleWithIconFull_Day_5_en","libraries.designsystem.components_PageTitleWithIconFull_Night_5_en",0,], ["libraries.designsystem.components_PageTitleWithIconMinimal_Day_0_en","libraries.designsystem.components_PageTitleWithIconMinimal_Night_0_en",0,], -["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20066,], -["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",20063,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20063,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20063,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20063,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20063,], +["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20070,], +["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",20070,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20070,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20070,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20070,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20070,], ["features.lockscreen.impl.components_PinEntryTextField_Day_0_en","features.lockscreen.impl.components_PinEntryTextField_Night_0_en",0,], ["libraries.designsystem.components_PinIcon_Day_0_en","libraries.designsystem.components_PinIcon_Night_0_en",0,], ["features.lockscreen.impl.unlock.keypad_PinKeypad_Day_0_en","features.lockscreen.impl.unlock.keypad_PinKeypad_Night_0_en",0,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20063,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20063,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20070,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20070,], ["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_0_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_0_en",0,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20063,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20063,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20063,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20063,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20063,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20063,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20070,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20070,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20070,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20070,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20070,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20070,], ["libraries.designsystem.atomic.atoms_PlaceholderAtom_Day_0_en","libraries.designsystem.atomic.atoms_PlaceholderAtom_Night_0_en",0,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20063,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20063,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20063,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20063,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20063,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20070,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20070,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20070,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20070,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20070,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en",0,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Night_0_en",0,], -["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20063,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20063,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20063,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20063,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20063,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20063,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20063,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20063,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20063,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20063,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20063,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20070,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20070,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20070,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20070,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20070,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20070,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20070,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20070,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20070,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20070,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20070,], ["features.poll.api.pollcontent_PollTitleView_Day_0_en","features.poll.api.pollcontent_PollTitleView_Night_0_en",0,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], @@ -682,197 +729,197 @@ export const screenshots = [ ["libraries.designsystem.components.preferences_PreferenceTextLight_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceTextWithEndBadgeDark_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceTextWithEndBadgeLight_Preferences_en","",0,], -["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20063,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20063,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20063,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20063,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20070,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20070,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20070,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20070,], ["features.messages.impl.timeline.components.event_ProgressButton_Day_0_en","features.messages.impl.timeline.components.event_ProgressButton_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20063,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20063,], -["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20063,], -["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20063,], -["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20063,], -["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20063,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20063,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20063,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20063,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20063,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20063,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20063,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20063,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20063,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20063,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20063,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20070,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20070,], +["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20070,], +["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20070,], +["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20070,], +["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20070,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20070,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20070,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20070,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20070,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20070,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20070,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20070,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20070,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20070,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20070,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20063,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20063,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20070,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20070,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], ["features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Night_0_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20063,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20063,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20063,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20063,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20063,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20063,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20063,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20070,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20070,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20070,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20070,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20070,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20070,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20070,], ["libraries.designsystem.atomic.atoms_RedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_RedIndicatorAtom_Night_0_en",0,], ["features.messages.impl.timeline.components_ReplySwipeIndicator_Day_0_en","features.messages.impl.timeline.components_ReplySwipeIndicator_Night_0_en",0,], -["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20063,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20063,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20063,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20063,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20063,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20063,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20063,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20063,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20063,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20063,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20063,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20063,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20070,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20070,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20070,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20070,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20070,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20070,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20070,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20070,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20070,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20070,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20070,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20070,], ["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_0_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_0_en",0,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20063,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20063,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20063,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20063,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20063,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20070,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20070,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20070,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20070,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20070,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_0_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_0_en",0,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",0,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20063,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_13_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",20063,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20063,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20063,], -["features.roomdetails.impl_RoomDetails_0_en","",20063,], -["features.roomdetails.impl_RoomDetails_10_en","",20063,], -["features.roomdetails.impl_RoomDetails_11_en","",20063,], -["features.roomdetails.impl_RoomDetails_12_en","",20063,], -["features.roomdetails.impl_RoomDetails_13_en","",20063,], -["features.roomdetails.impl_RoomDetails_1_en","",20063,], -["features.roomdetails.impl_RoomDetails_2_en","",20063,], -["features.roomdetails.impl_RoomDetails_3_en","",20063,], -["features.roomdetails.impl_RoomDetails_4_en","",20063,], -["features.roomdetails.impl_RoomDetails_5_en","",20063,], -["features.roomdetails.impl_RoomDetails_6_en","",20063,], -["features.roomdetails.impl_RoomDetails_7_en","",20063,], -["features.roomdetails.impl_RoomDetails_8_en","",20063,], -["features.roomdetails.impl_RoomDetails_9_en","",20063,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20063,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20063,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",20063,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",20063,], -["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",20063,], -["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",20063,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20070,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_13_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",20070,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20070,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20070,], +["features.roomdetails.impl_RoomDetails_0_en","",20070,], +["features.roomdetails.impl_RoomDetails_10_en","",20070,], +["features.roomdetails.impl_RoomDetails_11_en","",20070,], +["features.roomdetails.impl_RoomDetails_12_en","",20070,], +["features.roomdetails.impl_RoomDetails_13_en","",20070,], +["features.roomdetails.impl_RoomDetails_1_en","",20070,], +["features.roomdetails.impl_RoomDetails_2_en","",20070,], +["features.roomdetails.impl_RoomDetails_3_en","",20070,], +["features.roomdetails.impl_RoomDetails_4_en","",20070,], +["features.roomdetails.impl_RoomDetails_5_en","",20070,], +["features.roomdetails.impl_RoomDetails_6_en","",20070,], +["features.roomdetails.impl_RoomDetails_7_en","",20070,], +["features.roomdetails.impl_RoomDetails_8_en","",20070,], +["features.roomdetails.impl_RoomDetails_9_en","",20070,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20070,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20070,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",20070,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",20070,], +["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",20070,], +["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",20070,], ["features.roomlist.impl.components_RoomListContentView_Day_2_en","features.roomlist.impl.components_RoomListContentView_Night_2_en",0,], -["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",20063,], -["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",20063,], -["features.roomlist.impl.components_RoomListContentView_Day_5_en","features.roomlist.impl.components_RoomListContentView_Night_5_en",20063,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",20063,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",20063,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",20063,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",20063,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",20063,], +["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",20070,], +["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",20070,], +["features.roomlist.impl.components_RoomListContentView_Day_5_en","features.roomlist.impl.components_RoomListContentView_Night_5_en",20070,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",20070,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",20070,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",20070,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",20070,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",20070,], ["features.roomlist.impl.search_RoomListSearchContent_Day_0_en","features.roomlist.impl.search_RoomListSearchContent_Night_0_en",0,], -["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",20063,], -["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",20063,], -["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",20063,], -["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",20063,], -["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",20063,], -["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",20063,], -["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",20063,], -["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",20063,], -["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",20063,], -["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",20063,], -["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",20063,], +["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",20070,], +["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",20070,], +["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",20070,], +["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",20070,], +["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",20070,], +["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",20070,], +["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",20070,], +["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",20070,], +["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",20070,], +["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",20070,], +["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",20070,], ["features.roomlist.impl_RoomListView_Day_8_en","features.roomlist.impl_RoomListView_Night_8_en",0,], ["features.roomlist.impl_RoomListView_Day_9_en","features.roomlist.impl_RoomListView_Night_9_en",0,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20063,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20063,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20063,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20070,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20070,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20070,], ["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",0,], -["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20063,], -["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20063,], +["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20070,], +["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20070,], ["libraries.designsystem.atomic.molecules_RoomMembersCountMolecule_Day_0_en","libraries.designsystem.atomic.molecules_RoomMembersCountMolecule_Night_0_en",0,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",20063,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",20063,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",20063,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",20063,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",20070,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",20070,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",20070,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",20070,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",20070,], ["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",0,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",20063,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",20063,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",20063,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",20070,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",20070,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",20070,], ["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en",0,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20063,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20063,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20063,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20063,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20063,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20063,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20063,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20063,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20070,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20070,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20070,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20070,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20070,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20070,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20070,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20070,], ["features.roomlist.impl.components_RoomSummaryPlaceholderRow_Day_0_en","features.roomlist.impl.components_RoomSummaryPlaceholderRow_Night_0_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_0_en","features.roomlist.impl.components_RoomSummaryRow_Night_0_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_10_en","features.roomlist.impl.components_RoomSummaryRow_Night_10_en",0,], @@ -895,12 +942,12 @@ export const screenshots = [ ["features.roomlist.impl.components_RoomSummaryRow_Day_26_en","features.roomlist.impl.components_RoomSummaryRow_Night_26_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_27_en","features.roomlist.impl.components_RoomSummaryRow_Night_27_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_28_en","features.roomlist.impl.components_RoomSummaryRow_Night_28_en",0,], -["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",20063,], -["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",20063,], -["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",20063,], -["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",20063,], -["features.roomlist.impl.components_RoomSummaryRow_Day_32_en","features.roomlist.impl.components_RoomSummaryRow_Night_32_en",20063,], -["features.roomlist.impl.components_RoomSummaryRow_Day_33_en","features.roomlist.impl.components_RoomSummaryRow_Night_33_en",20063,], +["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",20070,], +["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",20070,], +["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",20070,], +["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",20070,], +["features.roomlist.impl.components_RoomSummaryRow_Day_32_en","features.roomlist.impl.components_RoomSummaryRow_Night_32_en",20070,], +["features.roomlist.impl.components_RoomSummaryRow_Day_33_en","features.roomlist.impl.components_RoomSummaryRow_Night_33_en",20070,], ["features.roomlist.impl.components_RoomSummaryRow_Day_3_en","features.roomlist.impl.components_RoomSummaryRow_Night_3_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_4_en","features.roomlist.impl.components_RoomSummaryRow_Night_4_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_5_en","features.roomlist.impl.components_RoomSummaryRow_Night_5_en",0,], @@ -908,59 +955,59 @@ export const screenshots = [ ["features.roomlist.impl.components_RoomSummaryRow_Day_7_en","features.roomlist.impl.components_RoomSummaryRow_Night_7_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_8_en","features.roomlist.impl.components_RoomSummaryRow_Night_8_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_9_en","features.roomlist.impl.components_RoomSummaryRow_Night_9_en",0,], -["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20063,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20063,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20063,], -["appicon.enterprise_RoundIcon_en","",0,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20070,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20070,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20070,], ["appicon.element_RoundIcon_en","",0,], +["appicon.enterprise_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20063,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20063,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20063,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20070,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20070,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20070,], ["libraries.designsystem.theme.components_SearchBarActiveNoneQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithContent_Search_views_en","",0,], -["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20063,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20070,], ["libraries.designsystem.theme.components_SearchBarActiveWithQueryNoBackButton_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarInactive_Search_views_en","",0,], -["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",20063,], -["features.createroom.impl.components_SearchSingleUserResultItem_en","",20063,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20063,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20063,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20063,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20063,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20063,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20063,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20063,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20063,], -["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20063,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20063,], +["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",20070,], +["features.createroom.impl.components_SearchSingleUserResultItem_en","",20070,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20070,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20070,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20070,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20070,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20070,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20070,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20070,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20070,], +["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20070,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20070,], ["libraries.matrix.ui.components_SelectedRoom_Day_0_en","libraries.matrix.ui.components_SelectedRoom_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoom_Day_1_en","libraries.matrix.ui.components_SelectedRoom_Night_1_en",0,], ["libraries.matrix.ui.components_SelectedRoom_Day_2_en","libraries.matrix.ui.components_SelectedRoom_Night_2_en",0,], @@ -968,11 +1015,11 @@ export const screenshots = [ ["libraries.matrix.ui.components_SelectedUser_Day_0_en","libraries.matrix.ui.components_SelectedUser_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedUsersRowList_Day_0_en","libraries.matrix.ui.components_SelectedUsersRowList_Night_0_en",0,], ["libraries.textcomposer.components_SendButton_Day_0_en","libraries.textcomposer.components_SendButton_Night_0_en",0,], -["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20063,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20063,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20063,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20063,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20063,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20070,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20070,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20070,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20070,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20070,], ["libraries.matrix.ui.messages.sender_SenderName_Day_0_en","libraries.matrix.ui.messages.sender_SenderName_Night_0_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_1_en","libraries.matrix.ui.messages.sender_SenderName_Night_1_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_2_en","libraries.matrix.ui.messages.sender_SenderName_Night_2_en",0,], @@ -982,27 +1029,27 @@ export const screenshots = [ ["libraries.matrix.ui.messages.sender_SenderName_Day_6_en","libraries.matrix.ui.messages.sender_SenderName_Night_6_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_7_en","libraries.matrix.ui.messages.sender_SenderName_Night_7_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_8_en","libraries.matrix.ui.messages.sender_SenderName_Night_8_en",0,], -["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20063,], -["features.roomlist.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20063,], -["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20063,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20063,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20063,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20063,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20063,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20063,], +["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20070,], +["features.roomlist.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20070,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20070,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20070,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20070,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20070,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20070,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20070,], ["features.share.impl_ShareView_Day_0_en","features.share.impl_ShareView_Night_0_en",0,], ["features.share.impl_ShareView_Day_1_en","features.share.impl_ShareView_Night_1_en",0,], ["features.share.impl_ShareView_Day_2_en","features.share.impl_ShareView_Night_2_en",0,], -["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20063,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20063,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20063,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20063,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20063,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20063,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20063,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20063,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20063,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20063,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20070,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20070,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20070,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20070,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20070,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20070,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20070,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20070,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20070,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20070,], ["libraries.designsystem.components.dialogs_SingleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_SingleSelectionDialog_Night_0_en",0,], ["libraries.designsystem.components.list_SingleSelectionListItemCustomFormattert_Single_selection_List_item_-_custom_formatter_List_items_en","",0,], @@ -1011,7 +1058,7 @@ export const screenshots = [ ["libraries.designsystem.components.list_SingleSelectionListItemUnselectedWithSupportingText_Single_selection_List_item_-_no_selection,_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_SingleSelectionListItem_Single_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_Sliders_Sliders_en","",0,], -["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20063,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20070,], ["libraries.designsystem.theme.components_SnackbarWithActionAndCloseButton_Snackbar_with_action_and_close_button_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLineAndCloseButton_Snackbar_with_action_and_close_button_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLine_Snackbar_with_action_on_new_line_Snackbars_en","",0,], @@ -1021,40 +1068,40 @@ export const screenshots = [ ["libraries.designsystem.modifiers_SquareSizeModifierLargeHeight_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeWidth_en","",0,], ["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",0,], -["features.location.api.internal_StaticMapPlaceholder_Day_1_en","features.location.api.internal_StaticMapPlaceholder_Night_1_en",20063,], +["features.location.api.internal_StaticMapPlaceholder_Day_1_en","features.location.api.internal_StaticMapPlaceholder_Night_1_en",20070,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], -["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20063,], +["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20070,], ["libraries.designsystem.atomic.pages_SunsetPage_Day_0_en","libraries.designsystem.atomic.pages_SunsetPage_Night_0_en",0,], ["libraries.designsystem.components.button_SuperButton_Day_0_en","libraries.designsystem.components.button_SuperButton_Night_0_en",0,], ["libraries.designsystem.theme.components_Surface_en","",0,], ["libraries.designsystem.theme.components_Switch_Toggles_en","",0,], -["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20063,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20070,], ["libraries.designsystem.theme.components_TextButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20063,], -["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20063,], -["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20063,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20063,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20063,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20063,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20063,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20063,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20063,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20063,], +["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20070,], +["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20070,], +["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20070,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20070,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20070,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20070,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20070,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20070,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20070,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20070,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], ["libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en","",0,], @@ -1064,14 +1111,14 @@ export const screenshots = [ ["libraries.designsystem.theme.components_TextFieldsLight_TextFields_en","",0,], ["libraries.textcomposer.components_TextFormatting_Day_0_en","libraries.textcomposer.components_TextFormatting_Night_0_en",0,], ["libraries.designsystem.theme.components_TextLight_Text_en","",0,], -["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20063,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20063,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20063,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20070,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20070,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20070,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_0_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_1_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_2_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20063,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20063,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20070,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20070,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_5_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_6_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_6_en",0,], ["features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en",0,], @@ -1080,17 +1127,17 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20070,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_0_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_1_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20066,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20066,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20066,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20070,], ["features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en","",0,], @@ -1098,17 +1145,17 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20063,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20070,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20070,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20063,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20070,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20070,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20063,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20070,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20070,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en",0,], @@ -1117,40 +1164,40 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20070,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20063,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20070,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20063,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20070,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20063,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20070,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20070,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemInformativeView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemInformativeView_Night_0_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20070,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20063,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20070,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20070,], ["features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20063,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20063,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20070,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20070,], ["features.messages.impl.timeline.components_TimelineItemReactionsView_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsView_Night_0_en",0,], -["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20063,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20070,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_0_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_0_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_1_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_1_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_2_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_2_en",0,], @@ -1159,8 +1206,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_5_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_5_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_6_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_6_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_7_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_7_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20063,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20070,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20070,], ["features.messages.impl.timeline.components_TimelineItemStateEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemStateEventRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStateView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStateView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStickerView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStickerView_Night_0_en",0,], @@ -1173,8 +1220,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_4_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_5_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20063,], -["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20063,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20070,], +["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20070,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_2_en",0,], @@ -1197,85 +1244,87 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemVoiceView_Day_9_en","features.messages.impl.timeline.components.event_TimelineItemVoiceView_Night_9_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en",0,], -["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20063,], +["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20070,], ["features.messages.impl.timeline_TimelineView_Day_10_en","features.messages.impl.timeline_TimelineView_Night_10_en",0,], -["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20063,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20070,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20070,], ["features.messages.impl.timeline_TimelineView_Day_2_en","features.messages.impl.timeline_TimelineView_Night_2_en",0,], ["features.messages.impl.timeline_TimelineView_Day_3_en","features.messages.impl.timeline_TimelineView_Night_3_en",0,], -["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20070,], ["features.messages.impl.timeline_TimelineView_Day_5_en","features.messages.impl.timeline_TimelineView_Night_5_en",0,], -["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20070,], ["features.messages.impl.timeline_TimelineView_Day_7_en","features.messages.impl.timeline_TimelineView_Night_7_en",0,], -["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20063,], +["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20070,], ["features.messages.impl.timeline_TimelineView_Day_9_en","features.messages.impl.timeline_TimelineView_Night_9_en",0,], ["libraries.designsystem.theme.components_TopAppBar_App_Bars_en","",0,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20063,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20063,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20070,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20070,], ["features.messages.impl.typing_TypingNotificationView_Day_0_en","features.messages.impl.typing_TypingNotificationView_Night_0_en",0,], -["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20063,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20063,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20063,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20063,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20063,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20063,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20070,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20070,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20070,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20070,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20070,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20070,], ["features.messages.impl.typing_TypingNotificationView_Day_7_en","features.messages.impl.typing_TypingNotificationView_Night_7_en",0,], ["features.messages.impl.typing_TypingNotificationView_Day_8_en","features.messages.impl.typing_TypingNotificationView_Night_8_en",0,], ["libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en",0,], -["libraries.matrix.ui.components_UnresolvedUserRow_en","",20063,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",20070,], ["libraries.matrix.ui.components_UnsavedAvatar_Day_0_en","libraries.matrix.ui.components_UnsavedAvatar_Night_0_en",0,], ["libraries.designsystem.components.avatar_UserAvatarColors_Day_0_en","libraries.designsystem.components.avatar_UserAvatarColors_Night_0_en",0,], -["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20063,], -["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",20063,], -["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",20063,], -["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",20063,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20070,], +["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",20070,], +["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",20070,], +["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",20070,], ["features.createroom.impl.components_UserListView_Day_3_en","features.createroom.impl.components_UserListView_Night_3_en",0,], ["features.createroom.impl.components_UserListView_Day_4_en","features.createroom.impl.components_UserListView_Night_4_en",0,], ["features.createroom.impl.components_UserListView_Day_5_en","features.createroom.impl.components_UserListView_Night_5_en",0,], ["features.createroom.impl.components_UserListView_Day_6_en","features.createroom.impl.components_UserListView_Night_6_en",0,], -["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",20063,], +["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",20070,], ["features.createroom.impl.components_UserListView_Day_8_en","features.createroom.impl.components_UserListView_Night_8_en",0,], -["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",20063,], +["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",20070,], ["features.preferences.impl.user_UserPreferences_Day_0_en","features.preferences.impl.user_UserPreferences_Night_0_en",0,], ["features.preferences.impl.user_UserPreferences_Day_1_en","features.preferences.impl.user_UserPreferences_Night_1_en",0,], ["features.preferences.impl.user_UserPreferences_Day_2_en","features.preferences.impl.user_UserPreferences_Night_2_en",0,], -["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20063,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20063,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20063,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20063,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20063,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20063,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20063,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20063,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en",20063,], +["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20070,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20070,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20070,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20070,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20070,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20070,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20070,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20070,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en",20070,], ["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en",0,], ["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_12_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_12_en",0,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en",20063,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en",20063,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en",20070,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en",20070,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], +["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en",0,], +["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_2_en","features.viewfolder.impl.file_ViewFileView_Night_2_en",0,], @@ -1289,6 +1338,6 @@ export const screenshots = [ ["libraries.textcomposer.components_VoiceMessageRecording_Day_0_en","libraries.textcomposer.components_VoiceMessageRecording_Night_0_en",0,], ["libraries.textcomposer.components_VoiceMessage_Day_0_en","libraries.textcomposer.components_VoiceMessage_Night_0_en",0,], ["libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en","libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en",0,], -["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",20063,], +["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",20070,], ["libraries.designsystem.ruler_WithRulers_Day_0_en","libraries.designsystem.ruler_WithRulers_Night_0_en",0,], ]; From 85baf612ed5cc019488b3cab8f1a0f72feee8046 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 09:58:02 +0100 Subject: [PATCH 134/203] Format file --- .../impl/gallery/ui/FileItemView.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt index 99ff456296..307ed89bdc 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt @@ -49,8 +49,8 @@ fun FileItemView( ) { Column( modifier = modifier - .fillMaxWidth() - .padding(top = 20.dp, start = 16.dp, end = 16.dp), + .fillMaxWidth() + .padding(top = 20.dp, start = 16.dp, end = 16.dp), ) { FilenameRow( file = file, @@ -78,24 +78,24 @@ private fun FilenameRow( ) { Row( modifier = Modifier - .clip(RoundedCornerShape(12.dp)) - .background( - color = ElementTheme.colors.bgSubtleSecondary, - shape = RoundedCornerShape(12.dp), - ) - .clickable { onClick() } - .fillMaxWidth() - .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), + .clip(RoundedCornerShape(12.dp)) + .background( + color = ElementTheme.colors.bgSubtleSecondary, + shape = RoundedCornerShape(12.dp), + ) + .clickable { onClick() } + .fillMaxWidth() + .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { Icon( modifier = Modifier - .background( - color = ElementTheme.colors.bgActionSecondaryRest, - shape = CircleShape, - ) - .size(32.dp) - .padding(6.dp), + .background( + color = ElementTheme.colors.bgActionSecondaryRest, + shape = CircleShape, + ) + .size(32.dp) + .padding(6.dp), imageVector = CompoundIcons.Attachment(), contentDescription = null, ) From 49b413b92f04befd188e6765f0836c24532bfbc5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 09:53:02 +0100 Subject: [PATCH 135/203] Distinguish Audio and Voice media items. --- .../libraries/mediaviewer/api/MediaInfo.kt | 21 ++ .../impl/gallery/EventItemFactory.kt | 6 +- .../impl/gallery/MediaGalleryPresenter.kt | 1 + .../impl/gallery/MediaGalleryStateProvider.kt | 3 +- .../impl/gallery/MediaGalleryView.kt | 10 + .../mediaviewer/impl/gallery/MediaItem.kt | 15 +- .../impl/gallery/MediaItemsPostProcessor.kt | 1 + .../impl/gallery/ui/AudioItemView.kt | 53 ++--- .../impl/gallery/ui/MediaItemAudioProvider.kt | 7 - .../impl/gallery/ui/MediaItemVoiceProvider.kt | 54 +++++ .../impl/gallery/ui/VoiceItemView.kt | 191 ++++++++++++++++++ .../gallery/DefaultEventItemFactoryTest.kt | 3 +- .../gallery/MediaItemsPostProcessorTest.kt | 10 + 13 files changed, 324 insertions(+), 51 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index 8d72d049ed..7f2e823b1e 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -125,3 +125,24 @@ fun anAudioMediaInfo( dateSentFull = dateSentFull, waveform = waveForm, ) + +fun aVoiceMediaInfo( + filename: String = "a voice file.ogg", + caption: String? = null, + senderName: String? = null, + dateSent: String? = null, + dateSentFull: String? = null, + waveForm: List? = null, +): MediaInfo = MediaInfo( + filename = filename, + caption = caption, + mimeType = MimeTypes.Ogg, + formattedFileSize = "3MB", + fileExtension = "ogg", + senderId = UserId("@alice:server.org"), + senderName = senderName, + senderAvatar = null, + dateSent = dateSent, + dateSentFull = dateSentFull, + waveform = waveForm, +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index ede4c491ea..fbd4f647bc 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor +import kotlinx.collections.immutable.persistentListOf import timber.log.Timber import javax.inject.Inject @@ -104,7 +105,6 @@ class EventItemFactory @Inject constructor( ), mediaSource = type.source, duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), - waveform = null, ) is FileMessageType -> MediaItem.File( id = currentTimelineItem.uniqueId, @@ -182,7 +182,7 @@ class EventItemFactory @Inject constructor( thumbnailSource = type.info?.thumbnailSource, duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), ) - is VoiceMessageType -> MediaItem.Audio( + is VoiceMessageType -> MediaItem.Voice( id = currentTimelineItem.uniqueId, eventId = currentTimelineItem.eventId, mediaInfo = MediaInfo( @@ -200,7 +200,7 @@ class EventItemFactory @Inject constructor( ), mediaSource = type.source, duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), - waveform = type.details?.waveform, + waveform = type.details?.waveform ?: persistentListOf(), ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 242d23c10d..905ba2c770 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -137,6 +137,7 @@ class MediaGalleryPresenter @AssistedInject constructor( is MediaItem.Video -> event.mediaItem.thumbnailSource ?: event.mediaItem.mediaSource is MediaItem.Audio -> null is MediaItem.File -> null + is MediaItem.Voice -> null }, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index d148121dbd..c1d1e1ca72 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVoice import kotlinx.collections.immutable.toImmutableList open class MediaGalleryStateProvider : PreviewParameterProvider { @@ -65,7 +66,7 @@ open class MediaGalleryStateProvider : PreviewParameterProvider VoiceItemView( + item, + onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, + onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, + onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, + ) is MediaItem.DateSeparator -> DateItemView(item) is MediaItem.Image, is MediaItem.Video -> { @@ -332,6 +339,9 @@ private fun MediaGalleryImageGrid( is MediaItem.Audio -> { // Should not happen } + is MediaItem.Voice -> { + // Should not happen + } is MediaItem.File -> { // Should not happen } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt index 0925b4937b..222f620ec5 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt @@ -58,7 +58,15 @@ sealed interface MediaItem { val mediaInfo: MediaInfo, val mediaSource: MediaSource, val duration: String?, - val waveform: ImmutableList?, + ) : Event + + data class Voice( + val id: UniqueId, + val eventId: EventId?, + val mediaInfo: MediaInfo, + val mediaSource: MediaSource, + val duration: String?, + val waveform: ImmutableList, ) : Event data class File( @@ -77,6 +85,7 @@ fun MediaItem.id(): UniqueId { is MediaItem.Video -> id is MediaItem.File -> id is MediaItem.Audio -> id + is MediaItem.Voice -> id } } @@ -86,6 +95,7 @@ fun MediaItem.Event.eventId(): EventId? { is MediaItem.Video -> eventId is MediaItem.File -> eventId is MediaItem.Audio -> eventId + is MediaItem.Voice -> eventId } } @@ -95,6 +105,7 @@ fun MediaItem.Event.mediaInfo(): MediaInfo { is MediaItem.Video -> mediaInfo is MediaItem.File -> mediaInfo is MediaItem.Audio -> mediaInfo + is MediaItem.Voice -> mediaInfo } } @@ -104,6 +115,7 @@ fun MediaItem.Event.mediaSource(): MediaSource { is MediaItem.Video -> mediaSource is MediaItem.File -> mediaSource is MediaItem.Audio -> mediaSource + is MediaItem.Voice -> mediaSource } } @@ -113,5 +125,6 @@ fun MediaItem.Event.thumbnailSource(): MediaSource? { is MediaItem.Video -> thumbnailSource is MediaItem.File -> null is MediaItem.Audio -> null + is MediaItem.Voice -> null } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index 1ed9f0e42b..229f547c3b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -57,6 +57,7 @@ class MediaItemsPostProcessor @Inject constructor() { imageAndVideoItemsSubList.add(0, item) } is MediaItem.Audio, + is MediaItem.Voice, is MediaItem.File -> { fileItemsSublist.add(0, item) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt index a15a12ba58..9a7d498119 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt @@ -8,7 +8,6 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -21,6 +20,8 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.GraphicEq import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -31,7 +32,6 @@ 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.libraries.core.extensions.withBrackets -import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.HorizontalDivider @@ -39,7 +39,6 @@ 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.mediaviewer.impl.gallery.MediaItem -import kotlinx.collections.immutable.toPersistentList @Composable fun AudioItemView( @@ -94,18 +93,12 @@ private fun FilenameRow( Icon( modifier = Modifier .background( - color = ElementTheme.colors.bgCanvasDefault, + color = ElementTheme.colors.bgActionSecondaryRest, shape = CircleShape, ) - .border( - width = 1.dp, - color = ElementTheme.colors.borderInteractiveSecondary, - shape = CircleShape, - ) - .size(36.dp) + .size(32.dp) .padding(6.dp), - imageVector = CompoundIcons.PlaySolid(), - tint = ElementTheme.colors.iconSecondary, + imageVector = Icons.Outlined.GraphicEq, contentDescription = null, ) audio.duration?.let { @@ -119,34 +112,20 @@ private fun FilenameRow( ) } Spacer(modifier = Modifier.width(8.dp)) - val waveform = audio.waveform - if (waveform == null) { + Text( + text = audio.mediaInfo.filename, + modifier = Modifier.weight(1f), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + val formattedSize = audio.mediaInfo.formattedFileSize + if (formattedSize.isNotEmpty()) { Text( - text = audio.mediaInfo.filename, - modifier = Modifier.weight(1f), + text = formattedSize.withBrackets(), style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - val formattedSize = audio.mediaInfo.formattedFileSize - if (formattedSize.isNotEmpty()) { - Text( - text = formattedSize.withBrackets(), - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textPrimary, - ) - } - } else { - WaveformPlaybackView( - modifier = Modifier - .weight(1f) - .height(34.dp), - playbackProgress = 0f, - showCursor = false, - waveform = waveform.toPersistentList(), - onSeek = {}, - seekEnabled = false, ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt index 4bcdf60d3a..1194449aef 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt @@ -9,12 +9,10 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.core.preview.loremIpsum -import io.element.android.libraries.designsystem.components.media.aWaveForm import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem -import kotlinx.collections.immutable.toImmutableList class MediaItemAudioProvider : PreviewParameterProvider { override val values: Sequence @@ -27,9 +25,6 @@ class MediaItemAudioProvider : PreviewParameterProvider { aMediaItemAudio( caption = loremIpsum, ), - aMediaItemAudio( - waveform = aWaveForm(), - ), ) } @@ -38,7 +33,6 @@ fun aMediaItemAudio( filename: String = "filename", caption: String? = null, duration: String? = "1:23", - waveform: List? = null, ): MediaItem.Audio { return MediaItem.Audio( id = id, @@ -49,6 +43,5 @@ fun aMediaItemAudio( ), mediaSource = MediaSource(""), duration = duration, - waveform = waveform?.toImmutableList(), ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt new file mode 100644 index 0000000000..8056c094e8 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.core.preview.loremIpsum +import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.aVoiceMediaInfo +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import kotlinx.collections.immutable.toImmutableList + +class MediaItemVoiceProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aMediaItemVoice(), + aMediaItemVoice( + filename = "A long filename that should be truncated.ogg", + caption = "A caption", + ), + aMediaItemVoice( + caption = loremIpsum, + ), + aMediaItemVoice( + waveform = emptyList(), + ), + ) +} + +fun aMediaItemVoice( + id: UniqueId = UniqueId("fileId"), + filename: String = "filename.ogg", + caption: String? = null, + duration: String? = "1:23", + waveform: List = aWaveForm(), +): MediaItem.Voice { + return MediaItem.Voice( + id = id, + eventId = null, + mediaInfo = aVoiceMediaInfo( + filename = filename, + caption = caption, + ), + mediaSource = MediaSource(""), + duration = duration, + waveform = waveform.toImmutableList(), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt new file mode 100644 index 0000000000..137a94e5d2 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +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.libraries.designsystem.components.media.WaveformPlaybackView +import io.element.android.libraries.designsystem.preview.ElementPreview +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.IconButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import kotlinx.collections.immutable.toPersistentList + +@Composable +fun VoiceItemView( + voice: MediaItem.Voice, + onShareClick: () -> Unit, + onDownloadClick: () -> Unit, + onInfoClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(top = 20.dp, start = 16.dp, end = 16.dp), + ) { + VoiceInfoRow( + voice = voice, + ) + val caption = voice.mediaInfo.caption + if (caption != null) { + Spacer(modifier = Modifier.height(16.dp)) + Caption(caption) + } + Spacer(modifier = Modifier.height(16.dp)) + ActionIconsRow( + onShareClick = onShareClick, + onDownloadClick = onDownloadClick, + onInfoClick = onInfoClick, + ) + HorizontalDivider() + } +} + +@Composable +private fun VoiceInfoRow( + voice: MediaItem.Voice, +) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background( + color = ElementTheme.colors.bgSubtleSecondary, + shape = RoundedCornerShape(12.dp), + ) + .fillMaxWidth() + .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .background( + color = ElementTheme.colors.bgCanvasDefault, + shape = CircleShape, + ) + .border( + width = 1.dp, + color = ElementTheme.colors.borderInteractiveSecondary, + shape = CircleShape, + ) + .size(36.dp) + .padding(6.dp), + imageVector = CompoundIcons.PlaySolid(), + tint = ElementTheme.colors.iconSecondary, + contentDescription = null, + ) + voice.duration?.let { + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = voice.duration, + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Spacer(modifier = Modifier.width(8.dp)) + WaveformPlaybackView( + modifier = Modifier + .weight(1f) + .height(34.dp), + playbackProgress = 0f, + showCursor = false, + waveform = voice.waveform.toPersistentList(), + onSeek = { + + }, + seekEnabled = true, + ) + } +} + +@Composable +private fun Caption(caption: String) { + Text( + modifier = Modifier.fillMaxWidth(), + text = caption, + maxLines = 5, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) +} + +@Composable +private fun ActionIconsRow( + onShareClick: () -> Unit, + onDownloadClick: () -> Unit, + onInfoClick: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + IconButton( + onClick = onShareClick, + ) { + Icon( + imageVector = CompoundIcons.ShareAndroid(), + contentDescription = null, + ) + } + IconButton( + onClick = onDownloadClick, + ) { + Icon( + imageVector = CompoundIcons.Download(), + contentDescription = null, + ) + } + IconButton( + onClick = onInfoClick, + ) { + Icon( + imageVector = CompoundIcons.Info(), + contentDescription = null, + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun VoiceItemViewPreview( + @PreviewParameter(MediaItemVoiceProvider::class) voice: MediaItem.Voice, +) = ElementPreview { + VoiceItemView( + voice = voice, + onShareClick = {}, + onDownloadClick = {}, + onInfoClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt index 3478f8f867..d074581f7d 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -263,7 +263,6 @@ class DefaultEventItemFactoryTest { ), mediaSource = MediaSource(""), duration = "7:36", - waveform = null, ) ) } @@ -348,7 +347,7 @@ class DefaultEventItemFactoryTest { ) ) assertThat(result).isEqualTo( - MediaItem.Audio( + MediaItem.Voice( id = A_UNIQUE_ID, eventId = AN_EVENT_ID, mediaInfo = MediaInfo( diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt index cf32409248..9621413da6 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo +import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVoice import kotlinx.collections.immutable.toImmutableList import org.junit.Test @@ -27,6 +28,9 @@ class MediaItemsPostProcessorTest { private val audio1 = aMediaItemAudio(id = UniqueId("1")) private val audio2 = aMediaItemAudio(id = UniqueId("2")) private val audio3 = aMediaItemAudio(id = UniqueId("3")) + private val voice1 = aMediaItemVoice(id = UniqueId("1")) + private val voice2 = aMediaItemVoice(id = UniqueId("2")) + private val voice3 = aMediaItemVoice(id = UniqueId("3")) private val image1 = aMediaItemImage(id = UniqueId("1")) private val image2 = aMediaItemImage(id = UniqueId("2")) private val image3 = aMediaItemImage(id = UniqueId("3")) @@ -163,6 +167,9 @@ class MediaItemsPostProcessorTest { fun `process will handle complex case`() { test( mediaItems = listOf( + voice3, + voice2, + voice1, audio3, audio2, audio1, @@ -192,6 +199,9 @@ class MediaItemsPostProcessorTest { audio1, audio2, audio3, + voice1, + voice2, + voice3, date3, file3, loading1, From abc8a14c61918a2448c45cd88dd7f21e94ad861e Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 16 Dec 2024 10:58:13 +0100 Subject: [PATCH 136/203] feat(crypto): Support for new UtdCause for historical messages --- .../components/event/TimelineItemEncryptedView.kt | 5 ++++- .../matrix/api/timeline/item/event/UtdCause.kt | 13 ++++++++++--- .../libraries/matrix/impl/analytics/UtdTracker.kt | 4 +++- .../item/event/TimelineEventContentMapper.kt | 3 ++- .../ui-strings/src/main/res/values/localazy.xml | 1 + 5 files changed, 20 insertions(+), 6 deletions(-) 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 b91b4ccc17..9ad875377c 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 @@ -40,9 +40,12 @@ fun TimelineItemEncryptedView( UtdCause.UnknownDevice -> { CommonStrings.common_unable_to_decrypt_insecure_device to CompoundDrawables.ic_compound_block } - UtdCause.HistoricalMessage -> { + UtdCause.HistoricalMessageAndBackupIsDisabled -> { CommonStrings.timeline_decryption_failure_historical_event_no_key_backup to CompoundDrawables.ic_compound_block } + UtdCause.HistoricalMessageAndDeviceIsUnverified -> { + CommonStrings.timeline_decryption_failure_historical_event_unverified_device to CompoundDrawables.ic_compound_block + } UtdCause.WithheldUnverifiedOrInsecureDevice -> { CommonStrings.timeline_decryption_failure_withheld_unverified to CompoundDrawables.ic_compound_block } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt index 51427c6cba..ab39f49bef 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt @@ -15,10 +15,17 @@ enum class UtdCause { UnknownDevice, /** - * Expected utd because this is a device-historical message and - * key storage is not setup or not configured correctly. + * We are missing the keys for this event, but it is a "device-historical" message and + * there is no key storage backup on the server, presumably because the user has turned it off. */ - HistoricalMessage, + HistoricalMessageAndBackupIsDisabled, + + /** + * We are missing the keys for this event, but it is a "device-historical" + * message, and even though a key storage backup does exist, we can't use + * it because our device is unverified. + */ + HistoricalMessageAndDeviceIsUnverified, /** * The key was withheld on purpose because your device is insecure and/or the diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt index a8577bbe40..aed1d7a055 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt @@ -27,7 +27,9 @@ class UtdTracker( UtdCause.UNKNOWN_DEVICE -> { Error.Name.ExpectedSentByInsecureDevice } - UtdCause.HISTORICAL_MESSAGE -> Error.Name.HistoricalMessage + UtdCause.HISTORICAL_MESSAGE_AND_BACKUP_IS_DISABLED, + UtdCause.HISTORICAL_MESSAGE_AND_DEVICE_IS_UNVERIFIED, + -> Error.Name.HistoricalMessage UtdCause.WITHHELD_FOR_UNVERIFIED_OR_INSECURE_DEVICE -> Error.Name.RoomKeysWithheldForUnverifiedDevice UtdCause.WITHHELD_BY_SENDER -> Error.Name.OlmKeysNotSentError } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 6e079ffe3f..3d13792eeb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -145,7 +145,8 @@ private fun RustUtdCause.map(): UtdCause { RustUtdCause.VERIFICATION_VIOLATION -> UtdCause.VerificationViolation RustUtdCause.UNSIGNED_DEVICE -> UtdCause.UnsignedDevice RustUtdCause.UNKNOWN_DEVICE -> UtdCause.UnknownDevice - RustUtdCause.HISTORICAL_MESSAGE -> UtdCause.HistoricalMessage + RustUtdCause.HISTORICAL_MESSAGE_AND_BACKUP_IS_DISABLED -> UtdCause.HistoricalMessageAndBackupIsDisabled + RustUtdCause.HISTORICAL_MESSAGE_AND_DEVICE_IS_UNVERIFIED -> UtdCause.HistoricalMessageAndDeviceIsUnverified RustUtdCause.WITHHELD_FOR_UNVERIFIED_OR_INSECURE_DEVICE -> UtdCause.WithheldUnverifiedOrInsecureDevice RustUtdCause.WITHHELD_BY_SENDER -> UtdCause.WithheldBySender } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index b571a23230..a9771364b7 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -343,6 +343,7 @@ Reason: %1$s." "en" "en" "Historical messages are not available on this device" + "You need to verify this device for access to historical messages" "You don\'t have access to this message" "Unable to decrypt message" "This message was blocked either because you did not verify your device or because the sender needs to verify your identity." From 75e8ec6e7ce5197585f1ebc9b47ced61f9d79980 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 10:12:16 +0100 Subject: [PATCH 137/203] Add inline voice player to the files gallery --- libraries/mediaviewer/impl/build.gradle.kts | 1 + .../impl/gallery/MediaGalleryNode.kt | 22 +- .../impl/gallery/MediaGalleryStateProvider.kt | 1 - .../impl/gallery/MediaGalleryView.kt | 37 +++- .../di/FakeTimelineItemPresenterFactories.kt | 25 +++ .../di/LocalMediaItemPresenterFactories.kt | 17 ++ .../gallery/di/MediaItemEventContentKey.kt | 20 ++ .../gallery/di/MediaItemPresenterFactories.kt | 90 ++++++++ .../gallery/di/MediaItemPresenterFactory.kt | 24 +++ .../impl/gallery/ui/VoiceItemView.kt | 201 +++++++++++++++--- .../gallery/voice/VoiceMessagePresenter.kt | 58 +++++ .../tests/konsist/KonsistPreviewTest.kt | 1 + 12 files changed, 449 insertions(+), 48 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index 4fa63820d3..395b57df38 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.voiceplayer.api) implementation(projects.services.toolbox.api) api(projects.libraries.mediaviewer.api) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt index ccea1a130e..0c4e3cfebc 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -18,12 +19,15 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.mediaviewer.impl.gallery.di.LocalMediaItemPresenterFactories +import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresenterFactories @ContributesNode(RoomScope::class) class MediaGalleryNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, presenterFactory: MediaGalleryPresenter.Factory, + private val mediaItemPresenterFactories: MediaItemPresenterFactories, ) : Node(buildContext, plugins = plugins), MediaGalleryNavigator { private val presenter = presenterFactory.create( @@ -56,12 +60,16 @@ class MediaGalleryNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - val state = presenter.present() - MediaGalleryView( - state = state, - onBackClick = ::onBackClick, - onItemClick = ::onItemClick, - modifier = modifier, - ) + CompositionLocalProvider( + LocalMediaItemPresenterFactories provides mediaItemPresenterFactories, + ) { + val state = presenter.present() + MediaGalleryView( + state = state, + onBackClick = ::onBackClick, + onItemClick = ::onItemClick, + modifier = modifier, + ) + } } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index c1d1e1ca72..87e7599991 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -64,7 +64,6 @@ open class MediaGalleryStateProvider : PreviewParameterProvider Unit, onItemClick: (MediaItem.Event) -> Unit, ) { + val presenterFactories = LocalMediaItemPresenterFactories.current LazyColumn( modifier = Modifier.fillMaxSize(), ) { @@ -275,12 +282,16 @@ private fun MediaGalleryFilesList( onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, ) - is MediaItem.Voice -> VoiceItemView( - item, - onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, - onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, - onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, - ) + is MediaItem.Voice -> { + val presenter: Presenter = presenterFactories.rememberPresenter(item) + VoiceItemView( + presenter.present(), + item, + onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, + onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, + onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, + ) + } is MediaItem.DateSeparator -> DateItemView(item) is MediaItem.Image, is MediaItem.Video -> { @@ -462,9 +473,13 @@ private fun LoadingContent( internal fun MediaGalleryViewPreview( @PreviewParameter(MediaGalleryStateProvider::class) state: MediaGalleryState ) = ElementPreview { - MediaGalleryView( - state = state, - onBackClick = {}, - onItemClick = {}, - ) + CompositionLocalProvider( + LocalMediaItemPresenterFactories provides aFakeMediaItemPresenterFactories(), + ) { + MediaGalleryView( + state = state, + onBackClick = {}, + onItemClick = {}, + ) + } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt new file mode 100644 index 0000000000..bf453c33e0 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.di + +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import io.element.android.libraries.voiceplayer.api.aVoiceMessageState + +/** + * A fake [MediaItemPresenterFactories] for screenshot tests. + */ +fun aFakeMediaItemPresenterFactories() = MediaItemPresenterFactories( + mapOf( + Pair( + MediaItem.Voice::class.java, + MediaItemPresenterFactory { Presenter { aVoiceMessageState() } }, + ), + ) +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt new file mode 100644 index 0000000000..8138d4c7f7 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.di + +import androidx.compose.runtime.staticCompositionLocalOf + +/** + * Provides a [MediaItemPresenterFactories] to the composition. + */ +val LocalMediaItemPresenterFactories = staticCompositionLocalOf { + MediaItemPresenterFactories(emptyMap()) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt new file mode 100644 index 0000000000..7db70901d3 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.di + +import dagger.MapKey +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import kotlin.reflect.KClass + +/** + * Annotation to add a factory of type [MediaItemPresenterFactory] to a + * Dagger map multi binding keyed with a subclass of [MediaItem.Event]. + */ +@Retention(AnnotationRetention.RUNTIME) +@MapKey +annotation class MediaItemEventContentKey(val value: KClass) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt new file mode 100644 index 0000000000..28b79194c7 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.di + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.multibindings.Multibinds +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import javax.inject.Inject + +/** + * Dagger module that declares the [MediaItemPresenterFactory] map multi binding. + * + * Its sole purpose is to support the case of an empty map multibinding. + */ +@Module +@ContributesTo(RoomScope::class) +interface MediaItemPresenterFactoriesModule { + @Multibinds + fun multiBindMediaItemPresenterFactories(): @JvmSuppressWildcards Map, MediaItemPresenterFactory<*, *>> +} + +/** + * Room level caching layer for the [MediaItemPresenterFactory] instances. + * + * It will cache the presenter instances in the room scope, so that they can be + * reused across recompositions of the gallery items that happen whenever an item + * goes out of the [LazyColumn] viewport. + */ +@SingleIn(RoomScope::class) +class MediaItemPresenterFactories @Inject constructor( + private val factories: @JvmSuppressWildcards Map, MediaItemPresenterFactory<*, *>>, +) { + private val presenters: MutableMap> = mutableMapOf() + + /** + * Creates and caches a presenter for the given content. + * + * Will throw if the presenter is not found in the [MediaItemPresenterFactory] map multi binding. + * + * @param C The [MediaItem.Event] subtype handled by this TimelineItem presenter. + * @param S The state type produced by this timeline item presenter. + * @param content The [MediaItem.Event] instance to create a presenter for. + * @param contentClass The class of [content]. + * @return An instance of a TimelineItem presenter that will be cached in the room scope. + */ + @Composable + fun rememberPresenter( + content: C, + contentClass: Class, + ): Presenter = remember(content) { + presenters[content]?.let { + @Suppress("UNCHECKED_CAST") + it as Presenter + } ?: factories.getValue(contentClass).let { + @Suppress("UNCHECKED_CAST") + (it as MediaItemPresenterFactory).create(content).apply { + presenters[content] = this + } + } + } +} + +/** + * Creates and caches a presenter for the given content. + * + * Will throw if the presenter is not found in the [MediaItemPresenterFactory] map multi binding. + * + * @param C The [MediaItem.Event] subtype handled by this TimelineItem presenter. + * @param S The state type produced by this timeline item presenter. + * @param content The [MediaItem.Event] instance to create a presenter for. + * @return An instance of a TimelineItem presenter that will be cached in the room scope. + */ +@Composable +inline fun MediaItemPresenterFactories.rememberPresenter( + content: C +): Presenter = rememberPresenter( + content = content, + contentClass = C::class.java +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt new file mode 100644 index 0000000000..fd621adbfb --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.di + +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem + +/** + * A factory for a [Presenter] associated with a timeline item. + * + * Implementations should be annotated with [AssistedFactory] to be created by Dagger. + * + * @param C The timeline item's [MediaItem.Event] subtype. + * @param S The [Presenter]'s state class. + * @return A [Presenter] that produces a state of type [S] for the given content of type [C]. + */ +fun interface MediaItemPresenterFactory { + fun create(content: C): Presenter +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt index 137a94e5d2..472ea6555f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -20,10 +20,18 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -32,15 +40,23 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator 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.IconButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider +import io.element.android.libraries.voiceplayer.api.aVoiceMessageState import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.delay @Composable fun VoiceItemView( + state: VoiceMessageState, voice: MediaItem.Voice, onShareClick: () -> Unit, onDownloadClick: () -> Unit, @@ -53,6 +69,7 @@ fun VoiceItemView( .padding(top = 20.dp, start = 16.dp, end = 16.dp), ) { VoiceInfoRow( + state = state, voice = voice, ) val caption = voice.mediaInfo.caption @@ -72,8 +89,13 @@ fun VoiceItemView( @Composable private fun VoiceInfoRow( + state: VoiceMessageState, voice: MediaItem.Voice, ) { + fun playPause() { + state.eventSink(VoiceMessageEvents.PlayPause) + } + Row( modifier = Modifier .clip(RoundedCornerShape(12.dp)) @@ -85,49 +107,155 @@ private fun VoiceInfoRow( .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { - Icon( - modifier = Modifier - .background( - color = ElementTheme.colors.bgCanvasDefault, - shape = CircleShape, - ) - .border( - width = 1.dp, - color = ElementTheme.colors.borderInteractiveSecondary, - shape = CircleShape, - ) - .size(36.dp) - .padding(6.dp), - imageVector = CompoundIcons.PlaySolid(), - tint = ElementTheme.colors.iconSecondary, - contentDescription = null, - ) - voice.duration?.let { - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = voice.duration, - style = ElementTheme.typography.fontBodyMdMedium, - color = ElementTheme.colors.textSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) + when (state.button) { + VoiceMessageState.Button.Play -> PlayButton(onClick = ::playPause) + VoiceMessageState.Button.Pause -> PauseButton(onClick = ::playPause) + VoiceMessageState.Button.Downloading -> ProgressButton() + VoiceMessageState.Button.Retry -> RetryButton(onClick = ::playPause) + VoiceMessageState.Button.Disabled -> PlayButton(onClick = {}, enabled = false) } + Spacer(Modifier.width(8.dp)) + Text( + text = state.time, + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodyMdMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) Spacer(modifier = Modifier.width(8.dp)) WaveformPlaybackView( modifier = Modifier .weight(1f) .height(34.dp), - playbackProgress = 0f, - showCursor = false, + showCursor = state.showCursor, + playbackProgress = state.progress, waveform = voice.waveform.toPersistentList(), onSeek = { - + state.eventSink(VoiceMessageEvents.Seek(it)) }, seekEnabled = true, ) } } +/** + * Progress button is shown when the voice message is being downloaded. + * + * The progress indicator is optimistic and displays a pause button (which + * indicates the audio is playing) for 2 seconds before revealing the + * actual progress indicator. + */ +@Composable +private fun ProgressButton( + displayImmediately: Boolean = false, +) { + var canDisplay by remember { mutableStateOf(displayImmediately) } + LaunchedEffect(Unit) { + delay(2000L) + canDisplay = true + } + CustomIconButton( + onClick = {}, + enabled = false, + ) { + if (canDisplay) { + CircularProgressIndicator( + modifier = Modifier + .padding(2.dp) + .size(16.dp), + color = ElementTheme.colors.iconSecondary, + strokeWidth = 2.dp, + ) + } else { + ControlIcon( + imageVector = CompoundIcons.PauseSolid(), + contentDescription = stringResource(id = CommonStrings.a11y_pause), + ) + } + } +} + +@Composable +private fun PlayButton( + onClick: () -> Unit, + enabled: Boolean = true, +) { + CustomIconButton( + onClick = onClick, + enabled = enabled, + ) { + ControlIcon( + imageVector = CompoundIcons.PlaySolid(), + contentDescription = stringResource(id = CommonStrings.a11y_play), + ) + } +} + +@Composable +private fun PauseButton( + onClick: () -> Unit, +) { + CustomIconButton( + onClick = onClick, + ) { + ControlIcon( + imageVector = CompoundIcons.PauseSolid(), + contentDescription = stringResource(id = CommonStrings.a11y_pause), + ) + } +} + +@Composable +private fun RetryButton( + onClick: () -> Unit, +) { + CustomIconButton( + onClick = onClick, + ) { + ControlIcon( + imageVector = CompoundIcons.Restart(), + contentDescription = stringResource(id = CommonStrings.action_retry), + ) + } +} + +@Composable +private fun ControlIcon( + imageVector: ImageVector, + contentDescription: String?, +) { + Icon( + modifier = Modifier.padding(vertical = 10.dp), + imageVector = imageVector, + contentDescription = contentDescription, + ) +} + +@Composable +private fun CustomIconButton( + onClick: () -> Unit, + enabled: Boolean = true, + content: @Composable () -> Unit, +) { + IconButton( + onClick = onClick, + modifier = Modifier + .background(color = ElementTheme.colors.bgCanvasDefault, shape = CircleShape) + .border( + width = 1.dp, + color = ElementTheme.colors.borderInteractiveSecondary, + shape = CircleShape, + ) + .size(36.dp), + enabled = enabled, + colors = IconButtonDefaults.iconButtonColors( + contentColor = ElementTheme.colors.iconSecondary, + disabledContentColor = ElementTheme.colors.iconDisabled, + ), + content = content, + ) +} + @Composable private fun Caption(caption: String) { Text( @@ -183,9 +311,24 @@ internal fun VoiceItemViewPreview( @PreviewParameter(MediaItemVoiceProvider::class) voice: MediaItem.Voice, ) = ElementPreview { VoiceItemView( + state = aVoiceMessageState(), voice = voice, onShareClick = {}, onDownloadClick = {}, onInfoClick = {}, ) } + +@PreviewsDayNight +@Composable +internal fun VoiceItemViewPlayPreview( + @PreviewParameter(VoiceMessageStateProvider::class) state: VoiceMessageState, +) = ElementPreview { + VoiceItemView( + state = state, + voice = aMediaItemVoice(), + onShareClick = {}, + onDownloadClick = {}, + onInfoClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt new file mode 100644 index 0000000000..9f5a6593ed --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.voice + +import androidx.compose.runtime.Composable +import com.squareup.anvil.annotations.ContributesTo +import dagger.Binds +import dagger.Module +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.multibindings.IntoMap +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem +import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemEventContentKey +import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresenterFactory +import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory +import io.element.android.libraries.voiceplayer.api.VoiceMessageState +import kotlin.time.Duration + +@Module +@ContributesTo(RoomScope::class) +interface VoiceMessagePresenterModule { + @Binds + @IntoMap + @MediaItemEventContentKey(MediaItem.Voice::class) + fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): MediaItemPresenterFactory<*, *> +} + +class VoiceMessagePresenter @AssistedInject constructor( + voiceMessagePresenterFactory: VoiceMessagePresenterFactory, + @Assisted private val item: MediaItem.Voice, +) : Presenter { + @AssistedFactory + fun interface Factory : MediaItemPresenterFactory { + override fun create(content: MediaItem.Voice): VoiceMessagePresenter + } + + private val presenter = voiceMessagePresenterFactory.createVoiceMessagePresenter( + eventId = item.eventId, + mediaSource = item.mediaSource, + mimeType = item.mediaInfo.mimeType, + filename = item.mediaInfo.filename, + // TODO Get the duration for the fallback? + duration = Duration.ZERO, + ) + + @Composable + override fun present(): VoiceMessageState { + return presenter.present() + } +} diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index 8d26082157..1235223337 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -128,6 +128,7 @@ class KonsistPreviewTest { "TimelineVideoWithCaptionRowPreview", "TimelineViewMessageShieldPreview", "UserAvatarColorsPreview", + "VoiceItemViewPlayPreview", ) .assertTrue( additionalMessage = "Functions for Preview should be named like this: Preview. " + From 33c02c1f2c5fe9bc0934fb74b0bc93227285dd9d Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 16 Dec 2024 11:07:38 +0100 Subject: [PATCH 138/203] knock requests : sync localazy --- .../impl/src/main/res/values/localazy.xml | 11 +++++++++++ libraries/ui-strings/src/main/res/values/localazy.xml | 1 + 2 files changed, 12 insertions(+) diff --git a/features/knockrequests/impl/src/main/res/values/localazy.xml b/features/knockrequests/impl/src/main/res/values/localazy.xml index d2e9dc5d14..454bb8e193 100644 --- a/features/knockrequests/impl/src/main/res/values/localazy.xml +++ b/features/knockrequests/impl/src/main/res/values/localazy.xml @@ -4,15 +4,26 @@ "Are you sure you want to accept all requests to join?" "Accept all requests" "Accept all" + "We couldn’t accept all requests. Would you like to try again?" + "Failed to accept all requests" + "Accepting all requests to join" + "We couldn’t accept this request. Would you like to try again?" + "Failed to accept request" + "Accepting request to join" "Yes, decline and ban" "Are you sure you want to decline and ban %1$s? This user won’t be able to request access to join this room again." "Decline and ban from accessing" + "Declining and banning access" "Yes, decline" "Are you sure you want to decline %1$s request to join this room?" "Decline access" "Decline and ban" + "We couldn’t decline this request. Would you like to try again?" + "Failed to decline request" + "Declining request to join" "When somebody will ask to join the room, you’ll be able to see their request here." "No pending request to join" + "Loading requests to join…" "Requests to join" "%1$s +%2$d other want to join this room" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index b571a23230..ab38b9b148 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -127,6 +127,7 @@ "View in timeline" "View source" "Yes" + "Yes, try again" "About" "Acceptable use policy" "Adding caption" From 44f98a52815141adf372c8d5b5e2b004b16c9e30 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 16 Dec 2024 10:15:23 +0000 Subject: [PATCH 139/203] Update screenshots --- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png | 3 --- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png | 3 --- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png | 3 +++ ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png | 3 +++ ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png | 3 +++ ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png | 3 +++ ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png | 3 +++ ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png | 3 +++ ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png | 3 +++ ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png | 3 +++ ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png | 3 +++ ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png | 3 +++ ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png | 3 +++ ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png | 3 +++ ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 4 ++-- 28 files changed, 70 insertions(+), 22 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png index dc645dea5e..6c0958790b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d07440581dc64c48c047b8895520c7f46213e4806053b6a5fa4b63c15386c8c -size 12410 +oid sha256:9712ec0c9240afdd093e76cbc04143c49807819c31697d957b22e664b66b2dba +size 11333 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png index 6ec428fbfb..b51d1b3801 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9beef21701df1566b27d7b71808de32a87ae439b5213f7e081d821e60add5b36 -size 16302 +oid sha256:d5ec1a0bd7a4d1bd06f3b01c960334503ddac208b6cdedf521dea7c0cd83efd2 +size 15156 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png index aa05357dd7..fd7f6f30fb 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0adeab82d5197daf0eb408f66ee605e0ae3862ac08de0c6a5093a957f724718c -size 39939 +oid sha256:80c113c5a4e71e00e41abc21f22340f8374488047101e4e79472a7549f5bb50f +size 38712 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png deleted file mode 100644 index a45dd44c3c..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:835cdc9b99cdd8eedfe2f59032344d07681cd8bbab39a0f15e340abd80c035ff -size 10979 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png index 0fba373d86..311b4ae127 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1813f6e96713fc8c11eca36475ac51b3162f88c71bbe4e9fe7cb0c9a58314ab4 -size 11719 +oid sha256:71e8ea9b852fc15170b61c283248034404761487091ed6290015f4e9c9ca2d3e +size 10919 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png index 87028a2acd..b97ea7edce 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1df8cd0cbef387b05959a423c20cf92605e1c671404aef4ccbb79048d4fc4a6f -size 15518 +oid sha256:527242ca1b585e76247210f2a000e6511bfbe90b0c8f8eaf850f8318b6067d5a +size 14627 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png index 12949183c1..77f9746562 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de63930be672d83e9bc508c6a84911f6a309c2dbac7b608c3bfef5e8a37fc49f -size 38349 +oid sha256:a0c1c64b768ec3491200e38b93ff558873d130a3a5c71b8bd5c3f4a49e3d9137 +size 37273 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png deleted file mode 100644 index d80a5bcf08..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:836b153cee837cb5c0ba375b587f6bfa6e34eb9f88e4198483f956ebff91048a -size 10234 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png new file mode 100644 index 0000000000..1bc8453928 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d5d02d56e050717a767bcf12d9da7c6ccf1207bedba0a7ec8b0451a668739d9 +size 11032 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png new file mode 100644 index 0000000000..7129e15299 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60f281d970321442e39b350f4f697c7ecfc9bc32000cd19676ea7ed6468ee63a +size 11411 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png new file mode 100644 index 0000000000..070290ee09 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25386b28846ae826701c9e53530cdd1e5fda4d0899673394bf6d81dac1ca751f +size 11057 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png new file mode 100644 index 0000000000..6678d08a23 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:740923d3e740dc98a5d6890f8f8dfb5810606d99e81a4e6597078566194f076d +size 11290 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png new file mode 100644 index 0000000000..f673979846 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e18741850e92314b40da89c9e1cafd1795397960151f7bb4e221ae3866d25f9d +size 11497 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png new file mode 100644 index 0000000000..ee16fdae08 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:979326f2383611db2b1ffa8a2a9d0f2a4fb3296fa57a1240daa97ccf36d886e3 +size 10279 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png new file mode 100644 index 0000000000..afab3fd152 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19c209ec0dca3c3b722af63dcd33444d1074749afd47a8364da4a7a071fce8aa +size 10680 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png new file mode 100644 index 0000000000..1ff42298f8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dabd54db78ae93b72cf9ee31fc4b153fe2ccf64869d861fd16995e16494f4d67 +size 10423 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png new file mode 100644 index 0000000000..0cef938ec6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ea12dad2ab5c70aa30bc506343f0fce05e08a2940460ec595216d07a244fc84 +size 10583 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png new file mode 100644 index 0000000000..132a446fae --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:470f7d0a1ceedcc0f9ade603c590101da8231b24745283976b3528a84e72d721 +size 10743 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png new file mode 100644 index 0000000000..aa1aebbac8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b799d6bed5f1648f1a5196a1c4ccc1a8f8ed49f3eeb5971bd7c342518a1c8ec3 +size 10852 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png new file mode 100644 index 0000000000..267ad90715 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28ead44aa94f2403c4273f38185f0eb21fd9645918829298aee52f9c8061ac95 +size 13003 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png new file mode 100644 index 0000000000..84f6b5081c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae77a7b1bb730842ff0f9608900188d5da78717476f10fb8b640a9c413bf4249 +size 38548 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png new file mode 100644 index 0000000000..086d96caa6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88df6cf8d0a0ca3ae5cdc0e67a21e0c2da793cc88d65fc780830678f5d5f2f24 +size 9218 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png new file mode 100644 index 0000000000..3aa6a4b56d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6dd8d2b4e7d18e3091ac8aca92b8ceb9db6ae15bd228b7a6ce268ed2f7852a53 +size 10113 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png new file mode 100644 index 0000000000..23ca97050b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c711dea48e04cc05c243541ad48e6ed576b5f225ceecb515d49d08c2b2db5e0f +size 12289 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png new file mode 100644 index 0000000000..f9cac2cee7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64f8121851420451628e215393f0835597ea82a67fb2365da2454d52f375bde2 +size 36802 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png new file mode 100644 index 0000000000..27ac795939 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80885e7fa1a8b84e1629f64eb4890079d8f4b556c91b415716616ed2d27e5924 +size 8589 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png index 2731706a84..51e4bfa3fe 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:237067f8a59de7f88c313d78716116c5dfe35d9112ad3d42a61f172de2637415 -size 40888 +oid sha256:f8d549d3ef133c621c9e223a3f02f351d8626aeb7b4b05180c2d76b85004cfb9 +size 39459 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png index c560e17b09..fbc749a35a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9304dc29a88d7829aebfb6de1bab40fde20fcaef0fb92efdf9135d4df92c4442 -size 38694 +oid sha256:742abcea0455c0628bbdd2e2cd3367d7a9431fe00cfa0c657a1a95e83c8a7cbf +size 37334 From ea85e5dabed638a791743139ab300d1c717a90e3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 11:55:20 +0100 Subject: [PATCH 140/203] Fix detekt issue. --- tools/detekt/detekt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index 1e73ef425d..0ebe19bcc4 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -224,6 +224,7 @@ Compose: - LocalCompoundColors - LocalSnackbarDispatcher - LocalCameraPositionState + - LocalMediaItemPresenterFactories - LocalTimelineItemPresenterFactories - LocalRoomMemberProfilesCache - LocalMentionSpanTheme From 759cd0f0e0518e846609ece5c22debff6911b946 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 16 Dec 2024 12:09:14 +0100 Subject: [PATCH 141/203] knock requests : manage remaining ui states --- .../impl/list/KnockRequestsListPresenter.kt | 7 +- .../impl/list/KnockRequestsListView.kt | 87 ++++++++++++++++--- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index 04bc48dd4b..3a0d4ba7ae 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -58,7 +58,9 @@ class KnockRequestsListPresenter @Inject constructor( actionTarget = KnockRequestsActionTarget.DeclineAndBan(event.knockRequest) } KnockRequestsListEvents.ResetCurrentAction -> { + asyncAction.value = AsyncAction.Uninitialized actionTarget = KnockRequestsActionTarget.None + targetActionConfirmed = false } KnockRequestsListEvents.RetryCurrentAction -> { retryCount++ @@ -103,10 +105,7 @@ class KnockRequestsListPresenter @Inject constructor( asyncAction.value = AsyncAction.ConfirmingNoParams } } - KnockRequestsActionTarget.None -> { - targetActionConfirmed = false - asyncAction.value = AsyncAction.Uninitialized - } + KnockRequestsActionTarget.None -> Unit } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 58ac4984f4..e928b3a726 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -11,6 +11,7 @@ import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -72,7 +73,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -137,10 +137,18 @@ private fun KnockRequestsListContent( } } is AsyncData.Loading -> { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center), - color = ElementTheme.colors.iconPrimary, - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = spacedBy(16.dp), + modifier = Modifier.align(Alignment.Center) + ) { + CircularProgressIndicator(color = ElementTheme.colors.iconPrimary) + Text( + text = stringResource(R.string.screen_knock_requests_list_initial_loading_title), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) + } } else -> Unit } @@ -186,23 +194,78 @@ private fun KnockRequestsActionsView( onSuccess = { onDismiss() }, onErrorDismiss = onDismiss, confirmationDialog = { - ConfirmationDialog( - title = "Confirmation", - content = "Are you sure?", - onSubmitClick = onConfirm, + KnockRequestActionConfirmation( + actionTarget = actionTarget, + onSubmit = onConfirm, onDismiss = onDismiss, ) }, progressDialog = { - ProgressDialog( - text = "Loading", - ) + KnockRequestActionProgress(target = actionTarget) + }, + errorMessage = { + when (actionTarget) { + is KnockRequestsActionTarget.Accept -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) + is KnockRequestsActionTarget.Decline -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) + is KnockRequestsActionTarget.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) + KnockRequestsActionTarget.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) + else -> "" + } }, onRetry = onRetry, ) } } +@Composable +private fun KnockRequestActionConfirmation( + actionTarget: KnockRequestsActionTarget, + onSubmit: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + val (title, content) = when (actionTarget) { + KnockRequestsActionTarget.AcceptAll -> Pair( + stringResource(R.string.screen_knock_requests_list_accept_all_alert_title), + stringResource(R.string.screen_knock_requests_list_accept_all_alert_description), + ) + is KnockRequestsActionTarget.Decline -> Pair( + stringResource(R.string.screen_knock_requests_list_decline_alert_title), + stringResource(R.string.screen_knock_requests_list_decline_alert_description, actionTarget.knockRequest.getBestName()), + ) + is KnockRequestsActionTarget.DeclineAndBan -> Pair( + stringResource(R.string.screen_knock_requests_list_ban_alert_title), + stringResource(R.string.screen_knock_requests_list_ban_alert_description, actionTarget.knockRequest.getBestName()), + ) + else -> return + } + ConfirmationDialog( + title = title, + content = content, + onSubmitClick = onSubmit, + onDismiss = onDismiss, + modifier = modifier, + ) +} + +@Composable +private fun KnockRequestActionProgress( + target: KnockRequestsActionTarget, + modifier: Modifier = Modifier, +) { + val progressText = when (target) { + is KnockRequestsActionTarget.Accept -> stringResource(R.string.screen_knock_requests_list_accept_loading_title) + is KnockRequestsActionTarget.Decline -> stringResource(R.string.screen_knock_requests_list_decline_loading_title) + is KnockRequestsActionTarget.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_ban_loading_title) + KnockRequestsActionTarget.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_loading_title) + else -> return + } + ProgressDialog( + text = progressText, + modifier = modifier, + ) +} + @Composable private fun KnockRequestsList( knockRequests: ImmutableList, From a1069500516ce8a7a66d9b588ca04e1a807791c6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 16:18:15 +0100 Subject: [PATCH 142/203] Make the room filter use normalized strings. --- .../core/extensions/BasicExtensions.kt | 6 ++++++ .../matrix/api/roomlist/RoomListFilter.kt | 6 +++++- .../matrix/impl/roomlist/RoomListFilter.kt | 3 ++- .../impl/roomlist/RoomListFilterTest.kt | 20 ++++++++++++++++++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index 22a0c518ec..e16985a1d2 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.core.extensions +import java.text.Normalizer import java.util.Locale fun Boolean.toOnOff() = if (this) "ON" else "OFF" @@ -83,3 +84,8 @@ fun String.safeCapitalize(): String { } } } + +fun String.withoutAccents(): String { + return Normalizer.normalize(this, Normalizer.Form.NFD) + .replace("\\p{Mn}+".toRegex(), "") +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt index d47e61099c..e3a6a64d46 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt @@ -7,6 +7,8 @@ package io.element.android.libraries.matrix.api.roomlist +import io.element.android.libraries.core.extensions.withoutAccents + sealed interface RoomListFilter { companion object { /** @@ -73,5 +75,7 @@ sealed interface RoomListFilter { */ data class NormalizedMatchRoomName( val pattern: String - ) : RoomListFilter + ) : RoomListFilter { + val normalizedPattern: String = pattern.withoutAccents() + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt index 88458d56bb..41ef1d79a2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.impl.roomlist +import io.element.android.libraries.core.extensions.withoutAccents import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.roomlist.RoomListFilter @@ -30,7 +31,7 @@ val RoomListFilter.predicate !roomSummary.isInvited() && (roomSummary.info.numUnreadNotifications > 0 || roomSummary.info.isMarkedUnread) } is RoomListFilter.NormalizedMatchRoomName -> { roomSummary: RoomSummary -> - roomSummary.info.name.orEmpty().contains(pattern, ignoreCase = true) + roomSummary.info.name?.withoutAccents().orEmpty().contains(normalizedPattern, ignoreCase = true) } RoomListFilter.Invite -> { roomSummary: RoomSummary -> roomSummary.isInvited() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt index abe7e17bba..870c3d196e 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt @@ -34,6 +34,9 @@ class RoomListFilterTest { private val roomToSearch = aRoomSummary( name = "Room to search" ) + private val roomWithAccent = aRoomSummary( + name = "Frédéric" + ) private val invitedRoom = aRoomSummary( currentUserMembership = CurrentUserMembership.INVITED ) @@ -45,6 +48,7 @@ class RoomListFilterTest { markedAsUnreadRoom, unreadNotificationRoom, roomToSearch, + roomWithAccent, invitedRoom ) @@ -69,7 +73,9 @@ class RoomListFilterTest { @Test fun `Room list filter group`() = runTest { val filter = RoomListFilter.Category.Group - assertThat(roomSummaries.filter(filter)).containsExactly(regularRoom, favoriteRoom, markedAsUnreadRoom, unreadNotificationRoom, roomToSearch) + assertThat(roomSummaries.filter(filter)).containsExactly( + regularRoom, favoriteRoom, markedAsUnreadRoom, unreadNotificationRoom, roomToSearch, roomWithAccent + ) } @Test @@ -96,6 +102,18 @@ class RoomListFilterTest { assertThat(roomSummaries.filter(filter)).containsExactly(roomToSearch) } + @Test + fun `Room list filter normalized match room name with accent`() = runTest { + val filter = RoomListFilter.NormalizedMatchRoomName("Fred") + assertThat(roomSummaries.filter(filter)).containsExactly(roomWithAccent) + } + + @Test + fun `Room list filter normalized match room name with accent when searching with accent`() = runTest { + val filter = RoomListFilter.NormalizedMatchRoomName("Fréd") + assertThat(roomSummaries.filter(filter)).containsExactly(roomWithAccent) + } + @Test fun `Room list filter all with one match`() = runTest { val filter = RoomListFilter.all( From d250b8890ce6a304baa3aca570205a1dc5efa758 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 16:58:58 +0100 Subject: [PATCH 143/203] Add Accept-Language to extra header when opening CustomChromeTab Follow good practice from https://developer.android.com/guide/topics/resources/app-languages#consider-header --- .../libraries/androidutils/browser/ChromeCustomTab.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt index 7c282b13d8..688db47288 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt @@ -10,10 +10,13 @@ package io.element.android.libraries.androidutils.browser import android.app.Activity import android.content.ActivityNotFoundException import android.net.Uri +import android.os.Bundle +import android.provider.Browser import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsSession import io.element.android.libraries.androidutils.system.openUrlInExternalApp +import java.util.Locale /** * Open url in custom tab or, if not available, in the default browser. @@ -51,6 +54,9 @@ fun Activity.openUrlInChromeCustomTab( intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON", true) // Disable bookmark button intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_START_BUTTON", true) + intent.putExtra(Browser.EXTRA_HEADERS, Bundle().apply { + putString("Accept-Language", Locale.getDefault().toLanguageTag()) + }) } .launchUrl(this, Uri.parse(url)) } catch (activityNotFoundException: ActivityNotFoundException) { From ea59ce118deb870a5e7bffa39fe384dcc849492e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 17:36:51 +0100 Subject: [PATCH 144/203] Add a quick filter on the open source licence screen. --- .../impl/list/DependencyLicensesListEvent.kt | 12 +++ .../list/DependencyLicensesListPresenter.kt | 32 ++++++- .../impl/list/DependencyLicensesListState.kt | 2 + .../DependencyLicensesListStateProvider.kt | 29 +++++- .../impl/list/DependencyLicensesListView.kt | 91 ++++++++++++------- .../DependencyLicensesListPresenterTest.kt | 35 +++++++ 6 files changed, 161 insertions(+), 40 deletions(-) create mode 100644 features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt new file mode 100644 index 0000000000..b1262708fc --- /dev/null +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.licenses.impl.list + +sealed interface DependencyLicensesListEvent { + data class SetFilter(val filter: String) : DependencyLicensesListEvent +} diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt index 8b01b00afe..d7ad980dd1 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt @@ -29,6 +29,10 @@ class DependencyLicensesListPresenter @Inject constructor( var licenses by remember { mutableStateOf>>(AsyncData.Loading()) } + var filteredLicenses by remember { + mutableStateOf>>(AsyncData.Loading()) + } + var filter by remember { mutableStateOf("") } LaunchedEffect(Unit) { runCatching { licenses = AsyncData.Success(licensesProvider.provides().toPersistentList()) @@ -36,6 +40,32 @@ class DependencyLicensesListPresenter @Inject constructor( licenses = AsyncData.Failure(it) } } - return DependencyLicensesListState(licenses = licenses) + LaunchedEffect(filter, licenses.dataOrNull()) { + val data = licenses.dataOrNull() + val safeFilter = filter.trim() + if (data != null && safeFilter.isNotEmpty()) { + filteredLicenses = AsyncData.Success(data.filter { + it.safeName.contains(safeFilter, ignoreCase = true) || + it.groupId.contains(safeFilter, ignoreCase = true) || + it.artifactId.contains(safeFilter, ignoreCase = true) + }.toPersistentList()) + } else { + filteredLicenses = licenses + } + } + + fun handleEvent(dependencyLicensesListEvent: DependencyLicensesListEvent) { + when (dependencyLicensesListEvent) { + is DependencyLicensesListEvent.SetFilter -> { + filter = dependencyLicensesListEvent.filter + } + } + } + + return DependencyLicensesListState( + licenses = filteredLicenses, + filter = filter, + eventSink = ::handleEvent, + ) } } diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt index c60c49c81b..fd9b1ccf1c 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt @@ -13,4 +13,6 @@ import kotlinx.collections.immutable.ImmutableList data class DependencyLicensesListState( val licenses: AsyncData>, + val filter: String, + val eventSink: (DependencyLicensesListEvent) -> Unit, ) diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt index dcbae607cb..74c4e424c0 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt @@ -11,28 +11,49 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.licenses.impl.model.DependencyLicenseItem import io.element.android.features.licenses.impl.model.License import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf open class DependencyLicensesListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - DependencyLicensesListState( + aDependencyLicensesListState( licenses = AsyncData.Loading() ), - DependencyLicensesListState( + aDependencyLicensesListState( licenses = AsyncData.Failure(Exception("Failed to load licenses")) ), - DependencyLicensesListState( + aDependencyLicensesListState( licenses = AsyncData.Success( persistentListOf( aDependencyLicenseItem(), aDependencyLicenseItem(name = null), ) ) - ) + ), + aDependencyLicensesListState( + licenses = AsyncData.Success( + persistentListOf( + aDependencyLicenseItem(), + aDependencyLicenseItem(name = null), + ) + ), + filter = "a filter", + ), ) } +private fun aDependencyLicensesListState( + licenses: AsyncData>, + filter: String = "", +): DependencyLicensesListState { + return DependencyLicensesListState( + licenses = licenses, + filter = filter, + eventSink = {}, + ) +} + internal fun aDependencyLicenseItem( name: String? = "A dependency", ) = DependencyLicenseItem( diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt index 740025ce17..f8ce40f1cd 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt @@ -7,31 +7,36 @@ package io.element.android.features.licenses.impl.list +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.OutlinedTextField import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.licenses.impl.model.DependencyLicenseItem import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight 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.ListItem import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun DependencyLicensesListView( state: DependencyLicensesListState, @@ -48,48 +53,64 @@ fun DependencyLicensesListView( ) }, ) { contentPadding -> - LazyColumn( + Column( modifier = Modifier .padding(contentPadding) .padding(horizontal = 16.dp) ) { - when (state.licenses) { - is AsyncData.Failure -> item { - Text( - text = stringResource(CommonStrings.common_error), - modifier = Modifier.padding(16.dp) - ) - } - AsyncData.Uninitialized, - is AsyncData.Loading -> item { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = 64.dp) - ) { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center) + if (state.licenses.isSuccess()) { + // Search field + OutlinedTextField( + value = state.filter, + onValueChange = { state.eventSink(DependencyLicensesListEvent.SetFilter(it)) }, + leadingIcon = { + Icon( + imageVector = CompoundIcons.Search(), + contentDescription = null, + ) + }, + modifier = Modifier.fillMaxWidth(), + ) + } + LazyColumn { + when (state.licenses) { + is AsyncData.Failure -> item { + Text( + text = stringResource(CommonStrings.common_error), + modifier = Modifier.padding(16.dp) ) } - } - is AsyncData.Success -> items(state.licenses.data) { license -> - ListItem( - headlineContent = { Text(license.safeName) }, - supportingContent = { - Text( - buildString { - append(license.groupId) - append(":") - append(license.artifactId) - append(":") - append(license.version) - } + AsyncData.Uninitialized, + is AsyncData.Loading -> item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 64.dp) + ) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center) ) - }, - onClick = { - onOpenLicense(license) } - ) + } + is AsyncData.Success -> items(state.licenses.data) { license -> + ListItem( + headlineContent = { Text(license.safeName) }, + supportingContent = { + Text( + buildString { + append(license.groupId) + append(":") + append(license.artifactId) + append(":") + append(license.version) + } + ) + }, + onClick = { + onOpenLicense(license) + } + ) + } } } } diff --git a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt index 26c4a1ce6f..46dfa33081 100644 --- a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt +++ b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt @@ -33,6 +33,7 @@ class DependencyLicensesListPresenterTest { val finalState = awaitItem() assertThat(finalState.licenses.isSuccess()).isTrue() assertThat(finalState.licenses.dataOrNull()).isEmpty() + assertThat(finalState.filter).isEqualTo("") } } @@ -54,6 +55,40 @@ class DependencyLicensesListPresenterTest { } } + @Test + fun `present - initial state, one license, set filter`() = runTest { + val anItem = aDependencyLicenseItem() + val presenter = createPresenter { + listOf(anItem) + } + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.licenses).isInstanceOf(AsyncData.Loading::class.java) + val loadedState = awaitItem() + assertThat(loadedState.licenses.isSuccess()).isTrue() + assertThat(loadedState.licenses.dataOrNull()!!.size).isEqualTo(1) + loadedState.eventSink(DependencyLicensesListEvent.SetFilter("dep")) + awaitItem().let { state -> + assertThat(state.licenses.dataOrNull()!!.size).isEqualTo(1) + assertThat(state.filter).isEqualTo("dep") + } + loadedState.eventSink(DependencyLicensesListEvent.SetFilter("bleh")) + skipItems(1) + awaitItem().let { state -> + assertThat(state.licenses.dataOrNull()!!.size).isEqualTo(0) + assertThat(state.filter).isEqualTo("bleh") + } + loadedState.eventSink(DependencyLicensesListEvent.SetFilter("")) + skipItems(1) + awaitItem().let { state -> + assertThat(state.licenses.dataOrNull()!!.size).isEqualTo(1) + assertThat(state.filter).isEqualTo("") + } + } + } + private fun createPresenter( provideResult: () -> List ) = DependencyLicensesListPresenter( From 1446524b1e12799ede6efc82556398c938db7501 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 16 Dec 2024 16:52:07 +0000 Subject: [PATCH 145/203] Update screenshots --- ...licenses.impl.list_DependencyLicensesListView_Day_2_en.png | 4 ++-- ...licenses.impl.list_DependencyLicensesListView_Day_3_en.png | 3 +++ ...censes.impl.list_DependencyLicensesListView_Night_2_en.png | 4 ++-- ...censes.impl.list_DependencyLicensesListView_Night_3_en.png | 3 +++ 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png index a66e7fcd95..a967566289 100644 --- a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcc448310dd9e586df3d8e27b9384e5047f9ebca04c1adf7be4d6d1a6ec88aa7 -size 28735 +oid sha256:8f4a7102b45fc1acd7c8cba59282548ae36bf8a3e65acc12c4041990f0cc61c0 +size 30165 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png new file mode 100644 index 0000000000..683799c494 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b16d4f9226d4df9efa9b57a0f6573e9fd6b0301f2e4cde2794de2863cf31ac5 +size 31373 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png index 9f5c57aaea..ad9bc11f60 100644 --- a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2303d25aa0330a27c53d5349b26f86d965fdd87f64c118b6ec8c72a75aa49de7 -size 28165 +oid sha256:a5ccee59f5f0fbc79d252886b647003ce9b3e3b7cb8e54f5c154c37221e30c14 +size 29382 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png new file mode 100644 index 0000000000..a47858aeb1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7afe56fbb521c23252ca5b375a594824d75bf80d9571f830acc14b0f9230d944 +size 30549 From 5715f6054a55afcd95a784729181ae8628592af2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 16 Dec 2024 13:10:23 +0100 Subject: [PATCH 146/203] knock request : expose JoinRule from sdk --- .../matrix/api/room/MatrixRoomInfo.kt | 2 ++ .../matrix/api/room/join/AllowRule.kt | 15 ++++++++++++ .../matrix/api/room/join/JoinRule.kt | 18 +++++++++++++++ .../matrix/impl/room/MatrixRoomInfoMapper.kt | 2 ++ .../matrix/impl/room/join/AllowRule.kt | 19 +++++++++++++++ .../matrix/impl/room/join/JoinRule.kt | 23 +++++++++++++++++++ .../impl/room/MatrixRoomInfoMapperTest.kt | 6 +++++ .../matrix/test/room/RoomInfoFixture.kt | 3 +++ .../matrix/test/room/RoomSummaryFixture.kt | 3 +++ 9 files changed, 91 insertions(+) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt index 6105a59c38..825ff85e96 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt @@ -12,6 +12,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap @@ -27,6 +28,7 @@ data class MatrixRoomInfo( val avatarUrl: String?, val isDirect: Boolean, val isPublic: Boolean, + val joinRule: JoinRule?, val isSpace: Boolean, val isTombstoned: Boolean, val isFavorite: Boolean, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt new file mode 100644 index 0000000000..2ba4893ec8 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.join + +import io.element.android.libraries.matrix.api.core.RoomId + +sealed interface AllowRule { + data class RoomMembership(val roomId: RoomId) : AllowRule + data class Custom(val json: String) : AllowRule +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt new file mode 100644 index 0000000000..33c2ccf092 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.join + +sealed interface JoinRule { + data object Public : JoinRule + data object Private: JoinRule + data object Knock: JoinRule + data object Invite: JoinRule + data class Restricted(val rules: List): JoinRule + data class KnockRestricted(val rules: List): JoinRule + data class Custom(val value: String): JoinRule +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index 607c316b25..2d96a8c970 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.impl.room.join.map import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableList @@ -36,6 +37,7 @@ class MatrixRoomInfoMapper { avatarUrl = it.avatarUrl, isDirect = it.isDirect, isPublic = it.isPublic, + joinRule = it.joinRule?.map(), isSpace = it.isSpace, isTombstoned = it.isTombstoned, isFavorite = it.isFavourite, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt new file mode 100644 index 0000000000..86f37c5f20 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.room.join + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.join.AllowRule +import org.matrix.rustcomponents.sdk.AllowRule as RustAllowRule + +fun RustAllowRule.map(): AllowRule { + return when (this) { + is RustAllowRule.RoomMembership -> AllowRule.RoomMembership(RoomId(roomId)) + is RustAllowRule.Custom -> AllowRule.Custom(json) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt new file mode 100644 index 0000000000..f5c65c7283 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.room.join + +import io.element.android.libraries.matrix.api.room.join.JoinRule +import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule + +fun RustJoinRule.map(): JoinRule { + return when (this) { + RustJoinRule.Public -> JoinRule.Public + RustJoinRule.Private -> JoinRule.Private + RustJoinRule.Knock -> JoinRule.Knock + RustJoinRule.Invite -> JoinRule.Invite + is RustJoinRule.Restricted -> JoinRule.Restricted(rules.map { it.map() }) + is RustJoinRule.Custom -> JoinRule.Custom(repr) + is RustJoinRule.KnockRestricted -> JoinRule.KnockRestricted(rules.map { it.map() }) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt index d2fa714880..9f91ed16de 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomHero import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomInfo @@ -30,6 +31,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toPersistentList import org.junit.Test +import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule import org.matrix.rustcomponents.sdk.Membership import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode @@ -47,6 +49,7 @@ class MatrixRoomInfoMapperTest { isDirect = true, isPublic = false, isSpace = false, + joinRule = RustJoinRule.Invite, isTombstoned = false, isFavourite = false, canonicalAlias = A_ROOM_ALIAS.value, @@ -83,6 +86,7 @@ class MatrixRoomInfoMapperTest { isSpace = false, isTombstoned = false, isFavorite = false, + joinRule = JoinRule.Invite, canonicalAlias = A_ROOM_ALIAS, alternativeAliases = listOf(A_ROOM_ALIAS).toImmutableList(), currentUserMembership = CurrentUserMembership.JOINED, @@ -125,6 +129,7 @@ class MatrixRoomInfoMapperTest { avatarUrl = null, isDirect = false, isPublic = true, + joinRule = null, isSpace = false, isTombstoned = false, isFavourite = true, @@ -159,6 +164,7 @@ class MatrixRoomInfoMapperTest { avatarUrl = null, isDirect = false, isPublic = true, + joinRule = null, isSpace = false, isTombstoned = false, isFavorite = true, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt index 5aae1a3258..c05c24e7de 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt @@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -33,6 +34,7 @@ fun aRoomInfo( avatarUrl: String? = AN_AVATAR_URL, isDirect: Boolean = false, isPublic: Boolean = true, + joinRule: JoinRule? = JoinRule.Public, isSpace: Boolean = false, isTombstoned: Boolean = false, isFavorite: Boolean = false, @@ -64,6 +66,7 @@ fun aRoomInfo( avatarUrl = avatarUrl, isDirect = isDirect, isPublic = isPublic, + joinRule = joinRule, isSpace = isSpace, isTombstoned = isTombstoned, isFavorite = isFavorite, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 39e13cb619..cd612f5ec1 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem @@ -46,6 +47,7 @@ fun aRoomSummary( avatarUrl: String? = null, isDirect: Boolean = false, isPublic: Boolean = true, + joinRule: JoinRule? = JoinRule.Public, isSpace: Boolean = false, isTombstoned: Boolean = false, isFavorite: Boolean = false, @@ -79,6 +81,7 @@ fun aRoomSummary( avatarUrl = avatarUrl, isDirect = isDirect, isPublic = isPublic, + joinRule = joinRule, isSpace = isSpace, isTombstoned = isTombstoned, isFavorite = isFavorite, From 00169c7be217c579cb9df3a523453755920afbcb Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 16 Dec 2024 20:32:17 +0100 Subject: [PATCH 147/203] knock requests : makes knock can be handled and is enabled --- features/knockrequests/impl/build.gradle.kts | 1 + .../impl/banner/KnockRequestsBannerPresenter.kt | 8 +++++++- .../roomdetails/impl/RoomDetailsPresenter.kt | 16 ++++++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts index 83f7132320..b664a0a6b4 100644 --- a/features/knockrequests/impl/build.gradle.kts +++ b/features/knockrequests/impl/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) implementation(projects.libraries.designsystem) + implementation(projects.libraries.featureflag.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index c95fca6a5f..a1ad661b0c 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -19,7 +19,10 @@ import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.core.extensions.firstIfSingle +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState import io.element.android.libraries.matrix.ui.room.canInviteAsState import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope @@ -33,6 +36,7 @@ class KnockRequestsBannerPresenter @Inject constructor( private val room: MatrixRoom, private val knockRequestsService: KnockRequestsService, private val appCoroutineScope: CoroutineScope, + private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable override fun present(): KnockRequestsBannerState { @@ -46,11 +50,13 @@ class KnockRequestsBannerPresenter @Inject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canAccept by room.canInviteAsState(syncUpdateFlow.value) + val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) val showAcceptError = remember { mutableStateOf(false) } + val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) val shouldShowBanner by remember { derivedStateOf { - knockRequests.isNotEmpty() + isKnockRequestsEnabled && canHandleKnockRequests && knockRequests.isNotEmpty() } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index d55f4e4f7d..0fc907a867 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.api.room.roomNotificationSettings @@ -47,7 +48,9 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -77,7 +80,7 @@ class RoomDetailsPresenter @Inject constructor( val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.displayName).trim() } } val roomTopic by remember { derivedStateOf { roomInfo?.topic ?: room.topic } } val isFavorite by remember { derivedStateOf { roomInfo?.isFavorite.orFalse() } } - val isPublic by remember { derivedStateOf { roomInfo?.isPublic.orFalse() } } + val joinRule by remember { derivedStateOf { roomInfo?.joinRule } } val canShowPinnedMessages = isPinnedMessagesFeatureEnabled() var canShowMediaGallery by remember { mutableStateOf(false) } @@ -106,11 +109,9 @@ class RoomDetailsPresenter @Inject constructor( val roomType by getRoomType(dmMember, currentMember) val roomCallState = roomCallStatePresenter.present() - val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic - when { !topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic) canEditTopic && roomType is RoomDetailsType.Room -> RoomTopicState.CanAddTopic @@ -118,10 +119,13 @@ class RoomDetailsPresenter @Inject constructor( } } + val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) - val knockRequestsCount by remember { mutableStateOf(null) } + val knockRequestsCount by produceState(null) { + room.knockRequestsFlow.collect { value = it.size } + } val canShowKnockRequests by remember { - derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests } + derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests && joinRule == JoinRule.Knock } } val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState() @@ -164,7 +168,7 @@ class RoomDetailsPresenter @Inject constructor( roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), isFavorite = isFavorite, displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin, - isPublic = isPublic, + isPublic = joinRule == JoinRule.Public, heroes = roomInfo?.heroes.orEmpty().toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, canShowMediaGallery = canShowMediaGallery, From b12211d1304cc44a3811199550cf51463426fe88 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 09:23:29 +0100 Subject: [PATCH 148/203] Fix formatting quality. --- .../libraries/matrix/impl/roomlist/RoomListFilterTest.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt index 870c3d196e..d057e4ecc3 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt @@ -74,7 +74,12 @@ class RoomListFilterTest { fun `Room list filter group`() = runTest { val filter = RoomListFilter.Category.Group assertThat(roomSummaries.filter(filter)).containsExactly( - regularRoom, favoriteRoom, markedAsUnreadRoom, unreadNotificationRoom, roomToSearch, roomWithAccent + regularRoom, + favoriteRoom, + markedAsUnreadRoom, + unreadNotificationRoom, + roomToSearch, + roomWithAccent, ) } From c48e4f413eb0f93fe9d1f5e9fbebfd0aee56c34f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 10:07:51 +0100 Subject: [PATCH 149/203] Add test on DefaultMediaPlayer. --- .../mediaplayer/impl/DefaultMediaPlayer.kt | 12 +- .../impl/DefaultMediaPlayerTest.kt | 388 +++++++++++++++++- .../mediaplayer/impl/FakeSimplePlayer.kt | 58 +++ 3 files changed, 449 insertions(+), 9 deletions(-) create mode 100644 libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt diff --git a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt index c013ddd587..45fc226cd6 100644 --- a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt @@ -15,7 +15,6 @@ import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.mediaplayer.api.MediaPlayer import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -36,6 +35,7 @@ import kotlin.time.Duration.Companion.seconds @SingleIn(RoomScope::class) class DefaultMediaPlayer @Inject constructor( private val player: SimplePlayer, + private val coroutineScope: CoroutineScope, ) : MediaPlayer { private val listener = object : SimplePlayer.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { @@ -47,7 +47,7 @@ class DefaultMediaPlayer @Inject constructor( ) } if (isPlaying) { - job = scope.launch { updateCurrentPosition() } + job = coroutineScope.launch { updateCurrentPosition() } } else { job?.cancel() } @@ -79,7 +79,6 @@ class DefaultMediaPlayer @Inject constructor( player.addListener(listener) } - private val scope = CoroutineScope(Job() + Dispatchers.Main) private var job: Job? = null private val _state = MutableStateFlow( @@ -102,7 +101,8 @@ class DefaultMediaPlayer @Inject constructor( mimeType: String, startPositionMs: Long, ): MediaPlayer.State { - player.pause() // Must pause here otherwise if the player was playing it would keep on playing the new media item. + // Must pause here otherwise if the player was playing it would keep on playing the new media item. + player.pause() player.clearMediaItems() player.setMediaItem( MediaItem.Builder() @@ -129,11 +129,9 @@ class DefaultMediaPlayer @Inject constructor( player.getCurrentMediaItem()?.let { player.setMediaItem(it, 0) player.prepare() - player.play() } - } else { - player.play() } + player.play() } override fun pause() { diff --git a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt index 16242badb7..5262d65f3d 100644 --- a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt +++ b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt @@ -7,12 +7,396 @@ package io.element.android.libraries.mediaplayer.impl +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.mediaplayer.api.MediaPlayer +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows import org.junit.Test class DefaultMediaPlayerTest { + private val aMediaId = "mediaId" + private val aMediaItem = MediaItem.Builder().setMediaId(aMediaId).build() + + @Test + fun `initial state`() = runTest { + val sut = createDefaultMediaPlayer() + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + } + } + + @Test + fun `start player will update the current position and pause it will stop`() = runTest { + val playLambda = lambdaRecorder { } + val pauseLambda = lambdaRecorder { } + val player = FakeSimplePlayer( + playLambda = playLambda, + pauseLambda = pauseLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + sut.play() + playLambda.assertions().isCalledOnce() + player.durationResult = 123L + player.simulateIsPlayingChanged(true) + val playingState = awaitItem() + assertThat(playingState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = true, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = 123, + ) + ) + player.currentPositionResult = 1L + assertThat(awaitItem()).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = true, + isEnded = false, + mediaId = null, + currentPosition = 1, + duration = 123, + ) + ) + player.currentPositionResult = 2L + assertThat(awaitItem()).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = true, + isEnded = false, + mediaId = null, + currentPosition = 2, + duration = 123, + ) + ) + player.pause() + pauseLambda.assertions().isCalledOnce() + player.simulateIsPlayingChanged(false) + assertThat(awaitItem()).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 2, + duration = 123, + ) + ) + } + } + + @Test + fun `start player on ended playback will not invoke more methods if current media item is null`() = runTest { + val playLambda = lambdaRecorder { } + val getCurrentMediaItemLambda = lambdaRecorder { null } + val player = FakeSimplePlayer( + playLambda = playLambda, + getCurrentMediaItemLambda = getCurrentMediaItemLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + player.playbackStateResult = Player.STATE_ENDED + sut.play() + playLambda.assertions().isCalledOnce() + } + } + + @Test + fun `start player on ended playback will invoke more methods if current media item is not null`() = runTest { + val playLambda = lambdaRecorder { } + val prepareLambda = lambdaRecorder { } + val getCurrentMediaItemLambda = lambdaRecorder { aMediaItem } + val setMediaItemLambda = lambdaRecorder { _, _ -> } + val player = FakeSimplePlayer( + playLambda = playLambda, + prepareLambda = prepareLambda, + setMediaItemLambda = setMediaItemLambda, + getCurrentMediaItemLambda = getCurrentMediaItemLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + player.playbackStateResult = Player.STATE_ENDED + sut.play() + setMediaItemLambda.assertions().isCalledOnce().with( + value(aMediaItem), + value(0L), + ) + prepareLambda.assertions().isCalledOnce() + playLambda.assertions().isCalledOnce() + } + } + + @Test + fun `pause player invokes pause on the embedded player`() = runTest { + val pauseLambda = lambdaRecorder { } + val player = FakeSimplePlayer( + pauseLambda = pauseLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.pause() + pauseLambda.assertions().isCalledOnce() + } + + @Test + fun `close player invokes release on the embedded player`() = runTest { + val releaseLambda = lambdaRecorder { } + val player = FakeSimplePlayer( + releaseLambda = releaseLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.close() + releaseLambda.assertions().isCalledOnce() + } + @Test - fun `default test`() = runTest { - // TODO + fun `seekTo invokes release on the embedded player`() = runTest { + val seekToLambda = lambdaRecorder { } + val player = FakeSimplePlayer( + seekToLambda = seekToLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + awaitItem() + player.currentPositionResult = 33L + sut.seekTo(33L) + seekToLambda.assertions().isCalledOnce().with(value(33L)) + val finalState = awaitItem() + assertThat(finalState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 33L, + duration = null, + ) + ) + } } + + @Test + fun `onPlaybackStateChanged update the state`() = runTest { + val player = FakeSimplePlayer() + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + player.currentPositionResult = 44 + player.durationResult = 123L + player.simulatePlaybackStateChanged(Player.STATE_READY) + val readyState = awaitItem() + assertThat(readyState).isEqualTo( + MediaPlayer.State( + isReady = true, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 44, + duration = 123, + ) + ) + player.simulatePlaybackStateChanged(Player.STATE_ENDED) + val endedState = awaitItem() + assertThat(endedState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = true, + mediaId = null, + currentPosition = 44, + duration = 123, + ) + ) + } + } + + @Test + fun `setMedia with timeout error`() = runTest { + val pauseLambda = lambdaRecorder { } + val clearMediaItemsLambda = lambdaRecorder { } + val setMediaItemLambda = lambdaRecorder { _, _ -> } + val prepareLambda = lambdaRecorder { } + val player = FakeSimplePlayer( + pauseLambda = pauseLambda, + clearMediaItemsLambda = clearMediaItemsLambda, + setMediaItemLambda = setMediaItemLambda, + prepareLambda = prepareLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + val result = runCatching { + sut.setMedia("uri", "mediaId", "mimeType", 12) + } + pauseLambda.assertions().isCalledOnce() + clearMediaItemsLambda.assertions().isCalledOnce() + setMediaItemLambda.assertions().isCalledOnce().with( + value(MediaItem.Builder().setUri("uri").setMediaId("mediaId").setMimeType("mimeType").build()), + value(12L), + ) + prepareLambda.assertions().isCalledOnce() + assertThat(result.isFailure).isTrue() + assertThrows(TimeoutCancellationException::class.java) { + result.getOrThrow() + } + } + } + + @Test + fun `setMedia success`() = runTest { + var player: FakeSimplePlayer? = null + val pauseLambda = lambdaRecorder { } + val clearMediaItemsLambda = lambdaRecorder { } + val setMediaItemLambda = lambdaRecorder { _, _ -> } + val prepareLambda = lambdaRecorder { + player?.simulatePlaybackStateChanged(Player.STATE_READY) + player?.simulateMediaItemTransition(aMediaItem) + } + player = FakeSimplePlayer( + pauseLambda = pauseLambda, + clearMediaItemsLambda = clearMediaItemsLambda, + setMediaItemLambda = setMediaItemLambda, + prepareLambda = prepareLambda, + ) + val sut = createDefaultMediaPlayer( + simplePlayer = player, + ) + sut.state.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo( + MediaPlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = null, + ) + ) + val state = sut.setMedia("uri", "mediaId", "mimeType", 12) + pauseLambda.assertions().isCalledOnce() + clearMediaItemsLambda.assertions().isCalledOnce() + setMediaItemLambda.assertions().isCalledOnce().with( + value(MediaItem.Builder().setUri("uri").setMediaId("mediaId").setMimeType("mimeType").build()), + value(12L), + ) + prepareLambda.assertions().isCalledOnce() + + val finalState = MediaPlayer.State( + isReady = true, + isPlaying = false, + isEnded = false, + mediaId = "mediaId", + currentPosition = 0, + duration = 0, + ) + assertThat(awaitItem()).isEqualTo( + MediaPlayer.State( + isReady = true, + isPlaying = false, + isEnded = false, + mediaId = null, + currentPosition = 0, + duration = 0, + ) + ) + assertThat(awaitItem()).isEqualTo(finalState) + assertThat(state).isEqualTo(finalState) + } + } + + private fun TestScope.createDefaultMediaPlayer( + simplePlayer: SimplePlayer = FakeSimplePlayer(), + ): DefaultMediaPlayer = DefaultMediaPlayer( + simplePlayer, + backgroundScope, + ) } diff --git a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt new file mode 100644 index 0000000000..d981fa7796 --- /dev/null +++ b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaplayer.impl + +import androidx.media3.common.MediaItem +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeSimplePlayer( + private val clearMediaItemsLambda: () -> Unit = { lambdaError() }, + private val setMediaItemLambda: (MediaItem, Long) -> Unit = { _, _ -> lambdaError() }, + private val getCurrentMediaItemLambda: () -> MediaItem? = { lambdaError() }, + private val prepareLambda: () -> Unit = { lambdaError() }, + private val playLambda: () -> Unit = { lambdaError() }, + private val pauseLambda: () -> Unit = { lambdaError() }, + private val seekToLambda: (Long) -> Unit = { lambdaError() }, + private val releaseLambda: () -> Unit = { lambdaError() }, +) : SimplePlayer { + private val listeners = mutableListOf() + override fun addListener(listener: SimplePlayer.Listener) { + listeners.add(listener) + } + + var currentPositionResult: Long = 0 + override val currentPosition: Long get() = currentPositionResult + var playbackStateResult: Int = 0 + override val playbackState: Int get() = playbackStateResult + var durationResult: Long = 0 + override val duration: Long get() = durationResult + + override fun clearMediaItems() = clearMediaItemsLambda() + override fun setMediaItem(mediaItem: MediaItem, startPositionMs: Long) { + setMediaItemLambda(mediaItem, startPositionMs) + } + + override fun getCurrentMediaItem(): MediaItem? = getCurrentMediaItemLambda() + override fun prepare() = prepareLambda() + override fun play() = playLambda() + override fun pause() = pauseLambda() + override fun seekTo(positionMs: Long) = seekToLambda(positionMs) + override fun release() = releaseLambda() + + fun simulateIsPlayingChanged(isPlaying: Boolean) { + listeners.forEach { it.onIsPlayingChanged(isPlaying) } + } + + fun simulateMediaItemTransition(mediaItem: MediaItem?) { + listeners.forEach { it.onMediaItemTransition(mediaItem) } + } + + fun simulatePlaybackStateChanged(playbackState: Int) { + listeners.forEach { it.onPlaybackStateChanged(playbackState) } + } +} From df400a531f0fa84f40a1df38f8827eeecea1f0bb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 11:15:04 +0100 Subject: [PATCH 150/203] Replace ic_developer_options.xml by the icon from Compound. --- .../messages/impl/actionlist/model/TimelineItemAction.kt | 3 +-- .../preferences/impl/root/PreferencesRootView.kt | 4 ++-- .../android/libraries/designsystem/icons/IconsList.kt | 1 - .../src/main/res/drawable/ic_developer_options.xml | 9 --------- 4 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 libraries/designsystem/src/main/res/drawable/ic_developer_options.xml diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index f700dcc6b1..0dd454d3cf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -11,7 +11,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.runtime.Immutable import io.element.android.libraries.designsystem.icons.CompoundDrawables -import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.ui.strings.CommonStrings @Immutable @@ -32,7 +31,7 @@ sealed class TimelineItemAction( data object EditCaption : TimelineItemAction(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit) data object AddCaption : TimelineItemAction(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit) data object RemoveCaption : TimelineItemAction(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_delete, destructive = true) - data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options) + data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CompoundDrawables.ic_compound_code) data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true) data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end) data object Pin : TimelineItemAction(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 0c758852db..c2c2fd0e21 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -25,6 +25,7 @@ import io.element.android.features.preferences.impl.user.UserPreferences import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferencePage +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight @@ -33,7 +34,6 @@ import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.core.DeviceId @@ -270,7 +270,7 @@ private fun ColumnScope.Footer( private fun DeveloperPreferencesView(onOpenDeveloperSettings: () -> Unit) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_developer_options)) }, - leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_developer_options)), + leadingContent = ListItemContent.Icon(IconSource.Resource(CompoundDrawables.ic_compound_code)), onClick = onOpenDeveloperSettings ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt index 28d7f84598..d2ecb3d2e7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt @@ -13,7 +13,6 @@ import io.element.android.libraries.designsystem.R // All the icons should be defined in Compound. internal val iconsOther = listOf( R.drawable.ic_cancel, - R.drawable.ic_developer_options, R.drawable.ic_encryption_enabled, R.drawable.ic_groups, R.drawable.ic_notification_small, diff --git a/libraries/designsystem/src/main/res/drawable/ic_developer_options.xml b/libraries/designsystem/src/main/res/drawable/ic_developer_options.xml deleted file mode 100644 index a55953010c..0000000000 --- a/libraries/designsystem/src/main/res/drawable/ic_developer_options.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - From 9d34275d9050dbcd5c4075f8fd959e947fd690f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 11:19:04 +0100 Subject: [PATCH 151/203] Remove unused ic_groups.xml --- .../libraries/designsystem/icons/IconsList.kt | 1 - .../src/main/res/drawable/ic_groups.xml | 15 --------------- 2 files changed, 16 deletions(-) delete mode 100644 libraries/designsystem/src/main/res/drawable/ic_groups.xml diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt index d2ecb3d2e7..3822401b37 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt @@ -14,7 +14,6 @@ import io.element.android.libraries.designsystem.R internal val iconsOther = listOf( R.drawable.ic_cancel, R.drawable.ic_encryption_enabled, - R.drawable.ic_groups, R.drawable.ic_notification_small, R.drawable.ic_plus_composer, R.drawable.ic_stop, diff --git a/libraries/designsystem/src/main/res/drawable/ic_groups.xml b/libraries/designsystem/src/main/res/drawable/ic_groups.xml deleted file mode 100644 index 6f4b91e8dc..0000000000 --- a/libraries/designsystem/src/main/res/drawable/ic_groups.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - From dddef7936d190f3afb9e0ec555d272d4d2dd692f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 11:21:09 +0100 Subject: [PATCH 152/203] Change icon of the remove caption action --- .../messages/impl/actionlist/model/TimelineItemAction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index 0dd454d3cf..c5ce637d2d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -30,7 +30,7 @@ sealed class TimelineItemAction( data object Edit : TimelineItemAction(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit) data object EditCaption : TimelineItemAction(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit) data object AddCaption : TimelineItemAction(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit) - data object RemoveCaption : TimelineItemAction(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_delete, destructive = true) + data object RemoveCaption : TimelineItemAction(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_close, destructive = true) data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CompoundDrawables.ic_compound_code) data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true) data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end) From 293da9800e228e0185c1f37282bc5a92897b57b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 11:26:46 +0100 Subject: [PATCH 153/203] Reorder items in message action list. --- .../impl/actionlist/model/TimelineItemActionComparator.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt index 8eef2d7619..b8ebd1a223 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt @@ -15,13 +15,13 @@ class TimelineItemActionComparator : Comparator { TimelineItemAction.Reply, TimelineItemAction.ReplyInThread, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.Unpin, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, - TimelineItemAction.CopyText, TimelineItemAction.AddCaption, TimelineItemAction.EditCaption, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, + TimelineItemAction.Unpin, + TimelineItemAction.CopyText, TimelineItemAction.CopyCaption, TimelineItemAction.RemoveCaption, TimelineItemAction.ViewSource, From 57595d6a5753b44beeeee53c17a1224f22124356 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 11:32:23 +0100 Subject: [PATCH 154/203] Make TimelineItemAction an enum class. --- .../actionlist/model/TimelineItemAction.kt | 36 +++++++++---------- ...MessagesListTimelineActionPostProcessor.kt | 6 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index c5ce637d2d..cfb6878ab5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -14,26 +14,26 @@ import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.ui.strings.CommonStrings @Immutable -sealed class TimelineItemAction( +enum class TimelineItemAction( @StringRes val titleRes: Int, @DrawableRes val icon: Int, val destructive: Boolean = false ) { - data object ViewInTimeline : TimelineItemAction(CommonStrings.action_view_in_timeline, CompoundDrawables.ic_compound_visibility_on) - data object Forward : TimelineItemAction(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward) - data object CopyText : TimelineItemAction(CommonStrings.action_copy_text, CompoundDrawables.ic_compound_copy) - data object CopyCaption : TimelineItemAction(CommonStrings.action_copy_caption, CompoundDrawables.ic_compound_copy) - data object CopyLink : TimelineItemAction(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link) - data object Redact : TimelineItemAction(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true) - data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply) - data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply) - data object Edit : TimelineItemAction(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit) - data object EditCaption : TimelineItemAction(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit) - data object AddCaption : TimelineItemAction(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit) - data object RemoveCaption : TimelineItemAction(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_close, destructive = true) - data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CompoundDrawables.ic_compound_code) - data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true) - data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end) - data object Pin : TimelineItemAction(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin) - data object Unpin : TimelineItemAction(CommonStrings.action_unpin, CompoundDrawables.ic_compound_unpin) + ViewInTimeline(CommonStrings.action_view_in_timeline, CompoundDrawables.ic_compound_visibility_on), + Forward(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward), + CopyText(CommonStrings.action_copy_text, CompoundDrawables.ic_compound_copy), + CopyCaption(CommonStrings.action_copy_caption, CompoundDrawables.ic_compound_copy), + CopyLink(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link), + Redact(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true), + Reply(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply), + ReplyInThread(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply), + Edit(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit), + EditCaption(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit), + AddCaption(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit), + RemoveCaption(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_close, destructive = true), + ViewSource(CommonStrings.action_view_source, CompoundDrawables.ic_compound_code), + ReportContent(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true), + EndPoll(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end), + Pin(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin), + Unpin(CommonStrings.action_unpin, CompoundDrawables.ic_compound_unpin), } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt index 48fdb83d79..1e86d4af08 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt @@ -14,9 +14,9 @@ class PinnedMessagesListTimelineActionPostProcessor : TimelineItemActionPostProc override fun process(actions: List): List { return buildList { add(TimelineItemAction.ViewInTimeline) - actions.firstOrNull { it is TimelineItemAction.Unpin }?.let(::add) - actions.firstOrNull { it is TimelineItemAction.Forward }?.let(::add) - actions.firstOrNull { it is TimelineItemAction.ViewSource }?.let(::add) + actions.firstOrNull { it == TimelineItemAction.Unpin }?.let(::add) + actions.firstOrNull { it == TimelineItemAction.Forward }?.let(::add) + actions.firstOrNull { it == TimelineItemAction.ViewSource }?.let(::add) } } } From 22214204fdc6a10a170bb3128781ab5e0fbc196a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 14:09:49 +0100 Subject: [PATCH 155/203] Add tests on TimelineItemActionComparator --- .../model/TimelineItemActionComparator.kt | 5 +++- .../model/TimelineItemActionComparatorTest.kt | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt index b8ebd1a223..e28f6ebb6d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt @@ -7,9 +7,12 @@ package io.element.android.features.messages.impl.actionlist.model +import androidx.annotation.VisibleForTesting + class TimelineItemActionComparator : Comparator { // See order in https://www.figma.com/design/ux3tYoZV9WghC7hHT9Fhk0/Compound-iOS-Components?node-id=2946-2392 - private val orderedList = listOf( + @VisibleForTesting + val orderedList = listOf( TimelineItemAction.EndPoll, TimelineItemAction.ViewInTimeline, TimelineItemAction.Reply, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt new file mode 100644 index 0000000000..9866d846ec --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.messages.impl.actionlist.model + +import org.junit.Test + +class TimelineItemActionComparatorTest { + @Test + fun `check that the list in the comparator only contain each item once`() { + val sut = TimelineItemActionComparator() + sut.orderedList.forEach { + require(sut.orderedList.count { item -> item == it } == 1, { "Duplicate ${it::class.java}.$it" }) + } + } + + @Test + fun `check that the list in the comparator contains all the items`() { + val sut = TimelineItemActionComparator() + TimelineItemAction.entries.forEach { + require(it in sut.orderedList, { "Missing ${it::class.simpleName}.$it in orderedList" }) + } + } +} From a0e0d2569f7235c0f9dd028b0f8856215b4f8c3f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 14:22:15 +0100 Subject: [PATCH 156/203] Add EditPoll action and fix tests. --- .../messages/impl/MessagesPresenter.kt | 3 +- .../impl/actionlist/ActionListPresenter.kt | 2 + .../actionlist/ActionListStateProvider.kt | 1 + .../actionlist/model/TimelineItemAction.kt | 1 + .../model/TimelineItemActionComparator.kt | 1 + .../messages/impl/MessagesPresenterTest.kt | 2 +- .../actionlist/ActionListPresenterTest.kt | 54 +++++++++---------- ...agesListTimelineActionPostProcessorTest.kt | 19 +------ 8 files changed, 36 insertions(+), 47 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 4895390ab8..5fc68a0c61 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 @@ -274,7 +274,8 @@ class MessagesPresenter @AssistedInject constructor( TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent) TimelineItemAction.CopyLink -> handleCopyLink(targetEvent) TimelineItemAction.Redact -> handleActionRedact(targetEvent) - TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting) + TimelineItemAction.Edit, + TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState) TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState) TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 95a6dc5a6f..d631cf3dcd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -178,6 +178,8 @@ class DefaultActionListPresenter @AssistedInject constructor( add(TimelineItemAction.EditCaption) add(TimelineItemAction.RemoveCaption) } + } else if (timelineItem.content is TimelineItemPollContent) { + add(TimelineItemAction.EditPoll) } else { add(TimelineItemAction.Edit) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index 1638a03fa3..2fef1fc525 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -203,6 +203,7 @@ fun aTimelineItemActionList( fun aTimelineItemPollActionList(): ImmutableList { return setOf( TimelineItemAction.EndPoll, + TimelineItemAction.EditPoll, TimelineItemAction.Reply, TimelineItemAction.Pin, TimelineItemAction.CopyLink, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index cfb6878ab5..bd506fa3a5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -28,6 +28,7 @@ enum class TimelineItemAction( Reply(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply), ReplyInThread(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply), Edit(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit), + EditPoll(CommonStrings.action_edit_poll, CompoundDrawables.ic_compound_edit), EditCaption(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit), AddCaption(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit), RemoveCaption(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_close, destructive = true), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt index e28f6ebb6d..a8a42b17ed 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt @@ -19,6 +19,7 @@ class TimelineItemActionComparator : Comparator { TimelineItemAction.ReplyInThread, TimelineItemAction.Forward, TimelineItemAction.Edit, + TimelineItemAction.EditPoll, TimelineItemAction.AddCaption, TimelineItemAction.EditCaption, TimelineItemAction.CopyLink, 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 8cb7464a9a..1369b5af63 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 @@ -467,7 +467,7 @@ class MessagesPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent(content = aTimelineItemPollContent()))) + initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditPoll, aMessageEvent(content = aTimelineItemPollContent()))) awaitItem() onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } 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 14605f31d5..cc4120ffab 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 @@ -179,8 +179,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, @@ -225,8 +225,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ReplyInThread, TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, @@ -273,8 +273,8 @@ class ActionListPresenterTest { verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, @@ -320,8 +320,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, @@ -368,8 +368,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, @@ -417,9 +417,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.Redact, @@ -463,9 +463,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ReplyInThread, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.Redact, @@ -512,9 +512,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, ) @@ -559,9 +559,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.CopyLink, TimelineItemAction.AddCaption, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.ViewSource, TimelineItemAction.Redact, ) @@ -612,8 +612,8 @@ class ActionListPresenterTest { TimelineItemAction.Forward, // Not here // TimelineItemAction.AddCaption, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.ViewSource, TimelineItemAction.Redact, ) @@ -660,9 +660,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.CopyLink, TimelineItemAction.EditCaption, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyCaption, TimelineItemAction.RemoveCaption, TimelineItemAction.ViewSource, @@ -711,8 +711,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyCaption, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, @@ -830,9 +830,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, + TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.CopyText, TimelineItemAction.Redact, ) @@ -878,8 +878,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, + TimelineItemAction.CopyLink, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.Redact, @@ -933,9 +933,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Unpin, - TimelineItemAction.CopyLink, TimelineItemAction.Edit, + TimelineItemAction.CopyLink, + TimelineItemAction.Unpin, TimelineItemAction.CopyText, TimelineItemAction.ViewSource, TimelineItemAction.Redact, @@ -1072,9 +1072,9 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.EndPoll, TimelineItemAction.Reply, - TimelineItemAction.Pin, + TimelineItemAction.EditPoll, TimelineItemAction.CopyLink, - TimelineItemAction.Edit, + TimelineItemAction.Pin, TimelineItemAction.Redact, ) ) @@ -1116,8 +1116,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.EndPoll, TimelineItemAction.Reply, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.Redact, ) ) @@ -1158,8 +1158,8 @@ class ActionListPresenterTest { verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = persistentListOf( TimelineItemAction.Reply, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.Redact, ) ) @@ -1203,8 +1203,8 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Pin, TimelineItemAction.CopyLink, + TimelineItemAction.Pin, TimelineItemAction.Redact, ) ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt index 7043d3f848..59545d6674 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt @@ -27,24 +27,7 @@ class PinnedMessagesListTimelineActionPostProcessorTest { fun `ensure that some actions are kept and some other are filtered out`() { val sut = PinnedMessagesListTimelineActionPostProcessor() val result = sut.process( - listOf( - TimelineItemAction.Forward, - TimelineItemAction.CopyText, - TimelineItemAction.CopyCaption, - TimelineItemAction.CopyLink, - TimelineItemAction.Redact, - TimelineItemAction.Reply, - TimelineItemAction.ReplyInThread, - TimelineItemAction.Edit, - TimelineItemAction.EditCaption, - TimelineItemAction.AddCaption, - TimelineItemAction.RemoveCaption, - TimelineItemAction.ViewSource, - TimelineItemAction.ReportContent, - TimelineItemAction.EndPoll, - TimelineItemAction.Pin, - TimelineItemAction.Unpin, - ) + TimelineItemAction.entries.toList() ) assertThat(result).isEqualTo( listOf( From cb7e1b6e4e7bbdcbc60aa2fd8bfd63407c5296f8 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 17 Dec 2024 13:46:25 +0000 Subject: [PATCH 157/203] Update screenshots --- ...ssages.impl.actionlist_ActionListViewContent_Day_10_en.png | 4 ++-- ...ssages.impl.actionlist_ActionListViewContent_Day_11_en.png | 4 ++-- ...ssages.impl.actionlist_ActionListViewContent_Day_12_en.png | 2 +- ...essages.impl.actionlist_ActionListViewContent_Day_2_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_3_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_4_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_5_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_6_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_7_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_8_en.png | 4 ++-- ...essages.impl.actionlist_ActionListViewContent_Day_9_en.png | 4 ++-- ...ages.impl.actionlist_ActionListViewContent_Night_10_en.png | 4 ++-- ...ages.impl.actionlist_ActionListViewContent_Night_11_en.png | 4 ++-- ...ages.impl.actionlist_ActionListViewContent_Night_12_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_2_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_3_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_4_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_5_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_6_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_7_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_8_en.png | 4 ++-- ...sages.impl.actionlist_ActionListViewContent_Night_9_en.png | 4 ++-- ...res.preferences.impl.root_PreferencesRootViewDark_0_en.png | 4 ++-- ...res.preferences.impl.root_PreferencesRootViewDark_1_en.png | 4 ++-- ...es.preferences.impl.root_PreferencesRootViewLight_0_en.png | 4 ++-- ...es.preferences.impl.root_PreferencesRootViewLight_1_en.png | 4 ++-- .../libraries.designsystem.icons_IconsOther_Day_0_en.png | 4 ++-- .../libraries.designsystem.icons_IconsOther_Night_0_en.png | 4 ++-- 28 files changed, 55 insertions(+), 55 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png index 223d7869b6..3bf8f0bd3d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbef9e8e887fa493ca9b875e64dc164bf35dd84ad25b84a1ad9b6fd523b26c38 -size 27280 +oid sha256:4bcf877df431dfe4cf3c7f19d58c413356cdf77ae26f9dfbc9f9136fea3f5c02 +size 29361 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png index cc268a914f..da9d93301b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1a0bc9c83b7e01f9492443433781509c0d899b397652fc7e9b7a539d8ce0412 -size 48219 +oid sha256:88d5893c849bfb7945535f1f450e0c8b8975b5b4eb3ca336bff73aa607f2f34d +size 48171 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png index d2eec8525c..e0975da1a9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a624587095825c971186288a0ad5a40ddfcadc8f4b9f92b1102d8ae20ca3bda +oid sha256:3f0451276061db5c189062b32414fd93c2f174c3e7597ab723c3f1a0abbfae28 size 49821 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png index 392c35230c..6b36ec5059 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4cdcc38bfae43298654c3e09d7ab1ca1e0d2fd32157a464539a360f3943f4f75 -size 42839 +oid sha256:f8ec0f80c44ea01158f58e53c0aeafe6df0586e13ff452a61c8b6a9ffb070702 +size 42824 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png index 89efb1c866..fe9b8d0f55 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a56b3e488ceecd0bfb763d60b6827f7d8c460fa198d380a502ff0d97b13c9bc0 -size 45962 +oid sha256:22aabc7669365a40151e7ecd82a3324a53d0f55a370e0dd803d843a5275b1434 +size 45949 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png index 496cf3053b..2f90f04914 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:600f28097c6ef8c54fe136d9bb05b8b800d693bff23f238a4e3e0216ee9918ff -size 44404 +oid sha256:1a82048429ed60a4b246c37bc1b104e166c8c2402884766a9e487ca98ad45a57 +size 44391 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png index 834430dfb3..70d0754dab 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:054cfe9290f91cf0f5d17e8f7c49e71eb50a2bb17067b1edc3c29f89410f76e8 -size 40806 +oid sha256:b7e8e9782859caf125c6258a612d52532cf005d1d21146e21068882b586f5d1d +size 40788 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png index f082532ffd..8e3e0660c1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:814bb524af65d077e3610e8cb920e5207aa808111a530357e8687831bd9c0413 -size 44687 +oid sha256:640a1d04239f041ae395b56eee251a2942764c97dc50db3a8d4c3bfc44225a1a +size 44670 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png index 6db122607c..d1cba8905f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4da93eb0b9fcea861ae80d6e14bc852acaacea52402b1b41e8c982b9c06b2b63 -size 42105 +oid sha256:150a3d6bd18e87b15575f7f452b222f07e6c3ef227032cbfe84a7728d17c491e +size 42088 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png index 358a9d9a81..cf5c641ec7 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4886407cfe23d5d16aadeef4226698957dad99dfc4d9ac5c184fe64347b3ee41 -size 44524 +oid sha256:737959115454525aa41c67d3ff47aa1829d78c7d18648b7c14b5e62ff6e44436 +size 44511 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png index 4de1129978..9778b3513b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c0d8491dbf0b6f50ae6be5c7aaf0d77b9eaa3a31e7763f120d6e08993c1c74b -size 34028 +oid sha256:dd031f67683678815eb3f4b55f3ee65fb79096035705019770a626bfc5c00794 +size 34009 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png index 65a49cb609..c281247aeb 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64a6d1a6af14176c933387a7a36de0d7392b1cd31cb04fcc5c6a282317874aac -size 26597 +oid sha256:97fbbf4f3b9466a086a8824f9230a1ea5525314d818e19879a2c5340c9495138 +size 28642 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png index 89e23b0444..c275736aa0 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f92ebc24de4dd34b84fcbbcc656a369d4a79bca160666c2fd4d32b5516c2f8fb -size 47245 +oid sha256:69f31cbb83a84e57e8a9e7bf4ff451d5a192523486c400abdc293a467ca0ffa5 +size 47200 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png index 6e575e21c5..17714bdf54 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a4a3730a941a4c6863c116b401ea7e4d5c2cef3fe4098e353954c4f93aa660a -size 48771 +oid sha256:410450770889b906c279084668980b41357e432345bc098560d8b06d6e35c86d +size 48841 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png index 053b325557..674ba67cce 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77521de94c04228ca0fb5d09cb7809527cf0dc0acc52a271a8fa4738a752aa66 -size 42028 +oid sha256:4ce4a7fdb57fd4afae868aa733b013fd5e4daea6aaa736256ed72d9d5eb759e7 +size 41988 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png index a3c9b39e27..faee81cc79 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:171ef67bdc5a1b6d921ecbdb13e3c79dfab073d7289862181a69515e6063c4c5 -size 45246 +oid sha256:6721b3503beb8ddfa9efc52a7ab269c6aea9fcefad2c1b1b9ce31b4240d48719 +size 45204 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png index 1374a97dd7..2e828a0005 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a8328afeab339104a83db16d7b4b894c060ff7e80c2b5c4d6ec64d3bc4cdc3a -size 43613 +oid sha256:ac363ebe575582b39b3ebd7857ff891994f512064f167fee342a9a2b2a35dd1e +size 43572 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png index 6f45705e5e..084646ff71 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d575d08141bf446de510162f5dc1e04fccf4831649495bdf94945bd8957768a6 -size 40201 +oid sha256:3d30a38861e317010616e7b17614803439f345e376f36b32e3e6e832070ada71 +size 40150 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png index 59e4356eb2..133a8cbd14 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:606379fcb517afa4583ac03fcb2af4f5649a7fc064df841f442c19f5a71f629b -size 43863 +oid sha256:96f4023e2a502fb50dc9c03ad53faa932baf1e316652651e109f21f531adb5de +size 43819 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png index 8225529b52..a6e70d1e6e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d63dec242ea479beb9a7bf0a58c6f31e2bba84df0a7db51fb07ea46612944d9a -size 41355 +oid sha256:8f52f074d2dbf93c3170c80dd18f4601ad1e2db6c57773d0e10c3f8c29ffdc9f +size 41303 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png index 26f908facc..5d6104964e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5421b7b5ce8441213b1323b4cd88c2f5aff6689ecd091b5be581fe5690cd611 -size 43685 +oid sha256:504ac65ffbcb0705f3e73d32970cd345d23d19e4f190420f8953e55250056329 +size 43641 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png index 0e328d0740..720075f007 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.actionlist_ActionListViewContent_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1f4600552aebd3a567251a85105a423882942b23e96f7958238a9b5ba0bb8fc -size 33137 +oid sha256:f72f6ec83324b0b6e3719e1e0f59387cb0376149decbb780970dcb7f219a1551 +size 33053 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_0_en.png index faf2367a0b..c13d3cb3ba 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48320aed4570138a76b04b08f37f67098a72e1b63f13273fd6d9c0a6e33b7e10 -size 37955 +oid sha256:4a6a54efb3bb7eeb97cfa06995a199eb83a7e630fb1b631d7992b464e0b04d20 +size 37954 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_1_en.png index 5ba26e89ea..ef6e64aa89 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewDark_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a71b634518191f299c924f8ddd39b44ddc1255698e981796bb8b034377c515f -size 37712 +oid sha256:d35962150a8dc5b7d774af160a994182afbe2d5538e204b2545c02287897cd99 +size 37711 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_0_en.png index b64b5e290d..4e534c9041 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e33a80d4f6dc4a1bd1cd86b2bfd64471926871f92d8d83cae6b32a79459c8ea0 -size 38775 +oid sha256:a3711fb587d635b28a398bb5b475bee16d4abdbf691d494348fe11e270e0624d +size 38774 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_1_en.png index 344a42b7a5..8d27190ccb 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.root_PreferencesRootViewLight_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c69e1d5748e65b35525df64b83c6616ab439299284ab2bda3a8408d5f4139d2f -size 38802 +oid sha256:41b91c8a1805bb24453c6f85091704e609a01f83a86ca17979d97de4c20e4cbe +size 38800 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png index 2b971f938f..ca94a884e3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:448e4fea5f9363a84c020548764328e477662aecc7f423237ea06e439563b507 -size 25511 +oid sha256:560c9159e78e9da940da58a4297aca1ac647218fab0e03c532016ac96a3a560d +size 21524 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png index af0fbca712..6793e1a405 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4efddd547921361891b175554e3ae789259f7059fc18b68bcca2a38401f387a -size 24742 +oid sha256:b0bb37fb7f6dbce206288431c59e3cc1b31d1a5a25d73b2f2321d7d46a459da5 +size 20631 From 03925109da9cb9ba696ad58c7f2a32860a483524 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 17 Dec 2024 15:35:44 +0100 Subject: [PATCH 158/203] knock request : simplify executing action --- .../impl/list/KnockRequestsListPresenter.kt | 95 ++++++++++--------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index 3a0d4ba7ae..d6e2adbebd 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -9,11 +9,12 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.AsyncAction @@ -23,6 +24,8 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.ui.room.canBanAsState import io.element.android.libraries.matrix.ui.room.canInviteAsState import io.element.android.libraries.matrix.ui.room.canKickAsState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor( @@ -33,8 +36,6 @@ class KnockRequestsListPresenter @Inject constructor( override fun present(): KnockRequestsListState { val asyncAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } var actionTarget by remember { mutableStateOf(KnockRequestsActionTarget.None) } - var targetActionConfirmed by remember { mutableStateOf(false) } - var retryCount by remember { mutableIntStateOf(0) } val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canBan by room.canBanAsState(syncUpdateFlow.value) @@ -43,6 +44,8 @@ class KnockRequestsListPresenter @Inject constructor( val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState() + val coroutineScope = rememberCoroutineScope() + fun handleEvents(event: KnockRequestsListEvents) { when (event) { KnockRequestsListEvents.AcceptAll -> { @@ -60,53 +63,17 @@ class KnockRequestsListPresenter @Inject constructor( KnockRequestsListEvents.ResetCurrentAction -> { asyncAction.value = AsyncAction.Uninitialized actionTarget = KnockRequestsActionTarget.None - targetActionConfirmed = false } KnockRequestsListEvents.RetryCurrentAction -> { - retryCount++ + coroutineScope.executeAction(actionTarget, asyncAction, isActionConfirmed = true) } KnockRequestsListEvents.ConfirmCurrentAction -> { - targetActionConfirmed = true + coroutineScope.executeAction(actionTarget, asyncAction, isActionConfirmed = true) } } } - - LaunchedEffect(actionTarget, targetActionConfirmed, retryCount) { - when (val action = actionTarget) { - is KnockRequestsActionTarget.Accept -> { - runUpdatingState(asyncAction) { - knockRequestsService.acceptKnockRequest(action.knockRequest) - } - } - is KnockRequestsActionTarget.Decline -> { - if (targetActionConfirmed) { - runUpdatingState(asyncAction) { - knockRequestsService.declineKnockRequest(action.knockRequest) - } - } else { - asyncAction.value = AsyncAction.ConfirmingNoParams - } - } - is KnockRequestsActionTarget.DeclineAndBan -> { - if (targetActionConfirmed) { - runUpdatingState(asyncAction) { - knockRequestsService.declineAndBanKnockRequest(action.knockRequest) - } - } else { - asyncAction.value = AsyncAction.ConfirmingNoParams - } - } - is KnockRequestsActionTarget.AcceptAll -> { - if (targetActionConfirmed) { - runUpdatingState(asyncAction) { - knockRequestsService.acceptAllKnockRequests() - } - } else { - asyncAction.value = AsyncAction.ConfirmingNoParams - } - } - KnockRequestsActionTarget.None -> Unit - } + LaunchedEffect(actionTarget) { + executeAction(actionTarget, asyncAction, isActionConfirmed = false) } return KnockRequestsListState( @@ -119,4 +86,46 @@ class KnockRequestsListPresenter @Inject constructor( eventSink = ::handleEvents ) } + + private fun CoroutineScope.executeAction( + actionTarget: KnockRequestsActionTarget, + asyncAction: MutableState>, + isActionConfirmed: Boolean, + ) = launch { + when (actionTarget) { + is KnockRequestsActionTarget.Accept -> { + runUpdatingState(asyncAction) { + knockRequestsService.acceptKnockRequest(actionTarget.knockRequest) + } + } + is KnockRequestsActionTarget.Decline -> { + if (isActionConfirmed) { + runUpdatingState(asyncAction) { + knockRequestsService.declineKnockRequest(actionTarget.knockRequest) + } + } else { + asyncAction.value = AsyncAction.ConfirmingNoParams + } + } + is KnockRequestsActionTarget.DeclineAndBan -> { + if (isActionConfirmed) { + runUpdatingState(asyncAction) { + knockRequestsService.declineAndBanKnockRequest(actionTarget.knockRequest) + } + } else { + asyncAction.value = AsyncAction.ConfirmingNoParams + } + } + is KnockRequestsActionTarget.AcceptAll -> { + if (isActionConfirmed) { + runUpdatingState(asyncAction) { + knockRequestsService.acceptAllKnockRequests() + } + } else { + asyncAction.value = AsyncAction.ConfirmingNoParams + } + } + KnockRequestsActionTarget.None -> Unit + } + } } From 0b5dc40d402ac2f287c3e483971d4181531d6b1a Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 17 Dec 2024 15:36:08 +0100 Subject: [PATCH 159/203] knock requests : make sure to use the correct confirmation submit text --- .../impl/list/KnockRequestsListView.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index e928b3a726..cee983050d 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -224,24 +224,29 @@ private fun KnockRequestActionConfirmation( onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { - val (title, content) = when (actionTarget) { - KnockRequestsActionTarget.AcceptAll -> Pair( + val (title, content, submitText) = when (actionTarget) { + KnockRequestsActionTarget.AcceptAll -> Triple( stringResource(R.string.screen_knock_requests_list_accept_all_alert_title), stringResource(R.string.screen_knock_requests_list_accept_all_alert_description), + stringResource(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title), + ) - is KnockRequestsActionTarget.Decline -> Pair( + is KnockRequestsActionTarget.Decline -> Triple( stringResource(R.string.screen_knock_requests_list_decline_alert_title), stringResource(R.string.screen_knock_requests_list_decline_alert_description, actionTarget.knockRequest.getBestName()), + stringResource(R.string.screen_knock_requests_list_decline_alert_confirm_button_title), ) - is KnockRequestsActionTarget.DeclineAndBan -> Pair( + is KnockRequestsActionTarget.DeclineAndBan -> Triple( stringResource(R.string.screen_knock_requests_list_ban_alert_title), stringResource(R.string.screen_knock_requests_list_ban_alert_description, actionTarget.knockRequest.getBestName()), + stringResource(R.string.screen_knock_requests_list_ban_alert_confirm_button_title), ) else -> return } ConfirmationDialog( title = title, content = content, + submitText = submitText, onSubmitClick = onSubmit, onDismiss = onDismiss, modifier = modifier, From e1f6c07ecd48c4aabd6c7a748fce78530c67094d Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 17 Dec 2024 15:36:30 +0100 Subject: [PATCH 160/203] knock requests : add tests to the feature --- features/knockrequests/impl/build.gradle.kts | 10 + .../impl/data/KnockRequestsModule.kt | 25 ++ .../impl/data/KnockRequestsService.kt | 19 +- .../KnockRequestsBannerPresenterTest.kt | 253 ++++++++++++++ .../banner/KnockRequestsBannerViewTest.kt | 102 ++++++ .../list/KnockRequestsListPresenterTest.kt | 310 ++++++++++++++++++ .../impl/list/KnockRequestsListViewTest.kt | 163 +++++++++ .../test/room/knock/FakeKnockRequest.kt | 21 +- 8 files changed, 886 insertions(+), 17 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt create mode 100644 features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt create mode 100644 features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt create mode 100644 features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt create mode 100644 features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts index b664a0a6b4..2664528d74 100644 --- a/features/knockrequests/impl/build.gradle.kts +++ b/features/knockrequests/impl/build.gradle.kts @@ -14,6 +14,11 @@ plugins { android { namespace = "io.element.android.features.knockrequests.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } setupAnvil() @@ -31,7 +36,12 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) + testImplementation(libs.test.robolectric) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) + testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(projects.libraries.featureflag.test) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt new file mode 100644 index 0000000000..83f0a08c5b --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.Provides +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.room.MatrixRoom + +@Module +@ContributesTo(RoomScope::class) +object KnockRequestsModule { + @Provides + @SingleIn(RoomScope::class) + fun knockRequestsService(room: MatrixRoom): KnockRequestsService { + return KnockRequestsService(room.knockRequestsFlow, room.roomCoroutineScope) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt index 653930b16b..5a373f3f36 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt @@ -8,13 +8,13 @@ package io.element.android.features.knockrequests.impl.data import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.knock.KnockRequest import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -22,23 +22,24 @@ import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.supervisorScope -import javax.inject.Inject -@SingleIn(RoomScope::class) -class KnockRequestsService @Inject constructor(room: MatrixRoom) { +class KnockRequestsService( + knockRequestsFlow: Flow>, + coroutineScope: CoroutineScope, +) { // Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them. private val handledKnockRequestIds = MutableStateFlow>(emptySet()) val knockRequestsFlow = combine( - room.wrappedKnockRequestsFlow(), + knockRequestsFlow.wrapped(), handledKnockRequestIds, ) { knockRequests, handledKnockIds -> val presentableKnockRequests = knockRequests .filter { it.eventId !in handledKnockIds } .toImmutableList() AsyncData.Success(presentableKnockRequests) - }.stateIn(room.roomCoroutineScope, SharingStarted.Lazily, AsyncData.Loading()) + }.stateIn(coroutineScope, SharingStarted.Lazily, AsyncData.Loading()) private fun knockRequestsList() = knockRequestsFlow.value.dataOrNull().orEmpty() @@ -129,7 +130,7 @@ class KnockRequestsService @Inject constructor(room: MatrixRoom) { private fun knockRequestNotFoundResult() = Result.failure(IllegalArgumentException("Knock request not found")) - private fun MatrixRoom.wrappedKnockRequestsFlow() = knockRequestsFlow.map { knockRequests -> + private fun Flow>.wrapped() = map { knockRequests -> knockRequests.map { KnockRequestWrapper(it) } } } diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt new file mode 100644 index 0000000000..189f392f95 --- /dev/null +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.knockrequests.impl.data.KnockRequestsService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.room.knock.KnockRequest +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.A_USER_ID_3 +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) class KnockRequestsBannerPresenterTest { + + @Test + fun `present - when feature is disabled then the banner should be hidden`() = runTest { + val knockRequests = flowOf(listOf(FakeKnockRequest())) + val presenter = createKnockRequestsBannerPresenter(isFeatureEnabled = false, knockRequestsFlow = knockRequests) + presenter.test { + skipItems(1) + awaitItem().also { state -> + assertThat(state.isVisible).isFalse() + } + } + } + + @Test + fun `present - when empty knock request list then the banner should be hidden`() = runTest { + val knockRequests = flowOf(emptyList()) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(1) + awaitItem().also { state -> + assertThat(state.isVisible).isFalse() + } + } + } + + @Test + fun `present - when no permission to manage knock requests then the banner should be hidden`() = runTest { + val presenter = createKnockRequestsBannerPresenter(canAcceptKnockRequests = false) + presenter.test { + awaitItem().also { state -> + assertThat(state.isVisible).isFalse() + } + } + } + + @Test + fun `present - when everything is setup to manage knocks with data, then the banner should be visible `() = runTest { + val knockRequests = flowOf( + listOf( + FakeKnockRequest( + reason = "A reason", + ) + ) + ) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.isVisible).isTrue() + assertThat(state.knockRequests).hasSize(1) + assertThat(state.canAccept).isTrue() + assertThat(state.reason).isEqualTo("A reason") + } + } + } + + @Test + fun `present - when multiple knock requests, the banner should not have reason nor subtitle`() = runTest { + val knockRequests = flowOf( + listOf( + FakeKnockRequest( + displayName = "Alice", + ), + FakeKnockRequest( + displayName = "Bob", + ), + FakeKnockRequest( + displayName = "Charlie", + ), + ) + ) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.isVisible).isTrue() + assertThat(state.knockRequests).hasSize(3) + assertThat(state.reason).isNull() + assertThat(state.subtitle).isNull() + } + } + } + + @Test + fun `present - when there are some seen knock requests, then the banner should filtered them`() = runTest { + val knockRequests = flowOf( + listOf( + FakeKnockRequest( + displayName = "Alice", + isSeen = true, + userId = A_USER_ID + ), + FakeKnockRequest( + displayName = "Bob", + isSeen = true, + userId = A_USER_ID_2 + ), + FakeKnockRequest( + isSeen = false, + displayName = "Charlie", + reason = "A reason", + userId = A_USER_ID_3 + ), + ) + ) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.isVisible).isTrue() + // Only Charlie should be displayed + assertThat(state.knockRequests).hasSize(1) + assertThat(state.reason).isEqualTo("A reason") + assertThat(state.subtitle).isEqualTo(A_USER_ID_3.value) + } + } + } + + @Test + fun `present - given AcceptSingleRequest event with failure, then the banner should hide and reappear and error should appear and disappear`() = runTest { + val acceptLambda = lambdaRecorder> { Result.failure(Exception()) } + val knockRequest = FakeKnockRequest( + displayName = "Alice", + reason = "A reason", + acceptLambda = acceptLambda + ) + val knockRequests = flowOf(listOf(knockRequest)) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(2) + awaitItem().also { state -> + state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + } + awaitItem().also { state -> + assertThat(state.isVisible).isFalse() + assertThat(state.displayAcceptError).isFalse() + } + awaitItem().also { state -> + assertThat(state.isVisible).isFalse() + assertThat(state.displayAcceptError).isTrue() + } + awaitItem().also { state -> + assertThat(state.isVisible).isTrue() + assertThat(state.displayAcceptError).isTrue() + } + awaitItem().also { state -> + assertThat(state.isVisible).isTrue() + assertThat(state.displayAcceptError).isFalse() + } + assert(acceptLambda).isCalledOnce() + } + } + + @Test + fun `present - given an AcceptSingleRequest event with success, then banner should be dismissed`() = runTest { + val acceptLambda = lambdaRecorder> { Result.success(Unit) } + val knockRequest = FakeKnockRequest( + displayName = "Alice", + reason = "A reason", + acceptLambda = acceptLambda + ) + val knockRequests = flowOf(listOf(knockRequest)) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.knockRequests).hasSize(1) + state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + } + awaitItem().also { state -> + assertThat(state.isVisible).isFalse() + } + advanceUntilIdle() + assert(acceptLambda).isCalledOnce() + } + } + + @Test + fun `present - given a Dismiss event, then knock requests should be marked as seen`() = runTest { + val markAsSeenLambda = lambdaRecorder> { Result.success(Unit) } + val knockRequests = flowOf( + listOf( + FakeKnockRequest(markAsSeenLambda = markAsSeenLambda), + FakeKnockRequest(markAsSeenLambda = markAsSeenLambda), + FakeKnockRequest(markAsSeenLambda = markAsSeenLambda), + ) + ) + val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests) + presenter.test { + skipItems(2) + awaitItem().also { state -> + state.eventSink(KnockRequestsBannerEvents.Dismiss) + } + advanceUntilIdle() + assert(markAsSeenLambda).isCalledExactly(3) + } + } +} + +private fun TestScope.createKnockRequestsBannerPresenter( + knockRequestsFlow: Flow> = flowOf(emptyList()), + canAcceptKnockRequests: Boolean = true, + isFeatureEnabled: Boolean = true, +): KnockRequestsBannerPresenter { + val knockRequestsService = KnockRequestsService( + knockRequestsFlow = knockRequestsFlow, + coroutineScope = backgroundScope + ) + val featureFlagService = FakeFeatureFlagService( + initialState = mapOf( + FeatureFlags.Knock.key to isFeatureEnabled + ) + ) + return KnockRequestsBannerPresenter( + room = FakeMatrixRoom( + canInviteResult = { Result.success(canAcceptKnockRequests) } + ), + knockRequestsService = knockRequestsService, + appCoroutineScope = this, + featureFlagService = featureFlagService + ) +} diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt new file mode 100644 index 0000000000..7326be47b2 --- /dev/null +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.knockrequests.impl.R +import io.element.android.features.knockrequests.impl.data.aKnockRequest +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class KnockRequestsListViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on view on single request invoke the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setKnockRequestsBannerView( + state = aKnockRequestsBannerState( + eventSink = eventsRecorder, + ), + onViewRequestsClick = it + ) + rule.clickOn(R.string.screen_room_single_knock_request_view_button_title) + } + } + + @Test + fun `clicking on view all when multiple requests invoke the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setKnockRequestsBannerView( + state = aKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest(displayName = "Alice"), + aKnockRequest(displayName = "Bob"), + aKnockRequest(displayName = "Charlie") + ), + eventSink = eventsRecorder, + ), + onViewRequestsClick = it + ) + rule.clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title) + } + } + + @Test + fun `clicking on accept on a single request emit the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setKnockRequestsBannerView( + state = aKnockRequestsBannerState( + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_accept) + eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest) + } + + @Test + fun `clicking on dismiss emit the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setKnockRequestsBannerView( + state = aKnockRequestsBannerState( + eventSink = eventsRecorder, + ), + ) + val close = rule.activity.getString(CommonStrings.action_close) + rule.onNodeWithContentDescription(close).performClick() + eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss) + } +} + +private fun AndroidComposeTestRule.setKnockRequestsBannerView( + state: KnockRequestsBannerState, + onViewRequestsClick: () -> Unit = EnsureNeverCalled(), +) { + setContent { + KnockRequestsBannerView( + state = state, + onViewRequestsClick = onViewRequestsClick, + ) + } +} diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt new file mode 100644 index 0000000000..1638428c79 --- /dev/null +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt @@ -0,0 +1,310 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.features.knockrequests.impl.list + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.knockrequests.impl.data.KnockRequestsService +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.room.knock.KnockRequest +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class KnockRequestsListPresenterTest { + @Test + fun `present - initial states should be emitted`() = runTest { + val presenter = createKnockRequestsListPresenter() + presenter.test { + awaitItem().also { state -> + assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java) + assertThat(state.canAccept).isFalse() + assertThat(state.canDecline).isFalse() + assertThat(state.canBan).isFalse() + } + awaitItem().also { state -> + assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java) + assertThat(state.canAccept).isTrue() + assertThat(state.canDecline).isTrue() + assertThat(state.canBan).isTrue() + } + awaitItem().also { state -> + assertThat(state.knockRequests).isInstanceOf(AsyncData.Success::class.java) + assertThat(state.knockRequests.dataOrNull()).isEmpty() + } + } + } + + @Test + fun `present - accept success scenario`() = runTest { + val acceptLambda = lambdaRecorder> { Result.success(Unit) } + val knockRequest = FakeKnockRequest(acceptLambda = acceptLambda) + val knockRequests = flowOf(listOf(knockRequest)) + val presenter = createKnockRequestsListPresenter( + knockRequestsFlow = knockRequests + ) + presenter.test { + skipItems(2) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable)) + } + skipItems(1) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.Accept(knockRequestPresentable)) + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + } + skipItems(2) + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() + } + assert(acceptLambda).isCalledOnce() + } + } + + @Test + fun `present - accept failure scenario`() = runTest { + val acceptLambda = lambdaRecorder> { Result.failure(Exception()) } + val knockRequest = FakeKnockRequest(acceptLambda = acceptLambda) + val knockRequests = flowOf(listOf(knockRequest)) + val presenter = createKnockRequestsListPresenter( + knockRequestsFlow = knockRequests + ) + presenter.test { + skipItems(2) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable)) + } + skipItems(1) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.Accept(knockRequestPresentable)) + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java) + state.eventSink(KnockRequestsListEvents.RetryCurrentAction) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.knockRequests.dataOrNull()).hasSize(1) + } + assert(acceptLambda).isCalledExactly(2) + } + } + + @Test + fun `present - decline success scenario`() = runTest { + val declineLambda = lambdaRecorder> { Result.success(Unit) } + val knockRequest = FakeKnockRequest(declineLambda = declineLambda) + val knockRequests = flowOf(listOf(knockRequest)) + val presenter = createKnockRequestsListPresenter( + knockRequestsFlow = knockRequests + ) + presenter.test { + skipItems(2) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + state.eventSink(KnockRequestsListEvents.Decline(knockRequestPresentable)) + } + skipItems(1) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.Decline(knockRequestPresentable)) + assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) + state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + } + skipItems(2) + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() + } + } + assert(declineLambda).isCalledOnce() + } + + @Test + fun `present - decline and ban success scenario`() = runTest { + val declineAndBanLambda = lambdaRecorder> { Result.success(Unit) } + val knockRequest = FakeKnockRequest(declineAndBanLambda = declineAndBanLambda) + val knockRequests = flowOf(listOf(knockRequest)) + val presenter = createKnockRequestsListPresenter( + knockRequestsFlow = knockRequests + ) + presenter.test { + skipItems(2) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequestPresentable)) + } + skipItems(1) + awaitItem().also { state -> + val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.DeclineAndBan(knockRequestPresentable)) + assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) + state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + } + skipItems(2) + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() + } + } + assert(declineAndBanLambda).isCalledOnce() + } + + @Test + fun `present - accept all success scenario`() = runTest { + val acceptLambda = lambdaRecorder> { Result.success(Unit) } + val knockRequests = flowOf( + listOf( + FakeKnockRequest(eventId = AN_EVENT_ID, acceptLambda = acceptLambda), + FakeKnockRequest(eventId = AN_EVENT_ID_2, acceptLambda = acceptLambda), + ) + ) + val presenter = createKnockRequestsListPresenter( + knockRequestsFlow = knockRequests + ) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.canAcceptAll).isTrue() + state.eventSink(KnockRequestsListEvents.AcceptAll) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.AcceptAll) + assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) + state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + } + skipItems(2) + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() + } + } + assert(acceptLambda).isCalledExactly(2) + } + + @Test + fun `present - accept all partial success scenario`() = runTest { + val acceptSuccessLambda = lambdaRecorder> { Result.success(Unit) } + val acceptFailureLambda = lambdaRecorder> { Result.failure(Exception()) } + val knockRequests = flowOf( + listOf( + FakeKnockRequest(eventId = AN_EVENT_ID, acceptLambda = acceptSuccessLambda), + FakeKnockRequest(eventId = AN_EVENT_ID_2, acceptLambda = acceptFailureLambda), + ) + ) + val presenter = createKnockRequestsListPresenter( + knockRequestsFlow = knockRequests + ) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.canAcceptAll).isTrue() + state.eventSink(KnockRequestsListEvents.AcceptAll) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.AcceptAll) + assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) + state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java) + state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + } + skipItems(2) + awaitItem().also { state -> + assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.knockRequests.dataOrNull()).hasSize(1) + } + } + assert(acceptFailureLambda).isCalledOnce() + assert(acceptSuccessLambda).isCalledOnce() + } + + private fun TestScope.createKnockRequestsListPresenter( + canAccept: Boolean = true, + canDecline: Boolean = true, + canBan: Boolean = true, + knockRequestsFlow: Flow> = flowOf(emptyList()) + ): KnockRequestsListPresenter { + val room = FakeMatrixRoom( + canInviteResult = { Result.success(canAccept) }, + canKickResult = { Result.success(canDecline) }, + canBanResult = { Result.success(canBan) } + ) + val knockRequestsService = KnockRequestsService( + knockRequestsFlow = knockRequestsFlow, + coroutineScope = backgroundScope + ) + return KnockRequestsListPresenter( + room = room, + knockRequestsService = knockRequestsService, + ) + } +} diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt new file mode 100644 index 0000000000..5620c65ea7 --- /dev/null +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.knockrequests.impl.R +import io.element.android.features.knockrequests.impl.data.aKnockRequest +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import kotlinx.collections.immutable.persistentListOf +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class KnockRequestsListViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invoke the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setKnockRequestsListView( + aKnockRequestsListState( + eventSink = eventsRecorder, + ), + onBackClick = it + ) + rule.pressBack() + } + } + + @Test + fun `clicking on accept emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequest = aKnockRequest() + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(persistentListOf(knockRequest)), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_accept) + eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest)) + } + + @Test + fun `clicking on decline emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequest = aKnockRequest() + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(persistentListOf(knockRequest)), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_decline) + eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest)) + } + + @Test + fun `clicking on decline and ban emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequest = aKnockRequest() + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(persistentListOf(knockRequest)), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title) + eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest)) + } + + @Test + fun `clicking on accept all emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(knockRequests), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(R.string.screen_knock_requests_list_accept_all_button_title) + eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll) + } + + @Test + fun `retry on async view retry emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(knockRequests), + asyncAction = AsyncAction.Failure(Throwable("Failed to accept all")), + actionTarget = KnockRequestsActionTarget.AcceptAll, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_retry) + eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction) + } + + @Test + fun `canceling async view emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(knockRequests), + asyncAction = AsyncAction.Failure(Throwable("Failed to accept all")), + actionTarget = KnockRequestsActionTarget.AcceptAll, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction) + } + + @Test + fun `confirming async view emit the expected event`() { + val eventsRecorder = EventsRecorder() + val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + rule.setKnockRequestsListView( + aKnockRequestsListState( + knockRequests = AsyncData.Success(knockRequests), + asyncAction = AsyncAction.ConfirmingNoParams, + actionTarget = KnockRequestsActionTarget.AcceptAll, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title) + eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction) + } +} + +private fun AndroidComposeTestRule.setKnockRequestsListView( + state: KnockRequestsListState, + onBackClick: () -> Unit = EnsureNeverCalled(), +) { + setContent { + KnockRequestsListView( + state = state, + onBackClick = onBackClick, + ) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt index fad12e6bb6..88866b9dde 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt @@ -7,37 +7,42 @@ package io.element.android.libraries.matrix.test.room.knock +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.test.AN_AVATAR_URL +import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.simulateLongTask class FakeKnockRequest( + override val eventId: EventId = AN_EVENT_ID, override val userId: UserId = A_USER_ID, override val displayName: String? = A_USER_NAME, override val avatarUrl: String? = AN_AVATAR_URL, override val reason: String? = null, override val timestamp: Long? = null, + override val isSeen: Boolean = false, val acceptLambda: () -> Result = { lambdaError() }, val declineLambda: (String?) -> Result = { lambdaError() }, val declineAndBanLambda: (String?) -> Result = { lambdaError() }, val markAsSeenLambda: () -> Result = { lambdaError() }, ) : KnockRequest { - override suspend fun accept(): Result { - return acceptLambda() + override suspend fun accept(): Result = simulateLongTask{ + acceptLambda() } - override suspend fun decline(reason: String?): Result { - return declineLambda(reason) + override suspend fun decline(reason: String?): Result = simulateLongTask { + declineLambda(reason) } - override suspend fun declineAndBan(reason: String?): Result { - return declineAndBanLambda(reason) + override suspend fun declineAndBan(reason: String?): Result = simulateLongTask { + declineAndBanLambda(reason) } - override suspend fun markAsSeen(): Result { - return markAsSeenLambda() + override suspend fun markAsSeen(): Result = simulateLongTask { + markAsSeenLambda() } } From 865f877bb7e3ecb0d4b056178490c3660892c77d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 15:42:50 +0100 Subject: [PATCH 161/203] Fix flaky test by using CompletableDeferred --- .../DeveloperSettingsPresenterTest.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 5568b2beba..7e7a1f56c2 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -26,9 +26,8 @@ import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.lambda.lambdaRecorder -import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -155,10 +154,18 @@ class DeveloperSettingsPresenterTest { } } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - toggling simplified sliding sync changes the preferences and logs out the user`() = runTest { - val logoutCallRecorder = lambdaRecorder { "" } + val latch1 = CompletableDeferred() + val latch2 = CompletableDeferred() + val logoutCallRecorder = lambdaRecorder { + if (latch1.isActive) { + latch1.complete(Unit) + } else { + latch2.complete(Unit) + } + "" + } val logoutUseCase = FakeLogoutUseCase(logoutLambda = logoutCallRecorder) val preferences = InMemoryAppPreferencesStore() val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences, logoutUseCase = logoutUseCase) @@ -171,13 +178,12 @@ class DeveloperSettingsPresenterTest { initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true)) assertThat(awaitItem().isSimpleSlidingSyncEnabled).isTrue() assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue() - advanceUntilIdle() + latch1.await() logoutCallRecorder.assertions().isCalledOnce() - initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false)) assertThat(awaitItem().isSimpleSlidingSyncEnabled).isFalse() assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse() - advanceUntilIdle() + latch2.await() logoutCallRecorder.assertions().isCalledExactly(times = 2) } } From c6dfc14366be694d801526611131fc44afa7ddb8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2024 14:52:59 +0100 Subject: [PATCH 162/203] There is no need to compute the number of column manually. --- .../mediaviewer/impl/gallery/MediaGalleryView.kt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index d5ab8e1fa2..f0497250b0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -34,7 +34,6 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -73,7 +72,6 @@ import io.element.android.libraries.mediaviewer.impl.gallery.ui.VideoItemView import io.element.android.libraries.mediaviewer.impl.gallery.ui.VoiceItemView import io.element.android.libraries.voiceplayer.api.VoiceMessageState import kotlinx.collections.immutable.ImmutableList -import kotlin.math.max @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -315,19 +313,11 @@ private fun MediaGalleryImageGrid( eventSink: (MediaGalleryEvents) -> Unit, onItemClick: (MediaItem.Event) -> Unit, ) { - val configuration = LocalConfiguration.current - val screenWidth = configuration.screenWidthDp.dp - val horizontalPadding = 16.dp - val itemSpacing = 4.dp - val availableWidth = screenWidth - horizontalPadding * 2 - val minCellWidth = 80.dp - // Calculate the number of columns - val columns = max(1, (availableWidth / (minCellWidth + itemSpacing)).toInt()) LazyVerticalGrid( modifier = Modifier .fillMaxSize() - .padding(horizontal = horizontalPadding), - columns = GridCells.Fixed(columns), + .padding(horizontal = 16.dp), + columns = GridCells.Adaptive(80.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { @@ -336,7 +326,7 @@ private fun MediaGalleryImageGrid( span = { item -> when (item) { is MediaItem.LoadingIndicator, - is MediaItem.DateSeparator -> GridItemSpan(columns) + is MediaItem.DateSeparator -> GridItemSpan(maxLineSpan) is MediaItem.Event -> GridItemSpan(1) } }, From 70f133a291cb2a916c7569cd0bc2a61a394cff5a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2024 16:21:26 +0100 Subject: [PATCH 163/203] Gallery: Actions on files are actually only for voice messages. --- .../impl/gallery/EventItemFactory.kt | 1 - .../impl/gallery/MediaGalleryView.kt | 14 +--- .../mediaviewer/impl/gallery/MediaItem.kt | 1 - .../impl/gallery/ui/AudioItemView.kt | 82 ++----------------- .../impl/gallery/ui/CaptionView.kt | 34 ++++++++ .../impl/gallery/ui/FileItemView.kt | 71 ++-------------- .../impl/gallery/ui/MediaItemAudioProvider.kt | 2 - .../impl/gallery/ui/VoiceItemView.kt | 19 +---- .../gallery/DefaultEventItemFactoryTest.kt | 1 - 9 files changed, 52 insertions(+), 173 deletions(-) create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index fbd4f647bc..b039cef4e8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -104,7 +104,6 @@ class EventItemFactory @Inject constructor( waveform = null, ), mediaSource = type.source, - duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), ) is FileMessageType -> MediaItem.File( id = currentTimelineItem.uniqueId, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index f0497250b0..9508fb42d3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -267,24 +267,18 @@ private fun MediaGalleryFilesList( items(files) { item -> when (item) { is MediaItem.File -> FileItemView( - item, + file = item, onClick = { onItemClick(item) }, - onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, - onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, - onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, ) is MediaItem.Audio -> AudioItemView( - item, + audio = item, onClick = { onItemClick(item) }, - onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, - onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, - onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, ) is MediaItem.Voice -> { val presenter: Presenter = presenterFactories.rememberPresenter(item) VoiceItemView( - presenter.present(), - item, + state = presenter.present(), + voice = item, onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt index 222f620ec5..05b6937c15 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt @@ -57,7 +57,6 @@ sealed interface MediaItem { val eventId: EventId?, val mediaInfo: MediaInfo, val mediaSource: MediaSource, - val duration: String?, ) : Event data class Voice( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt index 9a7d498119..dd06fa1bf8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -30,13 +29,11 @@ import androidx.compose.ui.text.style.TextOverflow 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.libraries.core.extensions.withBrackets import io.element.android.libraries.designsystem.preview.ElementPreview 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.IconButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem @@ -44,31 +41,24 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem fun AudioItemView( audio: MediaItem.Audio, onClick: () -> Unit, - onShareClick: () -> Unit, - onDownloadClick: () -> Unit, - onInfoClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier .fillMaxWidth() - .padding(top = 20.dp, start = 16.dp, end = 16.dp), + .padding(horizontal = 16.dp), ) { + Spacer(modifier = Modifier.height(20.dp)) FilenameRow( audio = audio, onClick = onClick, ) val caption = audio.mediaInfo.caption if (caption != null) { - Spacer(modifier = Modifier.height(16.dp)) - Caption(caption) + CaptionView(caption) + } else { + Spacer(modifier = Modifier.height(20.dp)) } - Spacer(modifier = Modifier.height(16.dp)) - ActionIconsRow( - onShareClick = onShareClick, - onDownloadClick = onDownloadClick, - onInfoClick = onInfoClick, - ) HorizontalDivider() } } @@ -101,16 +91,6 @@ private fun FilenameRow( imageVector = Icons.Outlined.GraphicEq, contentDescription = null, ) - audio.duration?.let { - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = audio.duration, - style = ElementTheme.typography.fontBodyMdMedium, - color = ElementTheme.colors.textSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } Spacer(modifier = Modifier.width(8.dp)) Text( text = audio.mediaInfo.filename, @@ -131,55 +111,6 @@ private fun FilenameRow( } } -@Composable -private fun Caption(caption: String) { - Text( - modifier = Modifier.fillMaxWidth(), - text = caption, - maxLines = 5, - overflow = TextOverflow.Ellipsis, - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textPrimary, - ) -} - -@Composable -private fun ActionIconsRow( - onShareClick: () -> Unit, - onDownloadClick: () -> Unit, - onInfoClick: () -> Unit, -) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - IconButton( - onClick = onShareClick, - ) { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - contentDescription = null, - ) - } - IconButton( - onClick = onDownloadClick, - ) { - Icon( - imageVector = CompoundIcons.Download(), - contentDescription = null, - ) - } - IconButton( - onClick = onInfoClick, - ) { - Icon( - imageVector = CompoundIcons.Info(), - contentDescription = null, - ) - } - } -} - @PreviewsDayNight @Composable internal fun AudioItemViewPreview( @@ -188,8 +119,5 @@ internal fun AudioItemViewPreview( AudioItemView( audio = audio, onClick = {}, - onShareClick = {}, - onDownloadClick = {}, - onInfoClick = {}, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt new file mode 100644 index 0000000000..6fc85336a8 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.gallery.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun CaptionView( + caption: String, + modifier: Modifier = Modifier, +) { + Text( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + text = caption, + maxLines = 5, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt index 307ed89bdc..6618815c21 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -34,7 +33,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview 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.IconButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem @@ -42,31 +40,24 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem fun FileItemView( file: MediaItem.File, onClick: () -> Unit, - onShareClick: () -> Unit, - onDownloadClick: () -> Unit, - onInfoClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier .fillMaxWidth() - .padding(top = 20.dp, start = 16.dp, end = 16.dp), + .padding(horizontal = 16.dp), ) { + Spacer(modifier = Modifier.height(20.dp)) FilenameRow( file = file, onClick = onClick, ) val caption = file.mediaInfo.caption if (caption != null) { - Spacer(modifier = Modifier.height(16.dp)) - Caption(caption) + CaptionView(caption) + } else { + Spacer(modifier = Modifier.height(20.dp)) } - Spacer(modifier = Modifier.height(16.dp)) - ActionIconsRow( - onShareClick = onShareClick, - onDownloadClick = onDownloadClick, - onInfoClick = onInfoClick, - ) HorizontalDivider() } } @@ -119,55 +110,6 @@ private fun FilenameRow( } } -@Composable -private fun Caption(caption: String) { - Text( - modifier = Modifier.fillMaxWidth(), - text = caption, - maxLines = 5, - overflow = TextOverflow.Ellipsis, - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textPrimary, - ) -} - -@Composable -private fun ActionIconsRow( - onShareClick: () -> Unit, - onDownloadClick: () -> Unit, - onInfoClick: () -> Unit, -) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - IconButton( - onClick = onShareClick, - ) { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - contentDescription = null, - ) - } - IconButton( - onClick = onDownloadClick, - ) { - Icon( - imageVector = CompoundIcons.Download(), - contentDescription = null, - ) - } - IconButton( - onClick = onInfoClick, - ) { - Icon( - imageVector = CompoundIcons.Info(), - contentDescription = null, - ) - } - } -} - @PreviewsDayNight @Composable internal fun FileItemViewPreview( @@ -176,8 +118,5 @@ internal fun FileItemViewPreview( FileItemView( file = file, onClick = {}, - onShareClick = {}, - onDownloadClick = {}, - onInfoClick = {}, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt index 1194449aef..5d4ebe0b94 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt @@ -32,7 +32,6 @@ fun aMediaItemAudio( id: UniqueId = UniqueId("fileId"), filename: String = "filename", caption: String? = null, - duration: String? = "1:23", ): MediaItem.Audio { return MediaItem.Audio( id = id, @@ -42,6 +41,5 @@ fun aMediaItemAudio( caption = caption, ), mediaSource = MediaSource(""), - duration = duration, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt index 472ea6555f..807be6c116 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -66,18 +66,19 @@ fun VoiceItemView( Column( modifier = modifier .fillMaxWidth() - .padding(top = 20.dp, start = 16.dp, end = 16.dp), + .padding(horizontal = 16.dp), ) { + Spacer(modifier = Modifier.height(20.dp)) VoiceInfoRow( state = state, voice = voice, ) val caption = voice.mediaInfo.caption if (caption != null) { + CaptionView(caption) + } else { Spacer(modifier = Modifier.height(16.dp)) - Caption(caption) } - Spacer(modifier = Modifier.height(16.dp)) ActionIconsRow( onShareClick = onShareClick, onDownloadClick = onDownloadClick, @@ -256,18 +257,6 @@ private fun CustomIconButton( ) } -@Composable -private fun Caption(caption: String) { - Text( - modifier = Modifier.fillMaxWidth(), - text = caption, - maxLines = 5, - overflow = TextOverflow.Ellipsis, - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textPrimary, - ) -} - @Composable private fun ActionIconsRow( onShareClick: () -> Unit, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt index d074581f7d..c03ebc37c4 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/DefaultEventItemFactoryTest.kt @@ -262,7 +262,6 @@ class DefaultEventItemFactoryTest { waveform = null, ), mediaSource = MediaSource(""), - duration = "7:36", ) ) } From 9bdd2cb11d32918c13592c0cfbeaf92fe1f562e0 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 17 Dec 2024 16:25:16 +0000 Subject: [PATCH 164/203] Update screenshots --- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png | 4 ++-- ...ries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png | 4 ++-- ...ries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png | 4 ++-- ...ries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png | 4 ++-- ...es.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png | 4 ++-- ...es.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png | 4 ++-- ...es.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 4 ++-- 16 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png index 6c0958790b..02a746e1d8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9712ec0c9240afdd093e76cbc04143c49807819c31697d957b22e664b66b2dba -size 11333 +oid sha256:8b2613f0382fcc71f248e92a2ab007c20b88178f10f20e91a98f9112ac5dd3b2 +size 8226 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png index b51d1b3801..9ccb8d78d2 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5ec1a0bd7a4d1bd06f3b01c960334503ddac208b6cdedf521dea7c0cd83efd2 -size 15156 +oid sha256:dfc1ae0e3cb25ee9e6092c2eda4b5e8d03ec3fae37e8d4d4d591e3d697042201 +size 12845 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png index fd7f6f30fb..aea3483115 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80c113c5a4e71e00e41abc21f22340f8374488047101e4e79472a7549f5bb50f -size 38712 +oid sha256:51c50604b17ac864c548a33141aba6bf9c1c791790a11fe692493a9fd3d91fda +size 35598 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png index 311b4ae127..d79c6a8d25 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71e8ea9b852fc15170b61c283248034404761487091ed6290015f4e9c9ca2d3e -size 10919 +oid sha256:37f2c5ddc5dbf12e9006865a8e0348949e8811e9e3031721d67dcc0b878d00ba +size 8011 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png index b97ea7edce..04df2c1c11 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:527242ca1b585e76247210f2a000e6511bfbe90b0c8f8eaf850f8318b6067d5a -size 14627 +oid sha256:a9e21c6ef5aeb835cee91a073ccd23a9fae1f3a1e6bc5c5dfdbe80ce9da62c51 +size 12496 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png index 77f9746562..11c187832f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0c1c64b768ec3491200e38b93ff558873d130a3a5c71b8bd5c3f4a49e3d9137 -size 37273 +oid sha256:04c3a63bd1d6b1c217946fd3ac359ebbedd33d39da663b489397a4a81744c75d +size 34365 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png index 2d5f0a7613..56d561434c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:421bd0bd141a6f6b38ce938c68cb21672b1d3c9cbb83e8cd44fe47bbc2488c3f -size 11168 +oid sha256:9fe856f45857f420a42562f0618473857eccd3f9a16c75df43157b881d473fd4 +size 8892 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png index fc5c101c2e..7c0ae7e7de 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c94c521f50f3b8d2fc5e03365e54e8582ee48d31a47146abfb34cc41972fd38b -size 15539 +oid sha256:fc84a1980024331be4906d1df8702ba16cfee553d18c3556d6c08597cc5c1a05 +size 13243 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png index e28f8c7a7c..60abaf54cf 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a8d98c3b6bd629ca6318fb7ac8d989960bf2bb952a9719a305568864d3bbb77 -size 38554 +oid sha256:538d13a4aba7f8d7e503852eed6ae2fe35c1a54002c33abe7ef763052d4c7ab8 +size 36254 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png index e73f79b22d..412d151f90 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00f111de4ffa63cfb0cc047ea017a2e740f52a9d0786fadf379937c12ae0e199 -size 10715 +oid sha256:674d480011c737b98adc471bca330182efc6eb31b1671e6bdd6aa54bfeaada9d +size 8616 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png index 5663da541d..89ea4bcb8b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2637ec2e1303341c473cb9380c18fb7c7c1558c1dbd39dcff8d71744e4c3f5c9 -size 14901 +oid sha256:3ffaef3911f91499e4905e0d041c8ff31a6343c2b71ea79f0b4ab6d6c89800d5 +size 12795 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png index 84aae36b0a..31cbcbc59b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57b058e64c8361a70492056a09d13e87c4e1b61fbfe785198282d035d27326ac -size 37041 +oid sha256:b2f88af6da9f62f2fe0fb17883387574eaeca5d1f65f651d970e13c22ca8079b +size 34947 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png index 9a840d69b9..90b2f3dad1 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6c04486280ce42c65486d4c532c20521f0d03419f1ab042dc61c0b5ff1060c2 -size 18242 +oid sha256:873e124ea0560fe577b93b5e55e959baf443eedcb07d6b229f0370621ee0dbf3 +size 20621 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png index 51e4bfa3fe..164a0f8780 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8d549d3ef133c621c9e223a3f02f351d8626aeb7b4b05180c2d76b85004cfb9 -size 39459 +oid sha256:99985546b0972ab8055881ad637fc5ca9200d07677fa7efae7f9ed346618630e +size 34788 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png index 7a9b4af687..d936b7b1a6 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b332b50ff448deea9bb3fc0db047b99743bb16c730a9ef3dbb203b7ca8982dc -size 17067 +oid sha256:f7c98ab469dd76beea196cfceff2ea9f72c9225f15b701ab7965b54cc9064dd8 +size 19505 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png index fbc749a35a..8cb9408394 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:742abcea0455c0628bbdd2e2cd3367d7a9431fe00cfa0c657a1a95e83c8a7cbf -size 37334 +oid sha256:c3a46a21218afd8a29928e317c5b5ee4bb48aa150bd33119c012076e094e3c75 +size 32994 From 16b348a2cedbe9d0400057853a3669880599038e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2024 11:23:53 +0100 Subject: [PATCH 165/203] Fix ordering of media. --- .../impl/gallery/MediaItemsPostProcessor.kt | 4 +- .../gallery/MediaItemsPostProcessorTest.kt | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt index 229f547c3b..dfaa39ae10 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt @@ -54,12 +54,12 @@ class MediaItemsPostProcessor @Inject constructor() { when (item) { is MediaItem.Image, is MediaItem.Video -> { - imageAndVideoItemsSubList.add(0, item) + imageAndVideoItemsSubList.add(item) } is MediaItem.Audio, is MediaItem.Voice, is MediaItem.File -> { - fileItemsSublist.add(0, item) + fileItemsSublist.add(item) } } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt index 9621413da6..934ed860af 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt @@ -85,10 +85,10 @@ class MediaItemsPostProcessorTest { expectedImageAndVideoItems = emptyList(), expectedFileItems = listOf( date1, - file1, - file2, - file3, audio1, + file3, + file2, + file1, ), ) } @@ -104,9 +104,9 @@ class MediaItemsPostProcessorTest { ), expectedImageAndVideoItems = listOf( date1, - image1, - image2, image3, + image2, + image1, ), expectedFileItems = emptyList(), ) @@ -124,13 +124,13 @@ class MediaItemsPostProcessorTest { ), expectedImageAndVideoItems = listOf( date1, - video1, image1, + video1, ), expectedFileItems = listOf( date1, - file1, audio1, + file1, ), ) } @@ -167,6 +167,11 @@ class MediaItemsPostProcessorTest { fun `process will handle complex case`() { test( mediaItems = listOf( + file3, + date3, + video3, + video2, + date2, voice3, voice2, voice1, @@ -177,33 +182,28 @@ class MediaItemsPostProcessorTest { image1, video1, date1, - file3, - date3, - video3, - video2, - date2, loading1, ), expectedImageAndVideoItems = listOf( - date1, - video1, - image1, date2, - video2, video3, + video2, + date1, + image1, + video1, loading1, ), expectedFileItems = listOf( - date1, - file1, - audio1, - audio2, - audio3, - voice1, - voice2, - voice3, date3, file3, + date1, + voice3, + voice2, + voice1, + audio3, + audio2, + audio1, + file1, loading1, ), ) From 8bd34ed70283809588d420976337779d8159c6b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2024 11:29:02 +0100 Subject: [PATCH 166/203] Gallery: Voice message: render duration when the playback is not started. --- .../libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt index 807be6c116..5864914dda 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -117,7 +117,7 @@ private fun VoiceInfoRow( } Spacer(Modifier.width(8.dp)) Text( - text = state.time, + text = if (state.progress > 0f) state.time else voice.duration ?: state.time, color = ElementTheme.colors.textSecondary, style = ElementTheme.typography.fontBodyMdMedium, maxLines = 1, From 67e70cc615d3bad093a6eb311ed8fd000ab34dbd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2024 11:55:57 +0100 Subject: [PATCH 167/203] Gallery: animate items. --- .../impl/gallery/MediaGalleryView.kt | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 9508fb42d3..7ce847a118 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -264,19 +264,26 @@ private fun MediaGalleryFilesList( LazyColumn( modifier = Modifier.fillMaxSize(), ) { - items(files) { item -> + items( + items = files, + key = { it.id() }, + contentType = { it::class.java }, + ) { item -> when (item) { is MediaItem.File -> FileItemView( + modifier = Modifier.animateItem(), file = item, onClick = { onItemClick(item) }, ) is MediaItem.Audio -> AudioItemView( + modifier = Modifier.animateItem(), audio = item, onClick = { onItemClick(item) }, ) is MediaItem.Voice -> { val presenter: Presenter = presenterFactories.rememberPresenter(item) VoiceItemView( + modifier = Modifier.animateItem(), state = presenter.present(), voice = item, onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, @@ -284,13 +291,19 @@ private fun MediaGalleryFilesList( onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, ) } - is MediaItem.DateSeparator -> DateItemView(item) + is MediaItem.DateSeparator -> DateItemView( + modifier = Modifier.animateItem(), + item = item + ) is MediaItem.Image, is MediaItem.Video -> { // Should not happen } is MediaItem.LoadingIndicator -> { - LoadingMoreIndicator(item.direction) + LoadingMoreIndicator( + modifier = Modifier.animateItem(), + direction = item.direction, + ) val latestEventSink by rememberUpdatedState(eventSink) LaunchedEffect(item.timestamp) { latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) @@ -316,7 +329,7 @@ private fun MediaGalleryImageGrid( verticalArrangement = Arrangement.spacedBy(4.dp), ) { items( - imagesAndVideos, + items = imagesAndVideos, span = { item -> when (item) { is MediaItem.LoadingIndicator, @@ -328,9 +341,10 @@ private fun MediaGalleryImageGrid( contentType = { it::class.java }, ) { item -> when (item) { - is MediaItem.DateSeparator -> { - DateItemView(item) - } + is MediaItem.DateSeparator -> DateItemView( + modifier = Modifier.animateItem(), + item = item, + ) is MediaItem.Audio -> { // Should not happen } @@ -340,26 +354,27 @@ private fun MediaGalleryImageGrid( is MediaItem.File -> { // Should not happen } - is MediaItem.Image -> { - ImageItemView( - image = item, - onClick = { onItemClick(item) }, - onLongClick = { - eventSink(MediaGalleryEvents.OpenInfo(item)) - }, - ) - } - is MediaItem.Video -> { - VideoItemView( - video = item, - onClick = { onItemClick(item) }, - onLongClick = { - eventSink(MediaGalleryEvents.OpenInfo(item)) - }, - ) - } + is MediaItem.Image -> ImageItemView( + modifier = Modifier.animateItem(), + image = item, + onClick = { onItemClick(item) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, + ) + is MediaItem.Video -> VideoItemView( + modifier = Modifier.animateItem(), + video = item, + onClick = { onItemClick(item) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, + ) is MediaItem.LoadingIndicator -> { - LoadingMoreIndicator(item.direction) + LoadingMoreIndicator( + modifier = Modifier.animateItem(), + direction = item.direction, + ) val latestEventSink by rememberUpdatedState(eventSink) LaunchedEffect(item.timestamp) { latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) From ac075f055836957b1df7ad470649911e4717873e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2024 12:00:37 +0100 Subject: [PATCH 168/203] Gallery: rework load more management --- .../impl/gallery/MediaGalleryView.kt | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 7ce847a118..7f7952f1a6 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -299,16 +299,11 @@ private fun MediaGalleryFilesList( is MediaItem.Video -> { // Should not happen } - is MediaItem.LoadingIndicator -> { - LoadingMoreIndicator( - modifier = Modifier.animateItem(), - direction = item.direction, - ) - val latestEventSink by rememberUpdatedState(eventSink) - LaunchedEffect(item.timestamp) { - latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) - } - } + is MediaItem.LoadingIndicator -> LoadingMoreIndicator( + modifier = Modifier.animateItem(), + item = item, + eventSink = eventSink, + ) } } } @@ -370,16 +365,11 @@ private fun MediaGalleryImageGrid( eventSink(MediaGalleryEvents.OpenInfo(item)) }, ) - is MediaItem.LoadingIndicator -> { - LoadingMoreIndicator( - modifier = Modifier.animateItem(), - direction = item.direction, - ) - val latestEventSink by rememberUpdatedState(eventSink) - LaunchedEffect(item.timestamp) { - latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) - } - } + is MediaItem.LoadingIndicator -> LoadingMoreIndicator( + modifier = Modifier.animateItem(), + item = item, + eventSink = eventSink, + ) } } } @@ -387,14 +377,15 @@ private fun MediaGalleryImageGrid( @Composable private fun LoadingMoreIndicator( - direction: Timeline.PaginationDirection, + item: MediaItem.LoadingIndicator, + eventSink: (MediaGalleryEvents) -> Unit, modifier: Modifier = Modifier ) { Box( modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { - when (direction) { + when (item.direction) { Timeline.PaginationDirection.FORWARDS -> { LinearProgressIndicator( modifier = Modifier @@ -410,6 +401,10 @@ private fun LoadingMoreIndicator( ) } } + val latestEventSink by rememberUpdatedState(eventSink) + LaunchedEffect(item.timestamp) { + latestEventSink(MediaGalleryEvents.LoadMore(item.direction)) + } } } From 2a141decd8d8b318ccbc611599a78e6a1f7bedf3 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 18 Dec 2024 11:28:48 +0000 Subject: [PATCH 169/203] Update screenshots --- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png | 4 ++-- ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png index 1bc8453928..e1069af79c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d5d02d56e050717a767bcf12d9da7c6ccf1207bedba0a7ec8b0451a668739d9 -size 11032 +oid sha256:9d0b3f44a9a0ed9ab16192b23ccf95b7d34abccd025bb4a7ccd8ebb7a9379965 +size 10824 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png index ee16fdae08..3e5ffd3185 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:979326f2383611db2b1ffa8a2a9d0f2a4fb3296fa57a1240daa97ccf36d886e3 -size 10279 +oid sha256:73fecb12c33892a9bea41e5a4bbbf4db43990e44b2a36b96d03d7a81046c8f92 +size 10076 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png index aa1aebbac8..7d108d9701 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b799d6bed5f1648f1a5196a1c4ccc1a8f8ed49f3eeb5971bd7c342518a1c8ec3 -size 10852 +oid sha256:8f09ea644568c7ab55f0cfc3b8148d8e7821313852cc3fcd78dca2b473a07555 +size 10871 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png index 267ad90715..543d484800 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28ead44aa94f2403c4273f38185f0eb21fd9645918829298aee52f9c8061ac95 -size 13003 +oid sha256:a45e80573b7db0c47cbe9048c1bb95d66e4b8437dd4816cf6cb3548305549715 +size 13017 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png index 84f6b5081c..80ea9026f2 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae77a7b1bb730842ff0f9608900188d5da78717476f10fb8b640a9c413bf4249 -size 38548 +oid sha256:339a6ae86094fb3d0b99d6ea3b23bed14adf7bbe90bc55613d6ece3356f7be16 +size 38566 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png index 086d96caa6..53ec45dc2a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88df6cf8d0a0ca3ae5cdc0e67a21e0c2da793cc88d65fc780830678f5d5f2f24 -size 9218 +oid sha256:19b6feb0228df79d7f6ec505a8b073852e4400366027d93feb61c09186494ac6 +size 9228 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png index 3aa6a4b56d..6f74347dfc 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dd8d2b4e7d18e3091ac8aca92b8ceb9db6ae15bd228b7a6ce268ed2f7852a53 -size 10113 +oid sha256:b7fdc491e73aa3fe1afb52ac6a22b3ced09c73573dc175c415d116293c04c95d +size 10129 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png index 23ca97050b..bad0c59624 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c711dea48e04cc05c243541ad48e6ed576b5f225ceecb515d49d08c2b2db5e0f -size 12289 +oid sha256:f816378db978ff00cd722fd44b8219b6181d9cc6fbf4d3b31fea33430e37580e +size 12309 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png index f9cac2cee7..24de50d08d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64f8121851420451628e215393f0835597ea82a67fb2365da2454d52f375bde2 -size 36802 +oid sha256:a0b97a65622307f289695d3a19aa0957fe739d35070b1f0cd972f7ff51987a7d +size 36824 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png index 27ac795939..2c81f6a60f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80885e7fa1a8b84e1629f64eb4890079d8f4b556c91b415716616ed2d27e5924 -size 8589 +oid sha256:8b71ca3fcc1f94d89264f3d5c5751f46681014b662bcf7d910263dfb23178e0f +size 8623 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png index 164a0f8780..418b871733 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99985546b0972ab8055881ad637fc5ca9200d07677fa7efae7f9ed346618630e -size 34788 +oid sha256:c8b23c6c8f47f7e49cdb56098408873aa9d3f02af22cae01add580f0e623678d +size 34814 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png index 8cb9408394..4c08d0a7f4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3a46a21218afd8a29928e317c5b5ee4bb48aa150bd33119c012076e094e3c75 -size 32994 +oid sha256:e361247dd137a828f1170fc1497ace365e04bb551edcd4984c26b15c26fe65f1 +size 33011 From 34b81435fcb41f5346db4ee63d9d4f6bc7af490c Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2024 15:21:19 +0100 Subject: [PATCH 170/203] test(settings) : try to fix flakiness --- .../developer/DeveloperSettingsPresenter.kt | 7 +- .../impl/developer/DeveloperSettingsState.kt | 3 +- .../DeveloperSettingsStateProvider.kt | 5 +- .../DeveloperSettingsPresenterTest.kt | 226 +++++++++--------- 4 files changed, 119 insertions(+), 122 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index 844474e1e1..c4896ca5dd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -23,6 +23,7 @@ import io.element.android.features.logout.api.LogoutUseCase import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState +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.architecture.runCatchingUpdatingState @@ -63,7 +64,7 @@ class DeveloperSettingsPresenter @Inject constructor( mutableStateOf>(AsyncData.Uninitialized) } val clearCacheAction = remember { - mutableStateOf>(AsyncData.Uninitialized) + mutableStateOf>(AsyncAction.Uninitialized) } val customElementCallBaseUrl by appPreferencesStore .getCustomElementCallBaseUrlFlow() @@ -94,7 +95,7 @@ class DeveloperSettingsPresenter @Inject constructor( val featureUiModels = createUiModels(features, enabledFeatures) val coroutineScope = rememberCoroutineScope() // Compute cache size each time the clear cache action value is changed - LaunchedEffect(clearCacheAction.value) { + LaunchedEffect(clearCacheAction.value.isSuccess()) { computeCacheSize(cacheSize) } @@ -180,7 +181,7 @@ class DeveloperSettingsPresenter @Inject constructor( }.runCatchingUpdatingState(cacheSize) } - private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { + private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { suspend { clearCacheUseCase() }.runCatchingUpdatingState(clearCacheAction) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index e4c8641197..7c2b9438ae 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -8,6 +8,7 @@ package io.element.android.features.preferences.impl.developer import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import kotlinx.collections.immutable.ImmutableList @@ -16,7 +17,7 @@ data class DeveloperSettingsState( val features: ImmutableList, val cacheSize: AsyncData, val rageshakeState: RageshakePreferencesState, - val clearCacheAction: AsyncData, + val clearCacheAction: AsyncAction, val customElementCallBaseUrlState: CustomElementCallBaseUrlState, val isSimpleSlidingSyncEnabled: Boolean, val hideImagesAndVideos: Boolean, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index 601ed2ee7a..8742e4746d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.preferences.impl.developer import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.aFeatureUiModelList @@ -17,7 +18,7 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider = AsyncData.Uninitialized, + clearCacheAction: AsyncAction = AsyncAction.Uninitialized, customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), isSimplifiedSlidingSyncEnabled: Boolean = false, hideImagesAndVideos: Boolean = false, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 7e7a1f56c2..a31d1c032a 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -5,17 +5,17 @@ * Please see LICENSE in the repository root for full details. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package io.element.android.features.preferences.impl.developer -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.ElementCallConfig import io.element.android.features.logout.test.FakeLogoutUseCase import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType @@ -24,10 +24,11 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule -import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.lambda.lambdaRecorder -import kotlinx.coroutines.CompletableDeferred +import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -37,37 +38,29 @@ class DeveloperSettingsPresenterTest { val warmUpRule = WarmUpRule() @Test - fun `present - ensures initial state is correct`() = runTest { + fun `present - ensures initial states are correct`() = runTest { val presenter = createDeveloperSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.features).isEmpty() - assertThat(initialState.clearCacheAction).isEqualTo(AsyncData.Uninitialized) - assertThat(initialState.cacheSize).isEqualTo(AsyncData.Uninitialized) - assertThat(initialState.customElementCallBaseUrlState).isNotNull() - assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull() - assertThat(initialState.isSimpleSlidingSyncEnabled).isFalse() - assertThat(initialState.hideImagesAndVideos).isFalse() - val loadedState = awaitItem() - assertThat(loadedState.rageshakeState.isEnabled).isFalse() - assertThat(loadedState.rageshakeState.isSupported).isTrue() - assertThat(loadedState.rageshakeState.sensitivity).isEqualTo(0.3f) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `present - ensures feature list is loaded`() = runTest { - val presenter = createDeveloperSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val state = awaitLastSequentialItem() - val numberOfModifiableFeatureFlags = FeatureFlags.entries.count { it.isFinished.not() } - assertThat(state.features).hasSize(numberOfModifiableFeatureFlags) - cancelAndIgnoreRemainingEvents() + presenter.test { + awaitItem().also { state -> + assertThat(state.features).isEmpty() + assertThat(state.clearCacheAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(state.cacheSize).isEqualTo(AsyncData.Uninitialized) + assertThat(state.customElementCallBaseUrlState).isNotNull() + assertThat(state.customElementCallBaseUrlState.baseUrl).isNull() + assertThat(state.isSimpleSlidingSyncEnabled).isFalse() + assertThat(state.hideImagesAndVideos).isFalse() + assertThat(state.rageshakeState.isEnabled).isFalse() + assertThat(state.rageshakeState.isSupported).isTrue() + assertThat(state.rageshakeState.sensitivity).isEqualTo(0.3f) + } + awaitItem().also { state -> + assertThat(state.features).isNotEmpty() + val numberOfModifiableFeatureFlags = FeatureFlags.entries.count { it.isFinished.not() } + assertThat(state.features).hasSize(numberOfModifiableFeatureFlags) + } + awaitItem().also { state -> + assertThat(state.cacheSize).isInstanceOf(AsyncData.Success::class.java) + } } } @@ -75,30 +68,28 @@ class DeveloperSettingsPresenterTest { fun `present - ensures Room directory search is not present on release Google Play builds`() = runTest { val buildMeta = aBuildMeta(buildType = BuildType.RELEASE, flavorDescription = "GooglePlay") val presenter = createDeveloperSettingsPresenter(buildMeta = buildMeta) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val state = awaitLastSequentialItem() - assertThat(state.features).doesNotContain(FeatureFlags.RoomDirectorySearch) - cancelAndIgnoreRemainingEvents() + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.features).doesNotContain(FeatureFlags.RoomDirectorySearch) + } } } @Test fun `present - ensures state is updated when enabled feature event is triggered`() = runTest { val presenter = createDeveloperSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - val stateBeforeEvent = awaitItem() - val featureBeforeEvent = stateBeforeEvent.features.first() - stateBeforeEvent.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(featureBeforeEvent, !featureBeforeEvent.isEnabled)) - val stateAfterEvent = awaitItem() - val featureAfterEvent = stateAfterEvent.features.first() - assertThat(featureBeforeEvent.key).isEqualTo(featureAfterEvent.key) - assertThat(featureBeforeEvent.isEnabled).isNotEqualTo(featureAfterEvent.isEnabled) - cancelAndIgnoreRemainingEvents() + presenter.test { + skipItems(2) + awaitItem().also { state -> + val feature = state.features.first() + state.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(feature, !feature.isEnabled)) + } + awaitItem().also { state -> + val feature = state.features.first() + assertThat(feature.isEnabled).isTrue() + assertThat(feature.key).isEqualTo(feature.key) + } } } @@ -106,19 +97,25 @@ class DeveloperSettingsPresenterTest { fun `present - clear cache`() = runTest { val clearCacheUseCase = FakeClearCacheUseCase() val presenter = createDeveloperSettingsPresenter(clearCacheUseCase = clearCacheUseCase) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - val initialState = awaitItem() + presenter.test { + skipItems(2) assertThat(clearCacheUseCase.executeHasBeenCalled).isFalse() - initialState.eventSink(DeveloperSettingsEvents.ClearCache) - val stateAfterEvent = awaitItem() - assertThat(stateAfterEvent.clearCacheAction).isInstanceOf(AsyncData.Loading::class.java) - skipItems(1) - assertThat(awaitItem().clearCacheAction).isInstanceOf(AsyncData.Success::class.java) - assertThat(clearCacheUseCase.executeHasBeenCalled).isTrue() - cancelAndIgnoreRemainingEvents() + awaitItem().also { state -> + state.eventSink(DeveloperSettingsEvents.ClearCache) + } + awaitItem().also { state -> + assertThat(state.clearCacheAction).isInstanceOf(AsyncAction.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.clearCacheAction).isInstanceOf(AsyncAction.Success::class.java) + assertThat(clearCacheUseCase.executeHasBeenCalled).isTrue() + } + awaitItem().also { state -> + assertThat(state.cacheSize).isInstanceOf(AsyncData.Loading::class.java) + } + awaitItem().also { state -> + assertThat(state.cacheSize).isInstanceOf(AsyncData.Success::class.java) + } } } @@ -126,26 +123,25 @@ class DeveloperSettingsPresenterTest { fun `present - custom element call base url`() = runTest { val preferencesStore = InMemoryAppPreferencesStore() val presenter = createDeveloperSettingsPresenter(preferencesStore = preferencesStore) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - val initialState = awaitItem() - assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull() - initialState.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.ahoy")) - val updatedItem = awaitItem() - assertThat(updatedItem.customElementCallBaseUrlState.baseUrl).isEqualTo("https://call.element.ahoy") - assertThat(updatedItem.customElementCallBaseUrlState.defaultUrl).isEqualTo(ElementCallConfig.DEFAULT_BASE_URL) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.customElementCallBaseUrlState.baseUrl).isNull() + state.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.ahoy")) + } + awaitItem().also { state -> + assertThat(state.customElementCallBaseUrlState.baseUrl).isEqualTo("https://call.element.ahoy") + assertThat(state.customElementCallBaseUrlState.defaultUrl).isEqualTo(ElementCallConfig.DEFAULT_BASE_URL) + } } } @Test fun `present - custom element call base url validator needs at least an HTTP scheme and host`() = runTest { val presenter = createDeveloperSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val urlValidator = awaitLastSequentialItem().customElementCallBaseUrlState.validator + presenter.test { + skipItems(2) + val urlValidator = awaitItem().customElementCallBaseUrlState.validator assertThat(urlValidator("")).isTrue() // We allow empty string to clear the value and use the default one assertThat(urlValidator("test")).isFalse() assertThat(urlValidator("http://")).isFalse() @@ -156,35 +152,29 @@ class DeveloperSettingsPresenterTest { @Test fun `present - toggling simplified sliding sync changes the preferences and logs out the user`() = runTest { - val latch1 = CompletableDeferred() - val latch2 = CompletableDeferred() - val logoutCallRecorder = lambdaRecorder { - if (latch1.isActive) { - latch1.complete(Unit) - } else { - latch2.complete(Unit) - } - "" - } + val logoutCallRecorder = lambdaRecorder { "" } val logoutUseCase = FakeLogoutUseCase(logoutLambda = logoutCallRecorder) val preferences = InMemoryAppPreferencesStore() val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences, logoutUseCase = logoutUseCase) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitLastSequentialItem() - assertThat(initialState.isSimpleSlidingSyncEnabled).isFalse() - - initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true)) - assertThat(awaitItem().isSimpleSlidingSyncEnabled).isTrue() - assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue() - latch1.await() - logoutCallRecorder.assertions().isCalledOnce() - initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false)) - assertThat(awaitItem().isSimpleSlidingSyncEnabled).isFalse() - assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse() - latch2.await() - logoutCallRecorder.assertions().isCalledExactly(times = 2) + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.isSimpleSlidingSyncEnabled).isFalse() + state.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true)) + } + awaitItem().also { state -> + assertThat(state.isSimpleSlidingSyncEnabled).isTrue() + assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue() + advanceUntilIdle() + logoutCallRecorder.assertions().isCalledOnce() + state.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false)) + } + awaitItem().also { state -> + assertThat(state.isSimpleSlidingSyncEnabled).isFalse() + assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse() + advanceUntilIdle() + logoutCallRecorder.assertions().isCalledExactly(2) + } } } @@ -192,17 +182,21 @@ class DeveloperSettingsPresenterTest { fun `present - toggling hide image and video`() = runTest { val preferences = InMemoryAppPreferencesStore() val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitLastSequentialItem() - assertThat(initialState.hideImagesAndVideos).isFalse() - initialState.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(true)) - assertThat(awaitItem().hideImagesAndVideos).isTrue() - assertThat(preferences.doesHideImagesAndVideosFlow().first()).isTrue() - initialState.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(false)) - assertThat(awaitItem().hideImagesAndVideos).isFalse() - assertThat(preferences.doesHideImagesAndVideosFlow().first()).isFalse() + presenter.test { + skipItems(2) + awaitItem().also { state -> + assertThat(state.hideImagesAndVideos).isFalse() + state.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(true)) + } + awaitItem().also { state -> + assertThat(state.hideImagesAndVideos).isTrue() + assertThat(preferences.doesHideImagesAndVideosFlow().first()).isTrue() + state.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(false)) + } + awaitItem().also { state -> + assertThat(state.hideImagesAndVideos).isFalse() + assertThat(preferences.doesHideImagesAndVideosFlow().first()).isFalse() + } } } From 0ab0660a625fc9136c8f9edc9e3caf3608c6ccd8 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 18 Dec 2024 14:35:53 +0000 Subject: [PATCH 171/203] Update screenshots --- ...ne.components.event_TimelineItemEncryptedView_Day_5_en.png | 4 ++-- ...ne.components.event_TimelineItemEncryptedView_Day_6_en.png | 4 ++-- ...ne.components.event_TimelineItemEncryptedView_Day_7_en.png | 4 ++-- ...ne.components.event_TimelineItemEncryptedView_Day_8_en.png | 3 +++ ....components.event_TimelineItemEncryptedView_Night_5_en.png | 4 ++-- ....components.event_TimelineItemEncryptedView_Night_6_en.png | 4 ++-- ....components.event_TimelineItemEncryptedView_Night_7_en.png | 4 ++-- ....components.event_TimelineItemEncryptedView_Night_8_en.png | 3 +++ 8 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en.png index 8f51f4d170..7fec52751b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aba63b0f223f8480d40e6c9c48ce44a1ae1a8bdcc3d101030b9a0bd5f1e9ebee -size 23773 +oid sha256:dc3b043dcc28ab54ea0564e355b56d39ff79acd5180c9a34f727cb6e113b2611 +size 14473 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en.png index cff8f7e35f..8f51f4d170 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5c8494ebb4ceaf3a31b661aec47f8a33afeb7aab1457483b7009099a6b56f86 -size 8960 +oid sha256:aba63b0f223f8480d40e6c9c48ce44a1ae1a8bdcc3d101030b9a0bd5f1e9ebee +size 23773 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en.png index afa1dd9b61..cff8f7e35f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fee2af8462597d58274b7168c5cfe1f6d6b0909b048adc0f4fc5b3c12a90b859 -size 8861 +oid sha256:d5c8494ebb4ceaf3a31b661aec47f8a33afeb7aab1457483b7009099a6b56f86 +size 8960 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en.png new file mode 100644 index 0000000000..afa1dd9b61 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fee2af8462597d58274b7168c5cfe1f6d6b0909b048adc0f4fc5b3c12a90b859 +size 8861 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en.png index 8561e18151..3ab5d1cac8 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d0a687a259fafe830560b762a915e478d680dd7c34a295d72e82fc7c427a815 -size 23403 +oid sha256:c857b7836bc9369cfc950b774479dafcb717d6acc40cf7d8b9b34b99974573db +size 14377 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en.png index a851ce84e5..8561e18151 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dda55a19381d9f51a270afa644894f909d8c479be0a5e57b977393c9f1253683 -size 8960 +oid sha256:5d0a687a259fafe830560b762a915e478d680dd7c34a295d72e82fc7c427a815 +size 23403 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en.png index 212726df37..a851ce84e5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4f48cd7dc7e2bbf7363d1127e46721c25bb8dd887927dbcffe525f5bb5bae01 -size 8781 +oid sha256:dda55a19381d9f51a270afa644894f909d8c479be0a5e57b977393c9f1253683 +size 8960 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en.png new file mode 100644 index 0000000000..212726df37 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4f48cd7dc7e2bbf7363d1127e46721c25bb8dd887927dbcffe525f5bb5bae01 +size 8781 From c240b5da1a01f9bb0433395ab6b06ee3ec6a9154 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2024 16:04:48 +0100 Subject: [PATCH 172/203] Format file (no other change) --- .../impl/setup/views/RecoveryKeyView.kt | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index 7d03eb7fba..496c83d113 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -90,14 +90,14 @@ private fun RecoveryKeyStaticContent( ) { Row( modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .background( - color = ElementTheme.colors.bgSubtleSecondary, - shape = RoundedCornerShape(14.dp) - ) - .clickableIfNotNull(onClick) - .padding(horizontal = 16.dp, vertical = 16.dp), + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .background( + color = ElementTheme.colors.bgSubtleSecondary, + shape = RoundedCornerShape(14.dp) + ) + .clickableIfNotNull(onClick) + .padding(horizontal = 16.dp, vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { if (state.formattedRecoveryKey != null) { @@ -115,15 +115,15 @@ private fun RecoveryKeyStaticContent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier - .fillMaxWidth() - .padding(vertical = 11.dp) + .fillMaxWidth() + .padding(vertical = 11.dp) ) { if (state.inProgress) { CircularProgressIndicator( modifier = Modifier - .progressSemantics() - .padding(end = 8.dp) - .size(16.dp), + .progressSemantics() + .padding(end = 8.dp) + .size(16.dp), color = ElementTheme.colors.textPrimary, strokeWidth = 1.5.dp, ) @@ -160,12 +160,12 @@ private fun RecoveryKeyFormContent( } TextField( modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.recoveryKey) - .autofill( - autofillTypes = listOf(AutofillType.Password), - onFill = { onChange(it) }, - ), + .fillMaxWidth() + .testTag(TestTags.recoveryKey) + .autofill( + autofillTypes = listOf(AutofillType.Password), + onFill = { onChange(it) }, + ), minLines = 2, value = state.formattedRecoveryKey.orEmpty(), onValueChange = onChange, From 4d89c76e7b50f791c386e7739799496a4eba643a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2024 16:07:02 +0100 Subject: [PATCH 173/203] Update recovery key UI. Closes #4063 --- .../impl/setup/views/RecoveryKeyView.kt | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index 496c83d113..0cb87cb762 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -9,6 +9,7 @@ package io.element.android.features.securebackup.impl.setup.views import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -24,8 +25,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.autofill.AutofillType +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.VisualTransformation @@ -88,35 +91,31 @@ private fun RecoveryKeyStaticContent( state: RecoveryKeyViewState, onClick: (() -> Unit)?, ) { - Row( + Box( modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) + .clip(RoundedCornerShape(10.dp)) .background( color = ElementTheme.colors.bgSubtleSecondary, - shape = RoundedCornerShape(14.dp) ) .clickableIfNotNull(onClick) - .padding(horizontal = 16.dp, vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .padding(horizontal = 16.dp, vertical = 11.dp), + contentAlignment = Alignment.Center, ) { if (state.formattedRecoveryKey != null) { - Text( - text = state.formattedRecoveryKey, - modifier = Modifier.weight(1f), - ) - Icon( - imageVector = CompoundIcons.Copy(), - contentDescription = stringResource(id = CommonStrings.action_copy), - tint = ElementTheme.colors.iconSecondary, + RecoveryKeyWithCopy( + recoveryKey = state.formattedRecoveryKey, + alpha = 1f, ) } else { + // Use an invisible recovery key to ensure that the Box size is correct. + val fakeFormattedRecoveryKey = List(12) { "XXXX" }.joinToString(" ") + RecoveryKeyWithCopy( + recoveryKey = fakeFormattedRecoveryKey, + alpha = 0f, + ) Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 11.dp) ) { if (state.inProgress) { CircularProgressIndicator( @@ -144,6 +143,31 @@ private fun RecoveryKeyStaticContent( } } +@Composable +private fun RecoveryKeyWithCopy( + recoveryKey: String, + alpha: Float, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .alpha(alpha), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = recoveryKey, + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodyLgRegular.copy(fontFamily = FontFamily.Monospace), + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = CompoundIcons.Copy(), + contentDescription = stringResource(id = CommonStrings.action_copy), + tint = ElementTheme.colors.iconSecondary, + ) + } +} + @OptIn(ExperimentalComposeUiApi::class) @Composable private fun RecoveryKeyFormContent( From ad2496c615164276c3bf60ee219f3aa4f68f50ba Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 18 Dec 2024 15:21:34 +0000 Subject: [PATCH 174/203] Update screenshots --- ...securebackup.impl.setup.views_RecoveryKeyView_Day_0_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_1_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_2_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_3_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_4_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_5_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_6_en.png | 4 ++-- ...securebackup.impl.setup.views_RecoveryKeyView_Day_7_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_0_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_1_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_2_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_3_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_4_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_5_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_6_en.png | 4 ++-- ...curebackup.impl.setup.views_RecoveryKeyView_Night_7_en.png | 4 ++-- ...backup.impl.setup_SecureBackupSetupViewChange_Day_0_en.png | 4 ++-- ...backup.impl.setup_SecureBackupSetupViewChange_Day_1_en.png | 4 ++-- ...backup.impl.setup_SecureBackupSetupViewChange_Day_2_en.png | 4 ++-- ...backup.impl.setup_SecureBackupSetupViewChange_Day_3_en.png | 4 ++-- ...backup.impl.setup_SecureBackupSetupViewChange_Day_4_en.png | 4 ++-- ...ckup.impl.setup_SecureBackupSetupViewChange_Night_0_en.png | 4 ++-- ...ckup.impl.setup_SecureBackupSetupViewChange_Night_1_en.png | 4 ++-- ...ckup.impl.setup_SecureBackupSetupViewChange_Night_2_en.png | 4 ++-- ...ckup.impl.setup_SecureBackupSetupViewChange_Night_3_en.png | 4 ++-- ...ckup.impl.setup_SecureBackupSetupViewChange_Night_4_en.png | 4 ++-- ...securebackup.impl.setup_SecureBackupSetupView_Day_0_en.png | 4 ++-- ...securebackup.impl.setup_SecureBackupSetupView_Day_1_en.png | 4 ++-- ...securebackup.impl.setup_SecureBackupSetupView_Day_2_en.png | 4 ++-- ...securebackup.impl.setup_SecureBackupSetupView_Day_3_en.png | 4 ++-- ...securebackup.impl.setup_SecureBackupSetupView_Day_4_en.png | 4 ++-- ...curebackup.impl.setup_SecureBackupSetupView_Night_0_en.png | 4 ++-- ...curebackup.impl.setup_SecureBackupSetupView_Night_1_en.png | 4 ++-- ...curebackup.impl.setup_SecureBackupSetupView_Night_2_en.png | 4 ++-- ...curebackup.impl.setup_SecureBackupSetupView_Night_3_en.png | 4 ++-- ...curebackup.impl.setup_SecureBackupSetupView_Night_4_en.png | 4 ++-- 36 files changed, 72 insertions(+), 72 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en.png index 9939b2f5fc..7d28a0e898 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bc6ca9fdca5e61bcbc490eda3faa9931212a1fcf8836b756c629d64f71dfb44 -size 16285 +oid sha256:6ed28ec4c611e0c6b01e4f9742bbd579593103859775d41118bb11fef0586b11 +size 16140 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en.png index 64b1a49349..0f84095ec7 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:515442ef3d33d93b172ce0db8b0f2b06be2eee7137933128cf414223d8161b33 -size 14044 +oid sha256:f3e9406dc7e490469cfd6178c66f12af16b89c68241b939db81bb160232a1c61 +size 13938 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en.png index e3c391e6ba..5206ffd141 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea7920d1f69056b78471a4f789a02282d6929c72d542e4863affffa1cd99bbd1 -size 22611 +oid sha256:8bfa6e9b48e6ac6f5079127f6b978a5c144941d79ba347f91251ff0e6f1e3ae1 +size 21680 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en.png index e3c391e6ba..5206ffd141 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea7920d1f69056b78471a4f789a02282d6929c72d542e4863affffa1cd99bbd1 -size 22611 +oid sha256:8bfa6e9b48e6ac6f5079127f6b978a5c144941d79ba347f91251ff0e6f1e3ae1 +size 21680 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en.png index 1ea727091e..f3c7ad9543 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d53449b4f515a15207ed194469d56266f77c7208856c8481bba4bc89d681ec3 -size 16472 +oid sha256:6715956a1ffc033a907da275bd69397b423b7819346355933278f5f1187b20fa +size 16380 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en.png index 64b1a49349..0f84095ec7 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:515442ef3d33d93b172ce0db8b0f2b06be2eee7137933128cf414223d8161b33 -size 14044 +oid sha256:f3e9406dc7e490469cfd6178c66f12af16b89c68241b939db81bb160232a1c61 +size 13938 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en.png index e3c391e6ba..5206ffd141 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea7920d1f69056b78471a4f789a02282d6929c72d542e4863affffa1cd99bbd1 -size 22611 +oid sha256:8bfa6e9b48e6ac6f5079127f6b978a5c144941d79ba347f91251ff0e6f1e3ae1 +size 21680 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en.png index e3c391e6ba..5206ffd141 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea7920d1f69056b78471a4f789a02282d6929c72d542e4863affffa1cd99bbd1 -size 22611 +oid sha256:8bfa6e9b48e6ac6f5079127f6b978a5c144941d79ba347f91251ff0e6f1e3ae1 +size 21680 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en.png index ec9abfb944..b37ea9738e 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:484f2b3e592efbc3913cac20ce6cc7df3fe9424f0b40a16faaa870858756d847 -size 15750 +oid sha256:97a889bc71a579978cf4656a296221c4a929914f32dc0d74cd4faa07379ae519 +size 15487 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en.png index 7eaaa4292b..9a235d85c5 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33d7d1c4a7ff4e1fd82963969721b23326f7a86c4f716b877b5f65455c564902 -size 13572 +oid sha256:9122040db16a68daf4b88e0ef8239fcb28e1a858f6ab1bc0b352926ab7819caf +size 13427 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en.png index ad35d903c9..5eab82d590 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c11a80a52d977e27f560c7d9b80e87328ebf14406647c62c05a4c6c962e2d9 -size 21978 +oid sha256:39769a4a1de0cb8962fc14de88f8d4e8988ef36419fb18ebb5524f08f6eda001 +size 20811 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en.png index ad35d903c9..5eab82d590 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c11a80a52d977e27f560c7d9b80e87328ebf14406647c62c05a4c6c962e2d9 -size 21978 +oid sha256:39769a4a1de0cb8962fc14de88f8d4e8988ef36419fb18ebb5524f08f6eda001 +size 20811 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en.png index 2c27fddc9a..b248cd5960 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:597682e1dc18488401665fe4404fc98f4d76a6587cd142d9e41e826138affc32 -size 15961 +oid sha256:5f7f2901b56ac490efd3c0dc3d59f5fcffdb33f063e82060820bb0b352cba968 +size 15827 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en.png index 7eaaa4292b..9a235d85c5 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33d7d1c4a7ff4e1fd82963969721b23326f7a86c4f716b877b5f65455c564902 -size 13572 +oid sha256:9122040db16a68daf4b88e0ef8239fcb28e1a858f6ab1bc0b352926ab7819caf +size 13427 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en.png index ad35d903c9..5eab82d590 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c11a80a52d977e27f560c7d9b80e87328ebf14406647c62c05a4c6c962e2d9 -size 21978 +oid sha256:39769a4a1de0cb8962fc14de88f8d4e8988ef36419fb18ebb5524f08f6eda001 +size 20811 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en.png index ad35d903c9..5eab82d590 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c11a80a52d977e27f560c7d9b80e87328ebf14406647c62c05a4c6c962e2d9 -size 21978 +oid sha256:39769a4a1de0cb8962fc14de88f8d4e8988ef36419fb18ebb5524f08f6eda001 +size 20811 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en.png index 5f5470c52c..ec2a773363 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:226b0e80f8b0b39c0413bc72ad145b6edebb71f58bb1ad7d41dda49776b09f4d -size 42643 +oid sha256:2299427647ef9673a68b5235bc3503007874d0570324a17d3c3ee40a5e581af6 +size 42671 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en.png index 15916a2f9c..7e94c40141 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ac77b3456739f0abae3d05b03b35a17ff7b7d7a625f1bebd9b36b6c759e7e7d -size 40367 +oid sha256:0a011821df73e73ac3ec15caef2c6cff228cf0e5567271164b655aedbd4755bf +size 40228 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en.png index c8d4132209..7cbb9c3fbe 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a4aefb49b16a1bed3547d1259e3159590ef6428a73687b8b4bfdc9a9eea45e5 -size 57599 +oid sha256:b71e77d68b21262c0dc0731a4bd4a62053ab94f5ae3ef7ac27b29154cfe7556e +size 56493 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en.png index c8d4132209..7cbb9c3fbe 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a4aefb49b16a1bed3547d1259e3159590ef6428a73687b8b4bfdc9a9eea45e5 -size 57599 +oid sha256:b71e77d68b21262c0dc0731a4bd4a62053ab94f5ae3ef7ac27b29154cfe7556e +size 56493 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en.png index cb557befe2..df1b3a3548 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db5e61758f4decd9422be2ad11e595510cffb2433d42ec97923e4a7e3dc2610e -size 51310 +oid sha256:222f29e2c22cc2381a0c533fb237d61cf6a3537ed3f00f29ec7931b9933d841f +size 49987 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en.png index 6dc2165b3d..01d621ac7f 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:459f4185a7c9eaaedd179fdfa117fd730238277995c6520a7e0eba084762a640 -size 41368 +oid sha256:1220f0136cf5dd3558dcd08ff7f820fac4395b4b35fd5b144faa65ecf01ff620 +size 41305 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en.png index a62217d12e..e69ddadd5a 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:029a76515d629b66668f174ff55d5bc26be19e1a2d708f282b2de6213b1575cf -size 39090 +oid sha256:b538d064bf3efda30bc981e24390e65918e3ae570f13b1329bcc17e55e4216c7 +size 39020 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en.png index ff6109ec98..cceb41304b 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5b7a4b6d22c15e79ab8e547f10cf5298964d6de4ac5f6b10e0fd12380322350 -size 56012 +oid sha256:52cc26ca7c7db86be00075dcdd39ac364339c0b462edc529c21f17187fb878c2 +size 54642 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en.png index ff6109ec98..cceb41304b 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5b7a4b6d22c15e79ab8e547f10cf5298964d6de4ac5f6b10e0fd12380322350 -size 56012 +oid sha256:52cc26ca7c7db86be00075dcdd39ac364339c0b462edc529c21f17187fb878c2 +size 54642 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en.png index 3c9ab417db..ae0e7d8078 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8a88518bc44f9554dbe9db6c86c51f26f54aff7172dfa1a011e8c10b0f8dac6 -size 48564 +oid sha256:a0cf9ffc7f76bd53c4b94d02f0507994972993ae50ce022db72542a283f780b5 +size 47115 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en.png index bd4be5fcb3..3b466b3aa0 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaf674ca4ba02eaaff9f07fdc6374c5b195e7b9c8eb2e03b875e2690a236a975 -size 44115 +oid sha256:86dcedd7d1fb23ba10eb8b3e1c5d7f71331334cb98fb88bfa2a1fef0fffb3783 +size 44053 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en.png index 0cb67f883f..5d0f2b097c 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a96a86966223eb3ae22be485d8a7faf7607f0f9de8462419ad13772c55a8a76 -size 41939 +oid sha256:bcdb036e2a7581e69d3b658d16bb26e98aa27156fbafd17e9da15fd103158299 +size 41819 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en.png index c8d4132209..7cbb9c3fbe 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a4aefb49b16a1bed3547d1259e3159590ef6428a73687b8b4bfdc9a9eea45e5 -size 57599 +oid sha256:b71e77d68b21262c0dc0731a4bd4a62053ab94f5ae3ef7ac27b29154cfe7556e +size 56493 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en.png index c8d4132209..7cbb9c3fbe 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a4aefb49b16a1bed3547d1259e3159590ef6428a73687b8b4bfdc9a9eea45e5 -size 57599 +oid sha256:b71e77d68b21262c0dc0731a4bd4a62053ab94f5ae3ef7ac27b29154cfe7556e +size 56493 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en.png index cb557befe2..df1b3a3548 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db5e61758f4decd9422be2ad11e595510cffb2433d42ec97923e4a7e3dc2610e -size 51310 +oid sha256:222f29e2c22cc2381a0c533fb237d61cf6a3537ed3f00f29ec7931b9933d841f +size 49987 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en.png index 4c5e8940c8..cd6dda7370 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19b68c0545f4b087d19f5f54f3bb104077dcde4622cd5a51803c34ff8111db67 -size 42837 +oid sha256:64777bafec80f98b0b1f9b7e05ede31036032b97808d52911ddd9045d83dae58 +size 42794 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en.png index af5a72d802..d6a004275e 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1fae355b34c7b34026bbf932f3e6ba44483e8c39d2d1d3de45f638fd370024f -size 40729 +oid sha256:0d98ab21d3ae8099bd4f458c6422cf8d91a7405b45b1a41cc91fef78ccaf9475 +size 40658 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en.png index ff6109ec98..cceb41304b 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5b7a4b6d22c15e79ab8e547f10cf5298964d6de4ac5f6b10e0fd12380322350 -size 56012 +oid sha256:52cc26ca7c7db86be00075dcdd39ac364339c0b462edc529c21f17187fb878c2 +size 54642 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en.png index ff6109ec98..cceb41304b 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5b7a4b6d22c15e79ab8e547f10cf5298964d6de4ac5f6b10e0fd12380322350 -size 56012 +oid sha256:52cc26ca7c7db86be00075dcdd39ac364339c0b462edc529c21f17187fb878c2 +size 54642 diff --git a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en.png index 3c9ab417db..ae0e7d8078 100644 --- a/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8a88518bc44f9554dbe9db6c86c51f26f54aff7172dfa1a011e8c10b0f8dac6 -size 48564 +oid sha256:a0cf9ffc7f76bd53c4b94d02f0507994972993ae50ce022db72542a283f780b5 +size 47115 From 3a7903e5beb3f97fdfd1bd88f9e3801717015787 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:41:35 +0000 Subject: [PATCH 175/203] Update dependency org.maplibre.gl:android-sdk to v11.7.1 --- 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 e62f743f79..ab512bd032 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -187,7 +187,7 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.2" -maplibre = "org.maplibre.gl:android-sdk:11.7.0" +maplibre = "org.maplibre.gl:android-sdk:11.7.1" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" opusencoder = "io.element.android:opusencoder:1.1.0" From 1345c4ea0f35e73d3ef982003a9fa4a97b2a0379 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2024 18:04:30 +0100 Subject: [PATCH 176/203] knock requests : fix breaking api --- .../android/libraries/matrix/impl/room/RustMatrixRoom.kt | 8 ++++---- .../libraries/matrix/impl/room/knock/RustKnockRequest.kt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 64c4ba72c9..646ab8290a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -78,8 +78,7 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener -import org.matrix.rustcomponents.sdk.JoinRequest -import org.matrix.rustcomponents.sdk.JoinRequestsListener +import org.matrix.rustcomponents.sdk.KnockRequestsListener import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener import org.matrix.rustcomponents.sdk.RoomListItem @@ -95,6 +94,7 @@ import uniffi.matrix_sdk.RoomPowerLevelChanges import java.io.File import kotlin.coroutines.cancellation.CancellationException import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange +import org.matrix.rustcomponents.sdk.KnockRequest as InnerKnockRequest import org.matrix.rustcomponents.sdk.Room as InnerRoom import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline @@ -162,8 +162,8 @@ class RustMatrixRoom( } override val knockRequestsFlow: Flow> = mxCallbackFlow { - innerRoom.subscribeToJoinRequests(object : JoinRequestsListener { - override fun call(joinRequests: List) { + innerRoom.subscribeToKnockRequests(object : KnockRequestsListener { + override fun call(joinRequests: List) { val knockRequests = joinRequests.map { RustKnockRequest(it) } channel.trySend(knockRequests) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt index 8f7b075511..9e12866c9c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt @@ -10,10 +10,10 @@ package io.element.android.libraries.matrix.impl.room.knock import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.knock.KnockRequest -import org.matrix.rustcomponents.sdk.JoinRequest +import org.matrix.rustcomponents.sdk.KnockRequest as InnerKnockRequest class RustKnockRequest( - private val inner: JoinRequest, + private val inner: InnerKnockRequest, ) : KnockRequest { override val eventId: EventId = EventId(inner.eventId) override val userId: UserId = UserId(inner.userId) From a973c8f02866c1679e2f56bf793ee0ad9ebe9040 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2024 18:10:00 +0100 Subject: [PATCH 177/203] knock requests : fix wrong string resource for error --- .../knockrequests/impl/list/KnockRequestsListView.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index cee983050d..4857416f4e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -206,9 +206,9 @@ private fun KnockRequestsActionsView( errorMessage = { when (actionTarget) { is KnockRequestsActionTarget.Accept -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) - is KnockRequestsActionTarget.Decline -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) - is KnockRequestsActionTarget.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) - KnockRequestsActionTarget.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) + is KnockRequestsActionTarget.Decline -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description) + is KnockRequestsActionTarget.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description) + KnockRequestsActionTarget.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_failed_alert_description) else -> "" } }, From 7f372282dca198c93701f91caf2dabbac2d5ae4e Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2024 18:13:13 +0100 Subject: [PATCH 178/203] knock requests : format and clean --- .../impl/banner/KnockRequestsBannerState.kt | 2 +- .../impl/banner/KnockRequestsBannerView.kt | 2 -- .../impl/data/KnockRequestPresentable.kt | 1 - .../knockrequests/impl/data/KnockRequestsService.kt | 1 - .../knockrequests/impl/list/KnockRequestsListView.kt | 1 - .../impl/banner/KnockRequestsBannerPresenterTest.kt | 1 - .../impl/banner/KnockRequestsBannerViewTest.kt | 2 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 2 -- .../libraries/matrix/api/room/join/JoinRule.kt | 12 ++++++------ .../matrix/impl/room/MatrixRoomInfoMapperTest.kt | 2 +- .../matrix/test/room/knock/FakeKnockRequest.kt | 2 +- 11 files changed, 10 insertions(+), 18 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 9d53181e82..80d662bc5b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -10,8 +10,8 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.features.knockrequests.impl.R +import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.libraries.core.extensions.firstIfSingle import kotlinx.collections.immutable.ImmutableList diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 69dfa268a6..d029b80906 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset @@ -57,7 +56,6 @@ import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList -import timber.log.Timber private const val MAX_AVATAR_COUNT = 3 diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt index 6716908a56..5d45281d8a 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt @@ -32,5 +32,4 @@ interface KnockRequestPresentable { fun getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } - } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt index 5a373f3f36..298f4467e5 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt @@ -27,7 +27,6 @@ class KnockRequestsService( knockRequestsFlow: Flow>, coroutineScope: CoroutineScope, ) { - // Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them. private val handledKnockRequestIds = MutableStateFlow>(emptySet()) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 4857416f4e..3b7ef1670e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -229,7 +229,6 @@ private fun KnockRequestActionConfirmation( stringResource(R.string.screen_knock_requests_list_accept_all_alert_title), stringResource(R.string.screen_knock_requests_list_accept_all_alert_description), stringResource(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title), - ) is KnockRequestsActionTarget.Decline -> Triple( stringResource(R.string.screen_knock_requests_list_decline_alert_title), diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt index 189f392f95..01f91a81c4 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) class KnockRequestsBannerPresenterTest { - @Test fun `present - when feature is disabled then the banner should be hidden`() = runTest { val knockRequests = flowOf(listOf(FakeKnockRequest())) diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt index 7326be47b2..19716c349a 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt @@ -26,7 +26,7 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class KnockRequestsListViewTest { +class KnockRequestsBannerViewTest { @get:Rule val rule = createAndroidComposeRule() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 0fc907a867..e8af5a61a3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -50,7 +50,6 @@ import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -109,7 +108,6 @@ class RoomDetailsPresenter @Inject constructor( val roomType by getRoomType(dmMember, currentMember) val roomCallState = roomCallStatePresenter.present() - val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic when { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt index 33c2ccf092..b597fcf781 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt @@ -9,10 +9,10 @@ package io.element.android.libraries.matrix.api.room.join sealed interface JoinRule { data object Public : JoinRule - data object Private: JoinRule - data object Knock: JoinRule - data object Invite: JoinRule - data class Restricted(val rules: List): JoinRule - data class KnockRestricted(val rules: List): JoinRule - data class Custom(val value: String): JoinRule + data object Private : JoinRule + data object Knock : JoinRule + data object Invite : JoinRule + data class Restricted(val rules: List) : JoinRule + data class KnockRestricted(val rules: List) : JoinRule + data class Custom(val value: String) : JoinRule } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt index 9f91ed16de..f277b26bae 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt @@ -31,8 +31,8 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toPersistentList import org.junit.Test -import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule import org.matrix.rustcomponents.sdk.Membership +import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode class MatrixRoomInfoMapperTest { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt index 88866b9dde..416feae8b8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt @@ -30,7 +30,7 @@ class FakeKnockRequest( val declineAndBanLambda: (String?) -> Result = { lambdaError() }, val markAsSeenLambda: () -> Result = { lambdaError() }, ) : KnockRequest { - override suspend fun accept(): Result = simulateLongTask{ + override suspend fun accept(): Result = simulateLongTask { acceptLambda() } From 80ae08648ac09311064416192419a1dc3304f48b Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2024 20:24:29 +0100 Subject: [PATCH 179/203] knock requests : rework knock requests service to avoid reloading of data (and weird ui glitch because of them) --- .../banner/KnockRequestsBannerPresenter.kt | 16 ++------ .../impl/data/KnockRequestPermissions.kt | 32 ++++++++++++++++ .../impl/data/KnockRequestWrapper.kt | 24 ++++++------ .../impl/data/KnockRequestsModule.kt | 11 +++++- .../impl/data/KnockRequestsService.kt | 32 ++++++++++------ .../impl/list/KnockRequestsListPresenter.kt | 15 +------- .../impl/list/KnockRequestsListState.kt | 7 ++-- .../list/KnockRequestsListStateProvider.kt | 38 +++++++++++++------ .../impl/list/KnockRequestsListView.kt | 6 +-- .../KnockRequestsBannerPresenterTest.kt | 17 ++------- .../list/KnockRequestsListPresenterTest.kt | 28 ++++++-------- .../roomdetails/impl/RoomDetailsPresenter.kt | 1 - 12 files changed, 127 insertions(+), 100 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index a1ad661b0c..f155cdb4a3 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -19,11 +19,6 @@ import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.core.extensions.firstIfSingle -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState -import io.element.android.libraries.matrix.ui.room.canInviteAsState import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -33,10 +28,8 @@ import javax.inject.Inject private const val ACCEPT_ERROR_DISPLAY_DURATION = 1500L class KnockRequestsBannerPresenter @Inject constructor( - private val room: MatrixRoom, private val knockRequestsService: KnockRequestsService, private val appCoroutineScope: CoroutineScope, - private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable override fun present(): KnockRequestsBannerState { @@ -48,15 +41,12 @@ class KnockRequestsBannerPresenter @Inject constructor( } }.collectAsState() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canAccept by room.canInviteAsState(syncUpdateFlow.value) - val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) + val permissions by knockRequestsService.permissionsFlow.collectAsState() val showAcceptError = remember { mutableStateOf(false) } - val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) val shouldShowBanner by remember { derivedStateOf { - isKnockRequestsEnabled && canHandleKnockRequests && knockRequests.isNotEmpty() + permissions.canHandle && knockRequests.isNotEmpty() } } @@ -79,7 +69,7 @@ class KnockRequestsBannerPresenter @Inject constructor( return KnockRequestsBannerState( knockRequests = knockRequests, displayAcceptError = showAcceptError.value, - canAccept = canAccept, + canAccept = permissions.canAccept, isVisible = shouldShowBanner, eventSink = ::handleEvents, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt new file mode 100644 index 0000000000..658717d48b --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.powerlevels.canBan +import io.element.android.libraries.matrix.api.room.powerlevels.canInvite +import io.element.android.libraries.matrix.api.room.powerlevels.canKick +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +data class KnockRequestPermissions( + val canAccept: Boolean, + val canDecline: Boolean, + val canBan: Boolean, +) { + val canHandle = canAccept || canDecline || canBan +} + +fun MatrixRoom.knockRequestPermissionsFlow(): Flow { + return syncUpdateFlow.map { + val canAccept = canInvite().getOrDefault(false) + val canDecline = canKick().getOrDefault(false) + val canBan = canBan().getOrDefault(false) + KnockRequestPermissions(canAccept, canDecline, canBan) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt index 56bcc34422..f1df84beab 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt @@ -12,23 +12,23 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.knock.KnockRequest class KnockRequestWrapper( - private val knockRequest: KnockRequest, + private val inner: KnockRequest, dateFormatter: (Long?) -> String? = { null } ) : KnockRequestPresentable { - override val eventId: EventId = knockRequest.eventId - override val userId: UserId = knockRequest.userId - override val displayName: String? = knockRequest.displayName - override val avatarUrl: String? = knockRequest.avatarUrl - override val reason: String? = knockRequest.reason?.trim() - override val formattedDate: String? = dateFormatter(knockRequest.timestamp) + override val eventId: EventId = inner.eventId + override val userId: UserId = inner.userId + override val displayName: String? = inner.displayName + override val avatarUrl: String? = inner.avatarUrl + override val reason: String? = inner.reason?.trim() + override val formattedDate: String? = dateFormatter(inner.timestamp) - val isSeen: Boolean = knockRequest.isSeen + val isSeen: Boolean = inner.isSeen - suspend fun accept(): Result = knockRequest.accept() + suspend fun accept(): Result = inner.accept() - suspend fun decline(reason: String?): Result = knockRequest.decline(reason) + suspend fun decline(reason: String?): Result = inner.decline(reason) - suspend fun declineAndBan(reason: String?): Result = knockRequest.declineAndBan(reason) + suspend fun declineAndBan(reason: String?): Result = inner.declineAndBan(reason) - suspend fun markAsSeen(): Result = knockRequest.markAsSeen() + suspend fun markAsSeen(): Result = inner.markAsSeen() } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt index 83f0a08c5b..1c1a17767f 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt @@ -12,6 +12,8 @@ import dagger.Module import dagger.Provides import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.room.MatrixRoom @Module @@ -19,7 +21,12 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom object KnockRequestsModule { @Provides @SingleIn(RoomScope::class) - fun knockRequestsService(room: MatrixRoom): KnockRequestsService { - return KnockRequestsService(room.knockRequestsFlow, room.roomCoroutineScope) + fun knockRequestsService(room: MatrixRoom, featureFlagService: FeatureFlagService): KnockRequestsService { + return KnockRequestsService( + knockRequestsFlow = room.knockRequestsFlow, + permissionsFlow = room.knockRequestPermissionsFlow(), + isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock), + coroutineScope = room.roomCoroutineScope + ) } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt index 298f4467e5..d8a594f148 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt @@ -10,6 +10,7 @@ package io.element.android.features.knockrequests.impl.data import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.knock.KnockRequest +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -19,27 +20,40 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.getAndUpdate -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.supervisorScope class KnockRequestsService( knockRequestsFlow: Flow>, + permissionsFlow: Flow, + isKnockFeatureEnabledFlow: Flow, coroutineScope: CoroutineScope, ) { // Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them. private val handledKnockRequestIds = MutableStateFlow>(emptySet()) val knockRequestsFlow = combine( - knockRequestsFlow.wrapped(), + isKnockFeatureEnabledFlow, + knockRequestsFlow, handledKnockRequestIds, - ) { knockRequests, handledKnockIds -> - val presentableKnockRequests = knockRequests - .filter { it.eventId !in handledKnockIds } - .toImmutableList() - AsyncData.Success(presentableKnockRequests) + ) { isKnockEnabled, knockRequests, handledKnockIds -> + if (!isKnockEnabled) { + AsyncData.Success(persistentListOf()) + } else { + val presentableKnockRequests = knockRequests + .filter { it.eventId !in handledKnockIds } + .map { inner -> KnockRequestWrapper(inner) } + .toImmutableList() + AsyncData.Success(presentableKnockRequests) + } }.stateIn(coroutineScope, SharingStarted.Lazily, AsyncData.Loading()) + val permissionsFlow = permissionsFlow.stateIn( + scope = coroutineScope, + started = SharingStarted.Lazily, + initialValue = KnockRequestPermissions(canAccept = false, canDecline = false, canBan = false) + ) + private fun knockRequestsList() = knockRequestsFlow.value.dataOrNull().orEmpty() private fun getKnockRequestById(eventId: EventId): KnockRequestWrapper? { @@ -128,8 +142,4 @@ class KnockRequestsService( } private fun knockRequestNotFoundResult() = Result.failure(IllegalArgumentException("Knock request not found")) - - private fun Flow>.wrapped() = map { knockRequests -> - knockRequests.map { KnockRequestWrapper(it) } - } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index d6e2adbebd..13c63d2d02 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -20,16 +20,11 @@ import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState -import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.ui.room.canBanAsState -import io.element.android.libraries.matrix.ui.room.canInviteAsState -import io.element.android.libraries.matrix.ui.room.canKickAsState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor( - private val room: MatrixRoom, private val knockRequestsService: KnockRequestsService, ) : Presenter { @Composable @@ -37,11 +32,7 @@ class KnockRequestsListPresenter @Inject constructor( val asyncAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } var actionTarget by remember { mutableStateOf(KnockRequestsActionTarget.None) } - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canBan by room.canBanAsState(syncUpdateFlow.value) - val canDecline by room.canKickAsState(syncUpdateFlow.value) - val canAccept by room.canInviteAsState(syncUpdateFlow.value) - + val permissions by knockRequestsService.permissionsFlow.collectAsState() val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState() val coroutineScope = rememberCoroutineScope() @@ -79,10 +70,8 @@ class KnockRequestsListPresenter @Inject constructor( return KnockRequestsListState( knockRequests = knockRequests, actionTarget = actionTarget, + permissions = permissions, asyncAction = asyncAction.value, - canAccept = canAccept, - canDecline = canDecline, - canBan = canBan, eventSink = ::handleEvents ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 763305eca2..3447034afd 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -8,6 +8,7 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Immutable +import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -17,12 +18,10 @@ data class KnockRequestsListState( val knockRequests: AsyncData>, val actionTarget: KnockRequestsActionTarget, val asyncAction: AsyncAction, - val canAccept: Boolean, - val canDecline: Boolean, - val canBan: Boolean, + val permissions: KnockRequestPermissions, val eventSink: (KnockRequestsListEvents) -> Unit, ) { - val canAcceptAll = canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1 + val canAcceptAll = permissions.canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1 } @Immutable diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index e7207cced1..f6d5aba7f6 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.features.knockrequests.impl.data.aKnockRequest import io.element.android.libraries.architecture.AsyncAction @@ -82,7 +83,11 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), actionTarget: KnockRequestsActionTarget = KnockRequestsActionTarget.None, asyncAction: AsyncAction = AsyncAction.Uninitialized, - canAccept: Boolean = true, - canDecline: Boolean = true, - canBan: Boolean = true, + permissions: KnockRequestPermissions = KnockRequestPermissions( + canAccept = true, + canDecline = true, + canBan = true, + ), eventSink: (KnockRequestsListEvents) -> Unit = {}, ) = KnockRequestsListState( knockRequests = knockRequests, actionTarget = actionTarget, asyncAction = asyncAction, - canAccept = canAccept, - canDecline = canDecline, - canBan = canBan, + permissions = permissions, eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 3b7ef1670e..2606bf26c4 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -126,9 +126,9 @@ private fun KnockRequestsListContent( } else { KnockRequestsList( knockRequests = knockRequests, - canAccept = state.canAccept, - canDecline = state.canDecline, - canBan = state.canBan, + canAccept = state.permissions.canAccept, + canDecline = state.permissions.canDecline, + canBan = state.permissions.canBan, onAcceptClick = ::onAcceptClick, onDeclineClick = ::onDeclineClick, onBanClick = ::onBanClick, diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt index 01f91a81c4..f692c85aa7 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt @@ -8,14 +8,12 @@ package io.element.android.features.knockrequests.impl.banner import com.google.common.truth.Truth.assertThat +import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestsService -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.room.knock.KnockRequest 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.A_USER_ID_3 -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -234,19 +232,12 @@ private fun TestScope.createKnockRequestsBannerPresenter( ): KnockRequestsBannerPresenter { val knockRequestsService = KnockRequestsService( knockRequestsFlow = knockRequestsFlow, - coroutineScope = backgroundScope - ) - val featureFlagService = FakeFeatureFlagService( - initialState = mapOf( - FeatureFlags.Knock.key to isFeatureEnabled - ) + coroutineScope = backgroundScope, + isKnockFeatureEnabledFlow = flowOf(isFeatureEnabled), + permissionsFlow = flowOf(KnockRequestPermissions(canAcceptKnockRequests, canAcceptKnockRequests, canAcceptKnockRequests)), ) return KnockRequestsBannerPresenter( - room = FakeMatrixRoom( - canInviteResult = { Result.success(canAcceptKnockRequests) } - ), knockRequestsService = knockRequestsService, appCoroutineScope = this, - featureFlagService = featureFlagService ) } diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt index 1638428c79..010a921515 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt @@ -10,13 +10,13 @@ package io.element.android.features.knockrequests.impl.list import com.google.common.truth.Truth.assertThat +import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -35,15 +35,15 @@ class KnockRequestsListPresenterTest { presenter.test { awaitItem().also { state -> assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java) - assertThat(state.canAccept).isFalse() - assertThat(state.canDecline).isFalse() - assertThat(state.canBan).isFalse() + assertThat(state.permissions.canAccept).isFalse() + assertThat(state.permissions.canDecline).isFalse() + assertThat(state.permissions.canBan).isFalse() } awaitItem().also { state -> assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java) - assertThat(state.canAccept).isTrue() - assertThat(state.canDecline).isTrue() - assertThat(state.canBan).isTrue() + assertThat(state.permissions.canAccept).isTrue() + assertThat(state.permissions.canDecline).isTrue() + assertThat(state.permissions.canBan).isTrue() } awaitItem().also { state -> assertThat(state.knockRequests).isInstanceOf(AsyncData.Success::class.java) @@ -293,18 +293,12 @@ class KnockRequestsListPresenterTest { canBan: Boolean = true, knockRequestsFlow: Flow> = flowOf(emptyList()) ): KnockRequestsListPresenter { - val room = FakeMatrixRoom( - canInviteResult = { Result.success(canAccept) }, - canKickResult = { Result.success(canDecline) }, - canBanResult = { Result.success(canBan) } - ) val knockRequestsService = KnockRequestsService( knockRequestsFlow = knockRequestsFlow, - coroutineScope = backgroundScope - ) - return KnockRequestsListPresenter( - room = room, - knockRequestsService = knockRequestsService, + coroutineScope = backgroundScope, + isKnockFeatureEnabledFlow = flowOf(true), + permissionsFlow = flowOf(KnockRequestPermissions(canAccept, canDecline, canBan)), ) + return KnockRequestsListPresenter(knockRequestsService = knockRequestsService) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index e8af5a61a3..50c403fe87 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -48,7 +48,6 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch From 9f0847f211bb1cfac9c4f22196459a7877f714a1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2024 20:36:40 +0100 Subject: [PATCH 180/203] knock requests : fix test name --- .../impl/banner/KnockRequestsBannerPresenterTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt index f692c85aa7..7027061804 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt @@ -62,7 +62,7 @@ import org.junit.Test } @Test - fun `present - when everything is setup to manage knocks with data, then the banner should be visible `() = runTest { + fun `present - when everything is setup to manage knocks with data, then the banner should be visible`() = runTest { val knockRequests = flowOf( listOf( FakeKnockRequest( From eb9b6a72ee635a4a64bf56619d23a5531507491d Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 18 Dec 2024 19:48:24 +0000 Subject: [PATCH 181/203] Update screenshots --- ...krequests.impl.banner_KnockRequestsBannerView_Day_0_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_1_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_2_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_4_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_5_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_6_en.png | 4 ++-- ...krequests.impl.banner_KnockRequestsBannerView_Day_7_en.png | 3 --- ...equests.impl.banner_KnockRequestsBannerView_Night_0_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_1_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_2_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_4_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_5_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_6_en.png | 4 ++-- ...equests.impl.banner_KnockRequestsBannerView_Night_7_en.png | 3 --- ...knockrequests.impl.list_KnockRequestsListView_Day_0_en.png | 4 ++-- ...nockrequests.impl.list_KnockRequestsListView_Day_10_en.png | 3 +++ ...knockrequests.impl.list_KnockRequestsListView_Day_5_en.png | 4 ++-- ...knockrequests.impl.list_KnockRequestsListView_Day_6_en.png | 4 ++-- ...knockrequests.impl.list_KnockRequestsListView_Day_7_en.png | 4 ++-- ...knockrequests.impl.list_KnockRequestsListView_Day_8_en.png | 4 ++-- ...knockrequests.impl.list_KnockRequestsListView_Day_9_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_0_en.png | 4 ++-- ...ckrequests.impl.list_KnockRequestsListView_Night_10_en.png | 3 +++ ...ockrequests.impl.list_KnockRequestsListView_Night_5_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_6_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_7_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_8_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_9_en.png | 4 ++-- 28 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_10_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_10_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png index 8d01d01fbd..6866b6afc0 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77db58461ef35ea3d1ab1dda6aff454e376b15d7037e83fbbd965037afa45572 -size 29317 +oid sha256:74f93deb90501b746d95e0edf2ae2cef58036a388888e42a9b0fd8aadac9758c +size 29447 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png index 4441fd8ef3..9298210927 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:585ea7b1f230ac6a12f8098200b04f433cbb30b39b8be7953dc6e278ffe8179e -size 34799 +oid sha256:6a9a248862bd05327e10481f868c6dc10cc1ccad6a56284d497a9fb45f737206 +size 35042 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png index 75cafbe376..a5e66c073f 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05770e4e11bbc7019ebc113d31eb8b76bbbddc8b1ca6acbb01c0764089047376 -size 17835 +oid sha256:ef72766061f056096fc6d030b0e12d05dfa767ea5cac45d71b02cdbd0e78971b +size 17859 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png index 55bf403a0d..8eef415284 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a10ccd7db4660bdff998622f5390a33adcaa39472c7d60ebae7c0a5b30a810d4 -size 27294 +oid sha256:3b98dc51e1f31bcd64dee223be7beb66b6ddd814189f290876633f5727d09f18 +size 27383 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png index 8d01d01fbd..462b3bc35e 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77db58461ef35ea3d1ab1dda6aff454e376b15d7037e83fbbd965037afa45572 -size 29317 +oid sha256:a386397f9a0f037050797980a3bd9482ec8a2f1a37d5e150ed8cdd50e5b576a1 +size 31873 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png index 8d01d01fbd..ab44725d4c 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77db58461ef35ea3d1ab1dda6aff454e376b15d7037e83fbbd965037afa45572 -size 29317 +oid sha256:1b0d57fed7af1716e23f9f1167f70199f5a21f60f3ac97940deccd824754a901 +size 39156 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png deleted file mode 100644 index ab52d1d019..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43e80c40ea0b0f6c02d03cf37537fa9e1b8ac71ec5f1d279b51477538a743063 -size 39209 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png index fd40cbce60..29ea2f7789 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f2c03edb4eaec4917b95955ac0b51cbdce25ae9710757cad68e3ed863707f7a -size 27374 +oid sha256:1a7c6898ea6667e5b7f09ee99ca6b2d75ae3bc20e0db57b9d7a20c48d4681dca +size 27308 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png index 8c1e0742e5..43684248f2 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3811a02bebe20381a5996bcb61176d258fbb1ca4302cf5f9742470c3f152c780 -size 32258 +oid sha256:e229ac5d069412a18ae7deeefd0e88d9897b239ab32a0edd2c2d06f139f1f1af +size 32430 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png index ca5bf4cb5f..31c1d7e220 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9aaf05a0f912cedf6897fac21e679429b86b0ed1d3e8b6a07cfe7ec6f60cec13 -size 15885 +oid sha256:62cf8a40ea8670ce9a23923d5791137ec057b130cde637c4dfb5b0edc75d7971 +size 15926 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png index 38ace64640..02fd5af4b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17d3263b1c47a75083bd597f73baf8cf5faa74274810e85cf2ee086812ca0e60 -size 25248 +oid sha256:89fdc1cf49c756e40d81eb858b507f751a6ff661fcb4f3194a5d7c0f738c1b94 +size 25250 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png index fd40cbce60..6abdd712c0 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f2c03edb4eaec4917b95955ac0b51cbdce25ae9710757cad68e3ed863707f7a -size 27374 +oid sha256:ad9532960a251f04ec0fd513df561010a91923ab41de8318f99df23d3799feec +size 28232 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png index fd40cbce60..de9b3caf77 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f2c03edb4eaec4917b95955ac0b51cbdce25ae9710757cad68e3ed863707f7a -size 27374 +oid sha256:8f2419c455efb74f0f528017e411c16d76755646e3449ae4a62077857e3ea93b +size 36848 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png deleted file mode 100644 index 81355d2bfc..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cbed125bd5c334bb5d7fce60df615d188c713989bcd6450f59d3f37973ab2bc7 -size 36735 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png index d2ddc2c7c6..8b36ceefb5 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbbd687e5e0a1fd3a1be442937e32990dc72a6e4817d6ffc29f6f596eeddef1b -size 8086 +oid sha256:0132466eb104c2249958a310b19336f97a5485f98e47a233c29740b14b9907ee +size 14298 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_10_en.png new file mode 100644 index 0000000000..8f0414d4b3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9afb58cfcd13c064f0b17d449318d5111aaa914cb8875fe0596c66e5a8a17051 +size 30647 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png index c14ef5dd52..9463263a2f 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e66d3f7d10e759f629795580545cda6644292020ce36763d07e9d476dcc231 -size 30325 +oid sha256:dcd89988740d57132796306568378471b67ceb7c182e82fce5fa01dc936a640f +size 44821 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png index 466e591450..a8e76f0ab7 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:586021e67718aa949b1de04799a43d9da8189fadacdb6dba23405c762fc7ee06 -size 30618 +oid sha256:7ee73fabc6f4db8b7fb26b606723c69cf169e3a003a828b74654b6286235e164 +size 34430 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png index 2952c5bd81..c4e4262ce2 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b71dc6dc0d29801e81d1d4e52df811624583f69c0510f2f650676747a326455 -size 30296 +oid sha256:170e1d045120d172d26d5d7bdd6bbf42c1a5bddcad7ff38d5352cd38c05efed4 +size 39551 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png index 9c47af83a4..2952c5bd81 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6be2cfe7991bce6c622f4f2e45f4ac96810cdc9462d9a4d7aea11baf69ab5ac -size 27446 +oid sha256:1b71dc6dc0d29801e81d1d4e52df811624583f69c0510f2f650676747a326455 +size 30296 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png index 8f0414d4b3..9c47af83a4 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9afb58cfcd13c064f0b17d449318d5111aaa914cb8875fe0596c66e5a8a17051 -size 30647 +oid sha256:f6be2cfe7991bce6c622f4f2e45f4ac96810cdc9462d9a4d7aea11baf69ab5ac +size 27446 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png index 5188e40e69..5ea469c53f 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ff4bc9e0588ea6a8396572b9ac9fb2401c3193fc3f1fbeb75efd69be6dda24d -size 7867 +oid sha256:ab53aa77207ee6984eb5a7a619b5b748178819bd2006ace4282f28f1c6b16cac +size 13944 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_10_en.png new file mode 100644 index 0000000000..54a5fc7d9c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68294db4cbd8bb42f4a0b6534b48585341beb2bc405e93cf74a2655f049522ff +size 29768 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png index 45b2a829ef..6fec3fbf9a 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c30b6e25ec148d4f98a6c305b7d71af6c7e413cc3425f5a6f5b42a201039f491 -size 29007 +oid sha256:00c25643adf7f06ef46e824aa60b35348c18e596a82557b1546b55d29392aa11 +size 42613 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png index b39ccb0150..d626612346 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5c6abc8bd5ca40eb91d926adc798cee95a8aacb0e87d937983c6240173fe2b9 -size 30071 +oid sha256:ad43b5aaa3ea7cf3ab79af88a2a93ba4294527ad2c40c72f4312f3e1b92b4f1a +size 33280 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png index 215cef276c..3cbbf256ec 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b67c8d2cedb724ece75a2171463c4e035d562c4480d39b11b1072f4fdae3053 -size 29840 +oid sha256:6d9f612c2e6d46ed59ec5a3d1c65fe8953e4fc8f0955bddaa2abef6e1eabb00d +size 37194 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png index 171b5f5aa5..215cef276c 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15903c9494100becc4dd2bc6e9fb7f38f8eebbb595cbc251cfc0f839d9a99a27 -size 27322 +oid sha256:3b67c8d2cedb724ece75a2171463c4e035d562c4480d39b11b1072f4fdae3053 +size 29840 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png index 54a5fc7d9c..171b5f5aa5 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68294db4cbd8bb42f4a0b6534b48585341beb2bc405e93cf74a2655f049522ff -size 29768 +oid sha256:15903c9494100becc4dd2bc6e9fb7f38f8eebbb595cbc251cfc0f839d9a99a27 +size 27322 From 7421542149198523ff5e2f0c0fb49688e5617430 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 10:28:48 +0100 Subject: [PATCH 182/203] Ensure that there is space between segmented button and gallery. --- .../libraries/mediaviewer/impl/gallery/MediaGalleryView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 7f7952f1a6..6febeb7125 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -106,7 +106,8 @@ fun MediaGalleryView( modifier = Modifier .padding(paddingValues) .consumeWindowInsets(paddingValues) - .fillMaxSize() + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(2.dp), ) { SingleChoiceSegmentedButtonRow( modifier = Modifier From 33ba517fe0bdb2e92ae1595b8b3c7393bc08c007 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 11:14:36 +0100 Subject: [PATCH 183/203] Always attempt to start the sync when starting the application. --- .../kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 940e576a30..5d52e7780d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -84,6 +84,7 @@ import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyn import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.collectIndexed import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn @@ -196,9 +197,9 @@ class LoggedInFlowNode @AssistedInject constructor( ) { syncState, networkStatus -> Pair(syncState, networkStatus) } - .collect { (syncState, networkStatus) -> - Timber.d("Sync state: $syncState, network status: $networkStatus") - if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) { + .collectIndexed { index, (syncState, networkStatus) -> + Timber.d("Sync state: $syncState, network status: $networkStatus, index: $index") + if (syncState != SyncState.Running && (index == 0 || networkStatus == NetworkStatus.Online)) { syncService.startSync() } } From 066dc8b18afb54d9ec4afbc93d532b36dba57131 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 12:24:58 +0100 Subject: [PATCH 184/203] Add gradient behind video item info. --- .../mediaviewer/impl/gallery/ui/VideoItemView.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt index e6d67fb01a..01aba3c20d 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalInspectionMode @@ -84,6 +85,14 @@ private fun VideoInfoRow( Row( modifier = modifier .fillMaxWidth() + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.White.copy(alpha = 0f), + Color.White, + ) + ) + ) .padding(8.dp), verticalAlignment = Alignment.CenterVertically, ) { From 33634b2c95d38022ed7a2e32e239d8abe055613b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 12:54:32 +0100 Subject: [PATCH 185/203] Add background to empty views. --- .../libraries/mediaviewer/impl/gallery/MediaGalleryView.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 6febeb7125..41dfdb1baa 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -41,6 +41,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.designsystem.background.OnboardingBackground import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle import io.element.android.libraries.designsystem.components.async.AsyncFailure @@ -427,6 +428,7 @@ private fun EmptyContent( Box( modifier = Modifier.fillMaxSize(), ) { + OnboardingBackground() PageTitle( modifier = Modifier .fillMaxWidth() From c4fd20faf4cf7c19ce381e85cbb9b6e78e8705f2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 13:44:19 +0100 Subject: [PATCH 186/203] Start sync faster --- .../io/element/android/appnav/LoggedInFlowNode.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 5d52e7780d..93fc8bd06e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -84,11 +84,11 @@ import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyn import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.collectIndexed import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import timber.log.Timber @@ -197,9 +197,13 @@ class LoggedInFlowNode @AssistedInject constructor( ) { syncState, networkStatus -> Pair(syncState, networkStatus) } - .collectIndexed { index, (syncState, networkStatus) -> - Timber.d("Sync state: $syncState, network status: $networkStatus, index: $index") - if (syncState != SyncState.Running && (index == 0 || networkStatus == NetworkStatus.Online)) { + .onStart { + // Temporary fix to ensure that the sync is started even if the networkStatus is offline. + syncService.startSync() + } + .collect { (syncState, networkStatus) -> + Timber.d("Sync state: $syncState, network status: $networkStatus") + if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) { syncService.startSync() } } From 5a103039ed4502f3f8fd06dff46287db48ee73b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:02:57 +0000 Subject: [PATCH 187/203] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.73 --- 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 ab512bd032..8273f4af5b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -173,7 +173,7 @@ jsoup = "org.jsoup:jsoup:1.18.3" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.72" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.73" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 5c41de60fa0c0ec74dabdef540f99de0d13a9559 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 16:20:41 +0100 Subject: [PATCH 188/203] Move share and download actions to the bottom sheet --- .../messages/impl/MessagesFlowNode.kt | 2 - .../mediaviewer/api/MediaViewerEntryPoint.kt | 2 - .../impl/DefaultMediaViewerEntryPoint.kt | 2 - .../impl/details/MediaDetailsBottomSheet.kt | 20 ++++++ .../impl/gallery/MediaGalleryEvents.kt | 4 +- .../impl/gallery/MediaGalleryPresenter.kt | 24 ++++++-- .../impl/gallery/MediaGalleryView.kt | 18 +++++- .../impl/gallery/root/MediaGalleryRootNode.kt | 2 - .../impl/gallery/ui/AudioItemView.kt | 10 ++- .../impl/gallery/ui/FileItemView.kt | 10 ++- .../impl/gallery/ui/VoiceItemView.kt | 61 +++---------------- .../impl/viewer/MediaViewerPresenter.kt | 17 ++++-- .../impl/viewer/MediaViewerState.kt | 2 - .../impl/viewer/MediaViewerStateProvider.kt | 6 -- .../impl/viewer/MediaViewerView.kt | 36 ++--------- .../impl/viewer/MediaViewerPresenterTest.kt | 52 ---------------- .../impl/viewer/MediaViewerViewTest.kt | 27 ++++++-- 17 files changed, 122 insertions(+), 173 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 2d0be01de1..1b9c9909f4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -251,8 +251,6 @@ class MessagesFlowNode @AssistedInject constructor( mediaSource = navTarget.mediaSource, thumbnailSource = navTarget.thumbnailSource, canShowInfo = true, - canDownload = true, - canShare = true, ) val callback = object : MediaViewerEntryPoint.Callback { override fun onDone() { diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt index 3e262c08f2..598d799723 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt @@ -36,7 +36,5 @@ interface MediaViewerEntryPoint : FeatureEntryPoint { val mediaSource: MediaSource, val thumbnailSource: MediaSource?, val canShowInfo: Boolean, - val canDownload: Boolean, - val canShare: Boolean, ) : NodeInputs } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt index 59a7f423e6..19ac8718d5 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt @@ -59,8 +59,6 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint mediaSource = MediaSource(url = avatarUrl), thumbnailSource = null, canShowInfo = false, - canDownload = false, - canShare = false, ) ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt index 42127db229..74d47797a2 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt @@ -48,6 +48,8 @@ import io.element.android.libraries.ui.strings.CommonStrings fun MediaDetailsBottomSheet( state: MediaBottomSheetState.MediaDetailsBottomSheetState, onViewInTimeline: (EventId) -> Unit, + onShare: (EventId) -> Unit, + onDownload: (EventId) -> Unit, onDelete: (EventId) -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier, @@ -92,6 +94,22 @@ fun MediaDetailsBottomSheet( onViewInTimeline(state.eventId) } ) + ListItem( + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ShareAndroid())), + headlineContent = { Text(stringResource(CommonStrings.action_share)) }, + style = ListItemStyle.Primary, + onClick = { + onShare(state.eventId) + } + ) + ListItem( + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Download())), + headlineContent = { Text(stringResource(CommonStrings.action_save)) }, + style = ListItemStyle.Primary, + onClick = { + onDownload(state.eventId) + } + ) if (state.canDelete) { HorizontalDivider() ListItem( @@ -196,6 +214,8 @@ internal fun MediaDetailsBottomSheetPreview() = ElementPreview { MediaDetailsBottomSheet( state = aMediaDetailsBottomSheetState(), onViewInTimeline = {}, + onShare = {}, + onDownload = {}, onDelete = {}, onDismiss = {}, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt index 717ba4edbb..e4199e6d5e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt @@ -15,8 +15,8 @@ import io.element.android.libraries.mediaviewer.api.MediaInfo sealed interface MediaGalleryEvents { data class ChangeMode(val mode: MediaGalleryMode) : MediaGalleryEvents data class LoadMore(val direction: Timeline.PaginationDirection) : MediaGalleryEvents - data class Share(val mediaItem: MediaItem.Event) : MediaGalleryEvents - data class SaveOnDisk(val mediaItem: MediaItem.Event) : MediaGalleryEvents + data class Share(val eventId: EventId?) : MediaGalleryEvents + data class SaveOnDisk(val eventId: EventId?) : MediaGalleryEvents data class OpenInfo(val mediaItem: MediaItem.Event) : MediaGalleryEvents data class ViewInTimeline(val eventId: EventId) : MediaGalleryEvents diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 905ba2c770..adedd83599 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -117,8 +117,16 @@ class MediaGalleryPresenter @AssistedInject constructor( timeline.dataOrNull()?.paginate(event.direction) } is MediaGalleryEvents.Delete -> coroutineScope.delete(timeline, event.eventId) - is MediaGalleryEvents.SaveOnDisk -> coroutineScope.saveOnDisk(event.mediaItem) - is MediaGalleryEvents.Share -> coroutineScope.share(event.mediaItem) + is MediaGalleryEvents.SaveOnDisk -> coroutineScope.launch { + mediaItems.dataOrNull().find(event.eventId)?.let { + saveOnDisk(it) + } + } + is MediaGalleryEvents.Share -> coroutineScope.launch { + mediaItems.dataOrNull().find(event.eventId)?.let { + share(it) + } + } is MediaGalleryEvents.ViewInTimeline -> { mediaBottomSheetState = MediaBottomSheetState.Hidden navigator.onViewInTimelineClick(event.eventId) @@ -221,7 +229,7 @@ class MediaGalleryPresenter @AssistedInject constructor( } } - private fun CoroutineScope.saveOnDisk(mediaItem: MediaItem.Event) = launch { + private suspend fun saveOnDisk(mediaItem: MediaItem.Event) { downloadMedia(mediaItem) .mapCatching { localMedia -> localMediaActions.saveOnDisk(localMedia) @@ -236,7 +244,7 @@ class MediaGalleryPresenter @AssistedInject constructor( } } - private fun CoroutineScope.share(mediaItem: MediaItem.Event) = launch { + private suspend fun share(mediaItem: MediaItem.Event) { downloadMedia(mediaItem) .mapCatching { localMedia -> localMediaActions.share(localMedia) @@ -255,3 +263,11 @@ class MediaGalleryPresenter @AssistedInject constructor( } } } + +private fun List?.find(eventId: EventId?): MediaItem.Event? { + if (this == null || eventId == null) { + return null + } + return filterIsInstance() + .firstOrNull { it.eventId() == eventId } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 41dfdb1baa..eec45a0b22 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -153,6 +153,12 @@ fun MediaGalleryView( onViewInTimeline = { eventId -> state.eventSink(MediaGalleryEvents.ViewInTimeline(eventId)) }, + onShare = { eventId -> + state.eventSink(MediaGalleryEvents.Share(eventId)) + }, + onDownload = { eventId -> + state.eventSink(MediaGalleryEvents.SaveOnDisk(eventId)) + }, onDelete = { eventId -> state.eventSink( MediaGalleryEvents.ConfirmDelete( @@ -276,11 +282,17 @@ private fun MediaGalleryFilesList( modifier = Modifier.animateItem(), file = item, onClick = { onItemClick(item) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, ) is MediaItem.Audio -> AudioItemView( modifier = Modifier.animateItem(), audio = item, onClick = { onItemClick(item) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, ) is MediaItem.Voice -> { val presenter: Presenter = presenterFactories.rememberPresenter(item) @@ -288,9 +300,9 @@ private fun MediaGalleryFilesList( modifier = Modifier.animateItem(), state = presenter.present(), voice = item, - onShareClick = { eventSink(MediaGalleryEvents.Share(item)) }, - onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) }, - onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) }, + onLongClick = { + eventSink(MediaGalleryEvents.OpenInfo(item)) + }, ) } is MediaItem.DateSeparator -> DateItemView( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt index 4f5272b01b..caee363d51 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryRootNode.kt @@ -122,8 +122,6 @@ class MediaGalleryRootNode @AssistedInject constructor( mediaSource = navTarget.mediaSource, thumbnailSource = navTarget.thumbnailSource, canShowInfo = true, - canDownload = true, - canShare = true, ) ) .callback(callback) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt index dd06fa1bf8..d3511fcaed 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt @@ -7,8 +7,9 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -41,6 +42,7 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem fun AudioItemView( audio: MediaItem.Audio, onClick: () -> Unit, + onLongClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -52,6 +54,7 @@ fun AudioItemView( FilenameRow( audio = audio, onClick = onClick, + onLongClick = onLongClick, ) val caption = audio.mediaInfo.caption if (caption != null) { @@ -63,10 +66,12 @@ fun AudioItemView( } } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun FilenameRow( audio: MediaItem.Audio, onClick: () -> Unit, + onLongClick: () -> Unit, ) { Row( modifier = Modifier @@ -75,7 +80,7 @@ private fun FilenameRow( color = ElementTheme.colors.bgSubtleSecondary, shape = RoundedCornerShape(12.dp), ) - .clickable { onClick() } + .combinedClickable(onClick = onClick, onLongClick = onLongClick) .fillMaxWidth() .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, @@ -119,5 +124,6 @@ internal fun AudioItemViewPreview( AudioItemView( audio = audio, onClick = {}, + onLongClick = {}, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt index 6618815c21..5ad2234900 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt @@ -7,8 +7,9 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -40,6 +41,7 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem fun FileItemView( file: MediaItem.File, onClick: () -> Unit, + onLongClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -51,6 +53,7 @@ fun FileItemView( FilenameRow( file = file, onClick = onClick, + onLongClick = onLongClick, ) val caption = file.mediaInfo.caption if (caption != null) { @@ -62,10 +65,12 @@ fun FileItemView( } } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun FilenameRow( file: MediaItem.File, onClick: () -> Unit, + onLongClick: () -> Unit, ) { Row( modifier = Modifier @@ -74,7 +79,7 @@ private fun FilenameRow( color = ElementTheme.colors.bgSubtleSecondary, shape = RoundedCornerShape(12.dp), ) - .clickable { onClick() } + .combinedClickable(onClick = onClick, onLongClick = onLongClick) .fillMaxWidth() .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, @@ -118,5 +123,6 @@ internal fun FileItemViewPreview( FileItemView( file = file, onClick = {}, + onLongClick = {}, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt index 5864914dda..ab322427ee 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -7,9 +7,10 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -58,9 +59,7 @@ import kotlinx.coroutines.delay fun VoiceItemView( state: VoiceMessageState, voice: MediaItem.Voice, - onShareClick: () -> Unit, - onDownloadClick: () -> Unit, - onInfoClick: () -> Unit, + onLongClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -72,6 +71,7 @@ fun VoiceItemView( VoiceInfoRow( state = state, voice = voice, + onLongClick = onLongClick, ) val caption = voice.mediaInfo.caption if (caption != null) { @@ -79,19 +79,16 @@ fun VoiceItemView( } else { Spacer(modifier = Modifier.height(16.dp)) } - ActionIconsRow( - onShareClick = onShareClick, - onDownloadClick = onDownloadClick, - onInfoClick = onInfoClick, - ) HorizontalDivider() } } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun VoiceInfoRow( state: VoiceMessageState, voice: MediaItem.Voice, + onLongClick: () -> Unit, ) { fun playPause() { state.eventSink(VoiceMessageEvents.PlayPause) @@ -104,6 +101,7 @@ private fun VoiceInfoRow( color = ElementTheme.colors.bgSubtleSecondary, shape = RoundedCornerShape(12.dp), ) + .combinedClickable(onClick = {}, onLongClick = onLongClick) .fillMaxWidth() .padding(start = 12.dp, end = 36.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, @@ -257,43 +255,6 @@ private fun CustomIconButton( ) } -@Composable -private fun ActionIconsRow( - onShareClick: () -> Unit, - onDownloadClick: () -> Unit, - onInfoClick: () -> Unit, -) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - IconButton( - onClick = onShareClick, - ) { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - contentDescription = null, - ) - } - IconButton( - onClick = onDownloadClick, - ) { - Icon( - imageVector = CompoundIcons.Download(), - contentDescription = null, - ) - } - IconButton( - onClick = onInfoClick, - ) { - Icon( - imageVector = CompoundIcons.Info(), - contentDescription = null, - ) - } - } -} - @PreviewsDayNight @Composable internal fun VoiceItemViewPreview( @@ -302,9 +263,7 @@ internal fun VoiceItemViewPreview( VoiceItemView( state = aVoiceMessageState(), voice = voice, - onShareClick = {}, - onDownloadClick = {}, - onInfoClick = {}, + onLongClick = {}, ) } @@ -316,8 +275,6 @@ internal fun VoiceItemViewPlayPreview( VoiceItemView( state = state, voice = aMediaItemVoice(), - onShareClick = {}, - onDownloadClick = {}, - onInfoClick = {}, + onLongClick = {}, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index a480d1ba7c..a907934724 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -83,9 +83,18 @@ class MediaViewerPresenter @AssistedInject constructor( when (mediaViewerEvents) { MediaViewerEvents.RetryLoading -> loadMediaTrigger++ MediaViewerEvents.ClearLoadingError -> localMedia.value = AsyncData.Uninitialized - MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value) - MediaViewerEvents.Share -> coroutineScope.share(localMedia.value) - MediaViewerEvents.OpenWith -> coroutineScope.open(localMedia.value) + MediaViewerEvents.SaveOnDisk -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + coroutineScope.saveOnDisk(localMedia.value) + } + MediaViewerEvents.Share -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + coroutineScope.share(localMedia.value) + } + MediaViewerEvents.OpenWith -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + coroutineScope.open(localMedia.value) + } is MediaViewerEvents.Delete -> { mediaBottomSheetState = MediaBottomSheetState.Hidden coroutineScope.delete(mediaViewerEvents.eventId) @@ -126,8 +135,6 @@ class MediaViewerPresenter @AssistedInject constructor( downloadedMedia = localMedia.value, snackbarMessage = snackbarMessage, canShowInfo = inputs.canShowInfo, - canDownload = inputs.canDownload, - canShare = inputs.canShare, mediaBottomSheetState = mediaBottomSheetState, eventSink = ::handleEvents ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt index 6ae8554b06..3e0deaf9b3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt @@ -22,8 +22,6 @@ data class MediaViewerState( val downloadedMedia: AsyncData, val snackbarMessage: SnackbarMessage?, val canShowInfo: Boolean, - val canDownload: Boolean, - val canShare: Boolean, val mediaBottomSheetState: MediaBottomSheetState, val eventSink: (MediaViewerEvents) -> Unit, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 61e4f78176..63c82be7ba 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -91,8 +91,6 @@ open class MediaViewerStateProvider : PreviewParameterProvider ), mediaInfo = it, canShowInfo = false, - canDownload = false, - canShare = false, ) }, aMediaViewerState( @@ -118,8 +116,6 @@ fun aMediaViewerState( downloadedMedia: AsyncData = AsyncData.Uninitialized, mediaInfo: MediaInfo = anImageMediaInfo(), canShowInfo: Boolean = true, - canDownload: Boolean = true, - canShare: Boolean = true, mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden, eventSink: (MediaViewerEvents) -> Unit = {}, ) = MediaViewerState( @@ -129,8 +125,6 @@ fun aMediaViewerState( downloadedMedia = downloadedMedia, snackbarMessage = null, canShowInfo = canShowInfo, - canDownload = canDownload, - canShare = canShare, mediaBottomSheetState = mediaBottomSheetState, eventSink = eventSink, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 6ca9ebec9c..d3347b9812 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -119,8 +119,6 @@ fun MediaViewerView( ) { MediaViewerTopBar( actionsEnabled = state.downloadedMedia is AsyncData.Success, - canDownload = state.canDownload, - canShare = state.canShare, mimeType = state.mediaInfo.mimeType, senderName = state.mediaInfo.senderName, dateSent = state.mediaInfo.dateSent, @@ -148,6 +146,12 @@ fun MediaViewerView( onViewInTimeline = { state.eventSink(MediaViewerEvents.ViewInTimeline(it)) }, + onShare = { + state.eventSink(MediaViewerEvents.Share) + }, + onDownload = { + state.eventSink(MediaViewerEvents.SaveOnDisk) + }, onDelete = { eventId -> state.eventSink(MediaViewerEvents.ConfirmDelete(eventId)) }, @@ -313,8 +317,6 @@ private fun rememberShowProgress(downloadedMedia: AsyncData): Boolea @Composable private fun MediaViewerTopBar( actionsEnabled: Boolean, - canDownload: Boolean, - canShare: Boolean, mimeType: String, senderName: String?, dateSent: String?, @@ -348,19 +350,6 @@ private fun MediaViewerTopBar( ), navigationIcon = { BackButton(onClick = onBackClick) }, actions = { - if (canShare) { - IconButton( - enabled = actionsEnabled, - onClick = { - eventSink(MediaViewerEvents.Share) - }, - ) { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - contentDescription = stringResource(id = CommonStrings.action_share) - ) - } - } IconButton( enabled = actionsEnabled, onClick = { @@ -378,19 +367,6 @@ private fun MediaViewerTopBar( ) } } - if (canDownload) { - IconButton( - enabled = actionsEnabled, - onClick = { - eventSink(MediaViewerEvents.SaveOnDisk) - }, - ) { - Icon( - imageVector = CompoundIcons.Download(), - contentDescription = stringResource(id = CommonStrings.action_save), - ) - } - } if (canShowInfo) { IconButton( onClick = onInfoClick, 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 43835e71e5..1e5f6120cc 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 @@ -66,8 +66,6 @@ class MediaViewerPresenterTest { assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.canShowInfo).isTrue() - assertThat(initialState.canDownload).isTrue() - assertThat(initialState.canShare).isTrue() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -86,48 +84,6 @@ class MediaViewerPresenterTest { assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.canShowInfo).isFalse() - assertThat(initialState.canDownload).isTrue() - assertThat(initialState.canShare).isTrue() - assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) - } - } - - @Test - fun `present - initial state cannot share`() = runTest { - val presenter = createMediaViewerPresenter( - canShare = false, - room = FakeMatrixRoom( - canRedactOwnResult = { Result.success(true) }, - ) - ) - presenter.test { - skipItems(2) - val initialState = awaitItem() - assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) - assertThat(initialState.snackbarMessage).isNull() - assertThat(initialState.canShowInfo).isTrue() - assertThat(initialState.canDownload).isTrue() - assertThat(initialState.canShare).isFalse() - assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) - } - } - - @Test - fun `present - initial state cannot download`() = runTest { - val presenter = createMediaViewerPresenter( - canDownload = false, - room = FakeMatrixRoom( - canRedactOwnResult = { Result.success(true) }, - ) - ) - presenter.test { - skipItems(2) - val initialState = awaitItem() - assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) - assertThat(initialState.snackbarMessage).isNull() - assertThat(initialState.canShowInfo).isTrue() - assertThat(initialState.canDownload).isFalse() - assertThat(initialState.canShare).isTrue() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -146,8 +102,6 @@ class MediaViewerPresenterTest { assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.canShowInfo).isTrue() - assertThat(initialState.canDownload).isTrue() - assertThat(initialState.canShare).isTrue() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -167,8 +121,6 @@ class MediaViewerPresenterTest { assertThat(initialState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.canShowInfo).isTrue() - assertThat(initialState.canDownload).isTrue() - assertThat(initialState.canShare).isTrue() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) } } @@ -350,8 +302,6 @@ class MediaViewerPresenterTest { localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(), snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), canShowInfo: Boolean = true, - canShare: Boolean = true, - canDownload: Boolean = true, mediaViewerNavigator: MediaViewerNavigator = FakeMediaViewerNavigator(), room: MatrixRoom = FakeMatrixRoom( liveTimeline = FakeTimeline(), @@ -364,8 +314,6 @@ class MediaViewerPresenterTest { mediaSource = aMediaSource(), thumbnailSource = null, canShowInfo = canShowInfo, - canShare = canShare, - canDownload = canDownload, ), localMediaFactory = localMediaFactory, mediaLoader = matrixMediaLoader, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt index acbfb57619..83fada0e54 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder @@ -54,17 +55,33 @@ class MediaViewerViewTest { testMenuAction(CommonStrings.action_open_with, MediaViewerEvents.OpenWith) } + private fun testMenuAction(contentDescriptionRes: Int, expectedEvent: MediaViewerEvents) { + val eventsRecorder = EventsRecorder() + rule.setMediaViewerView( + aMediaViewerState( + downloadedMedia = AsyncData.Success( + LocalMedia(Uri.EMPTY, anImageMediaInfo()) + ), + mediaInfo = anImageMediaInfo(), + eventSink = eventsRecorder + ), + ) + val contentDescription = rule.activity.getString(contentDescriptionRes) + rule.onNodeWithContentDescription(contentDescription).performClick() + eventsRecorder.assertSingle(expectedEvent) + } + @Test fun `clicking on save emit expected Event`() { - testMenuAction(CommonStrings.action_save, MediaViewerEvents.SaveOnDisk) + testBottomSheetAction(CommonStrings.action_save, MediaViewerEvents.SaveOnDisk) } @Test fun `clicking on share emit expected Event`() { - testMenuAction(CommonStrings.action_share, MediaViewerEvents.Share) + testBottomSheetAction(CommonStrings.action_share, MediaViewerEvents.Share) } - private fun testMenuAction(contentDescriptionRes: Int, expectedEvent: MediaViewerEvents) { + private fun testBottomSheetAction(contentDescriptionRes: Int, expectedEvent: MediaViewerEvents) { val eventsRecorder = EventsRecorder() rule.setMediaViewerView( aMediaViewerState( @@ -72,11 +89,11 @@ class MediaViewerViewTest { LocalMedia(Uri.EMPTY, anImageMediaInfo()) ), mediaInfo = anImageMediaInfo(), + mediaBottomSheetState = aMediaDetailsBottomSheetState(), eventSink = eventsRecorder ), ) - val contentDescription = rule.activity.getString(contentDescriptionRes) - rule.onNodeWithContentDescription(contentDescription).performClick() + rule.clickOn(contentDescriptionRes) eventsRecorder.assertSingle(expectedEvent) } From 52af70ad0df77482f335b7012e3b789bc1291687 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 16:35:51 +0100 Subject: [PATCH 189/203] Add UI test on MediaDetailsBottomSheet --- .../mediaviewer/impl/details/Preview.kt | 3 +- .../MediaDeleteConfirmationBottomSheetTest.kt | 70 +++++++++++ .../details/MediaDetailsBottomSheetTest.kt | 113 ++++++++++++++++++ 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt create mode 100644 libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt index 5957fd480f..25cab1429f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt @@ -12,10 +12,11 @@ import io.element.android.libraries.mediaviewer.api.anImageMediaInfo fun aMediaDetailsBottomSheetState( dateSentFull: String = "December 6, 2024 at 12:59", + canDelete: Boolean = true, ): MediaBottomSheetState.MediaDetailsBottomSheetState { return MediaBottomSheetState.MediaDetailsBottomSheetState( eventId = EventId("\$eventId"), - canDelete = true, + canDelete = canDelete, mediaInfo = anImageMediaInfo( senderName = "Alice", dateSentFull = dateSentFull, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt new file mode 100644 index 0000000000..cc652f21ec --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.details + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MediaDeleteConfirmationBottomSheetTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on Cancel invokes expected callback`() { + val state = aMediaDeleteConfirmationState() + ensureCalledOnce { callback -> + rule.setMediaDeleteConfirmationBottomSheet( + state = state, + onDismiss = callback, + ) + rule.clickOn(CommonStrings.action_cancel) + } + } + + @Test + fun `clicking on Remove invokes expected callback`() { + val state = aMediaDeleteConfirmationState() + ensureCalledOnceWithParam(state.eventId) { callback -> + rule.setMediaDeleteConfirmationBottomSheet( + state = state, + onDelete = callback, + ) + rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertExists() + rule.clickOn(CommonStrings.action_remove) + } + } +} + +private fun AndroidComposeTestRule.setMediaDeleteConfirmationBottomSheet( + state: MediaBottomSheetState.MediaDeleteConfirmationState, + onDelete: (EventId) -> Unit = EnsureNeverCalledWithParam(), + onDismiss: () -> Unit = EnsureNeverCalled(), +) { + setContent { + MediaDeleteConfirmationBottomSheet( + state = state, + onDelete = onDelete, + onDismiss = onDismiss, + ) + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt new file mode 100644 index 0000000000..2a19be26f3 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.details + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class MediaDetailsBottomSheetTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on View in timeline invokes expected callback`() { + val state = aMediaDetailsBottomSheetState() + ensureCalledOnceWithParam(state.eventId) { callback -> + rule.setMediaDetailsBottomSheet( + state = state, + onViewInTimeline = callback, + ) + rule.clickOn(CommonStrings.action_view_in_timeline) + } + } + + @Test + fun `clicking on Share invokes expected callback`() { + val state = aMediaDetailsBottomSheetState() + ensureCalledOnceWithParam(state.eventId) { callback -> + rule.setMediaDetailsBottomSheet( + state = state, + onShare = callback, + ) + rule.clickOn(CommonStrings.action_share) + } + } + + @Test + fun `clicking on Save invokes expected callback`() { + val state = aMediaDetailsBottomSheetState() + ensureCalledOnceWithParam(state.eventId) { callback -> + rule.setMediaDetailsBottomSheet( + state = state, + onDownload = callback, + ) + rule.clickOn(CommonStrings.action_save) + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on Remove invokes expected callback`() { + val state = aMediaDetailsBottomSheetState() + ensureCalledOnceWithParam(state.eventId) { callback -> + rule.setMediaDetailsBottomSheet( + state = state, + onDelete = callback, + ) + rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertExists() + rule.clickOn(CommonStrings.action_remove) + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `Remove is not present if canDelete is false`() { + val state = aMediaDetailsBottomSheetState( + canDelete = false, + ) + rule.setMediaDetailsBottomSheet( + state = state, + ) + rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertDoesNotExist() + } +} + +private fun AndroidComposeTestRule.setMediaDetailsBottomSheet( + state: MediaBottomSheetState.MediaDetailsBottomSheetState, + onViewInTimeline: (EventId) -> Unit = EnsureNeverCalledWithParam(), + onShare: (EventId) -> Unit = EnsureNeverCalledWithParam(), + onDownload: (EventId) -> Unit = EnsureNeverCalledWithParam(), + onDelete: (EventId) -> Unit = EnsureNeverCalledWithParam(), + onDismiss: () -> Unit = EnsureNeverCalled(), +) { + setContent { + MediaDetailsBottomSheet( + state = state, + onViewInTimeline = onViewInTimeline, + onShare = onShare, + onDownload = onDownload, + onDelete = onDelete, + onDismiss = onDismiss, + ) + } +} From 1e49ef7962dab18d45bcf738f4f7613df81d6b8b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 16:39:23 +0100 Subject: [PATCH 190/203] Enable MediaGallery in prod. --- .../element/android/libraries/featureflag/api/FeatureFlags.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index b028978c94..4668d6db7d 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -158,7 +158,7 @@ enum class FeatureFlags( key = "feature.media_gallery", title = "Allow user to open the media gallery", description = null, - defaultValue = { buildMeta -> buildMeta.buildType != BuildType.RELEASE }, + defaultValue = { true }, isFinished = false, ), EventCache( From 32436ecc92143792d0c33111dc1c9da1a0964632 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 19 Dec 2024 16:06:25 +0000 Subject: [PATCH 191/203] Update screenshots --- ...iaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png | 4 ++-- ...viewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png | 4 ++-- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png | 4 ++-- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png | 4 ++-- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png | 4 ++-- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png | 4 ++-- ...mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png | 4 ++-- ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png | 4 ++-- ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png | 4 ++-- ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png | 4 ++-- ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png | 4 ++-- ...diaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png | 4 ++-- ...es.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png | 2 +- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png | 4 ++-- ....mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png | 4 ++-- ...ibraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png | 4 ++-- ...ibraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png | 4 ++-- ...ibraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png | 4 ++-- ...libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png | 4 ++-- 59 files changed, 117 insertions(+), 117 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png index ceac4aade2..be45c32ec9 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c7ba307cf21056623bf35c8558809fdbc6deaacf4e9365a99ffcf829e8d9188 -size 34477 +oid sha256:4cded4f64be360fbd6ba607f9303e17154da24220712cf2e8da2d495b50bda26 +size 38103 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png index 8a0b19ace2..440c3309cf 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:724bff130c547e7f0065ccb4c1b3319162ded2e6c1c1db666f4e08e01289a5a0 -size 32776 +oid sha256:de4cec2f60dda00375c6583fb2926cc0fdfa02d4673bd3d99bbe0ca3a2193952 +size 36454 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png index f5caf4274b..f5cb7d82cd 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70f93330adb987d6f98d654670ab0898957d765ad3d92a47c9a1c781b24f9059 -size 5317 +oid sha256:00159ed8d968d53970e4a3b7f82ab542fb5eabfc4513dd68d0eef05f0615373e +size 7290 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png index 1821a79941..6665c107c1 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ffac5911f928411fa0ac94e9ac59f6b8bb8bce1016e06f348d946f9d10053e5 -size 4539 +oid sha256:efb12b63fde67256b255503d00848d64480e142c54619329b75aeed451a3dd17 +size 6375 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png index 43d55840d9..9c2e83b3ac 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0ee87589ec0e4f7cf67775dfa69cd289aeb27f22087bd91d54102923a28557d -size 4775 +oid sha256:ebe27622d5f1928701e441847dacded71a7de30df249253af663d45365e2ac40 +size 6261 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png index f743d18269..09686dc48d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6504e09eb09a9e28bc70594e669ec87abd290b4fe20e2ee9b3588c2116a049cd -size 3994 +oid sha256:cc2262a853ee32773e273b0795fdac5da1551fe0b104fcd08f43843b70123873 +size 5418 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png index e1069af79c..1c0a01c77f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d0b3f44a9a0ed9ab16192b23ccf95b7d34abccd025bb4a7ccd8ebb7a9379965 -size 10824 +oid sha256:4444ea352a367bb8617e9be4c86368b0a2292916a20a0c166933b219617e6055 +size 8502 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png index 7129e15299..f0839c5104 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60f281d970321442e39b350f4f697c7ecfc9bc32000cd19676ea7ed6468ee63a -size 11411 +oid sha256:eb103b365f83667834ccf8e6a181ab59d1c9dcbecf6fd4eb7f16bb236444b4e0 +size 9087 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png index 070290ee09..859c045a16 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25386b28846ae826701c9e53530cdd1e5fda4d0899673394bf6d81dac1ca751f -size 11057 +oid sha256:0ce431cefbd22d8232af06626336e4c3baccbf9ad32e88f662efab66c7640b0d +size 8737 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png index 6678d08a23..2e0831e58a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:740923d3e740dc98a5d6890f8f8dfb5810606d99e81a4e6597078566194f076d -size 11290 +oid sha256:d2a9292c525e7fe4d72362f2fe04a9f1184c7dff951f3b517e55f6f369214324 +size 8992 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png index f673979846..4387dbac9d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e18741850e92314b40da89c9e1cafd1795397960151f7bb4e221ae3866d25f9d -size 11497 +oid sha256:d10b446f168e8a1295c811c57a08897db6636935646cd6c314eb7b6b7e310d5c +size 9184 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png index 3e5ffd3185..c69aff8a3f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73fecb12c33892a9bea41e5a4bbbf4db43990e44b2a36b96d03d7a81046c8f92 -size 10076 +oid sha256:44b37f78992762569431bed9330140037b5fe9e49bb0043e2bb94cdfc2b53844 +size 7989 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png index afab3fd152..2acff7a04e 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19c209ec0dca3c3b722af63dcd33444d1074749afd47a8364da4a7a071fce8aa -size 10680 +oid sha256:54f3371f7003d7fe40dea4968037fbd4e148449e431356e693c7951a2814dab6 +size 8592 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png index 1ff42298f8..01146031c3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dabd54db78ae93b72cf9ee31fc4b153fe2ccf64869d861fd16995e16494f4d67 -size 10423 +oid sha256:bbe8efde3b6f6f13558eb323b7774640a269c66a5fe4b0c44e5b60fc6bccf4c6 +size 8329 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png index 0cef938ec6..38b1cfb67e 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ea12dad2ab5c70aa30bc506343f0fce05e08a2940460ec595216d07a244fc84 -size 10583 +oid sha256:e3935b49178db454d1cd20270a1641d380f5f3e576548d74e194fe7619bff4c8 +size 8492 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png index 132a446fae..a1e77b2b01 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemViewPlay_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:470f7d0a1ceedcc0f9ade603c590101da8231b24745283976b3528a84e72d721 -size 10743 +oid sha256:0796d9962b93631f27338f9e93b9f7812964cd830cc1d62903829fd8012865cf +size 8653 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png index 7d108d9701..7234dad634 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f09ea644568c7ab55f0cfc3b8148d8e7821313852cc3fcd78dca2b473a07555 -size 10871 +oid sha256:b98131ca183aaa3fd550f2b78317d73fb63ad03122c298d56d451d4023fd61d3 +size 8548 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png index 543d484800..6e460fc429 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a45e80573b7db0c47cbe9048c1bb95d66e4b8437dd4816cf6cb3548305549715 -size 13017 +oid sha256:e0fb23e74fded45ea3d56cd69c2675efa2c0c0190b17bc8b4cc3ffbb2715650e +size 10772 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png index 80ea9026f2..d9e5fffc64 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:339a6ae86094fb3d0b99d6ea3b23bed14adf7bbe90bc55613d6ece3356f7be16 -size 38566 +oid sha256:a52ddb20b4f07ecdfc23f5d19a26832d8d61cbc25159ac2868e9ff56d844e755 +size 36271 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png index 53ec45dc2a..872a6e3520 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19b6feb0228df79d7f6ec505a8b073852e4400366027d93feb61c09186494ac6 -size 9228 +oid sha256:7a52b93594d74e66ac4c5145254feb4a5b82941783defd0e8820c6acc21cac97 +size 6900 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png index 6f74347dfc..5d2152939f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7fdc491e73aa3fe1afb52ac6a22b3ced09c73573dc175c415d116293c04c95d -size 10129 +oid sha256:3d11986925680f2b7a9827858396e85ad13792b84412d01f5589a32f1a48c631 +size 8038 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png index bad0c59624..310557b2bd 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f816378db978ff00cd722fd44b8219b6181d9cc6fbf4d3b31fea33430e37580e -size 12309 +oid sha256:d440f08f846bec01f07d10ab2347dd5e863ae594109038dc9e6e4e40fe9528d0 +size 10239 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png index 24de50d08d..ea2f0891af 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0b97a65622307f289695d3a19aa0957fe739d35070b1f0cd972f7ff51987a7d -size 36824 +oid sha256:ee9ef033060816da98e228abfeea3d09c2afc8822b884f1d656646dc3d15ce02 +size 34685 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png index 2c81f6a60f..677aaf260b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b71ca3fcc1f94d89264f3d5c5751f46681014b662bcf7d910263dfb23178e0f -size 8623 +oid sha256:1957dbe8c580b048fc32877639d42e3761b10a298dc80804634481ef391dcf76 +size 6544 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png index 14e6352729..f7d527bf6d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29d55f260237060e5ac280d6c87f5eefbdaa9dc6710572cab38fbd41dea77090 -size 15465 +oid sha256:1853de49049bcd1448cc4e7c4a38ed8ab3cf11c3d11b1ddf296f2b6a37e985b4 +size 15428 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png index ef0ab7b7ee..9cec961966 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:925df53e0666d800b0f047f45abda50f1744ef1571594be50efd6008ed988b72 -size 14549 +oid sha256:d90abb10774208a842a3284346c3fa1d3c8e34b53daf6ea0f14f61bf4be5bb87 +size 14551 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png index 14e6352729..f7d527bf6d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29d55f260237060e5ac280d6c87f5eefbdaa9dc6710572cab38fbd41dea77090 -size 15465 +oid sha256:1853de49049bcd1448cc4e7c4a38ed8ab3cf11c3d11b1ddf296f2b6a37e985b4 +size 15428 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png index 14e65ac8bd..0d080b8cd0 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08b47d8b631032d7cb33cdd150eab67e4dc9e6498813e0f5107007c30143d249 -size 26058 +oid sha256:e74c3ab733e969059c4d8bb72903ab53aeb855f49857656bfe61f4dee574dbe4 +size 70317 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png index 90b2f3dad1..9807f8bd54 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:873e124ea0560fe577b93b5e55e959baf443eedcb07d6b229f0370621ee0dbf3 -size 20621 +oid sha256:4ee8d9e829efc0723d62411e2069a8dac90b18069d1d8cd5dc5ec6a5b9899a14 +size 21572 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png index 5544b7f86f..1f1b966fff 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88c68143c2bc7b098664d881bb6c0f2c35ac10ec690c0c7eb7ae6d9366144e83 -size 15136 +oid sha256:7d1cdeba30c6efca096c3625613ce5ad33b6f09e08eb09b2758d9a039e4206fb +size 15106 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png index 5544b7f86f..1f1b966fff 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88c68143c2bc7b098664d881bb6c0f2c35ac10ec690c0c7eb7ae6d9366144e83 -size 15136 +oid sha256:7d1cdeba30c6efca096c3625613ce5ad33b6f09e08eb09b2758d9a039e4206fb +size 15106 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png index 38be3c256b..07edef2141 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2d9ef7383d17436738abf01c450ad189ebffebcfbee12de253fdc92f6feda93 -size 28593 +oid sha256:cbcf086763463eaa1dbf9cb52620c430f7a7982f01d3abcd039ebd307544f8e7 +size 72986 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png index 418b871733..ca39d4ccea 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8b23c6c8f47f7e49cdb56098408873aa9d3f02af22cae01add580f0e623678d -size 34814 +oid sha256:ff96b22724d7f82b3003a73a560da0a34c9c196757b4336706b5823bbfa32589 +size 32398 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png index 319ab0eca4..cc888a4d57 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25ebb7460550b31bf2cebfca7bdab32e5f89e327b68f6687bf490e5d14cb9220 -size 40950 +oid sha256:492b5ae698dc52672d8d0a4599c9cd9a5b6f414e8a0a6f42c91e765e5a5b221d +size 40921 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png index 90c8032c3e..5d694d039b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fff7687206e1b1c03ecc4da231e8d030ec730573070550f1d6a7c355ba0c90d2 +oid sha256:782fa9e5501e399d4840c0aab6ee317aa4fa8137eab93ee85924ec512b071be1 size 14525 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png index a8e5914937..fa9f362ab4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e379008ac3a5346b727bd9355a4a59b75a1a2eca8855ad4ef5b6d7c239da129b -size 15076 +oid sha256:739e2618bac9233b0ff7335d734d7fb594e3ee8860f9e61ef80d2dc4d7736a27 +size 15026 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png index 2ffaf9289e..6ead8ed866 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3fe3f3e7668a3d529132172366622db970391dcc8718f87a7fc90ec67b93ed1 -size 13992 +oid sha256:49e6a6bda914fc5e77bd0a864900f4fd7f654f4017a331be6008825b2150340d +size 14013 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png index a8e5914937..fa9f362ab4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e379008ac3a5346b727bd9355a4a59b75a1a2eca8855ad4ef5b6d7c239da129b -size 15076 +oid sha256:739e2618bac9233b0ff7335d734d7fb594e3ee8860f9e61ef80d2dc4d7736a27 +size 15026 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png index 47b5c76148..8844a9f4a3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a99c2ae96a72551e0da2e6e3cce08a76fffc2f99763c81d5c3fe65a9604bbdc7 -size 25564 +oid sha256:9833ad112df471f8be9587999f444ad371ae1eebadfc351cea83c6db5685c9ad +size 62028 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png index d936b7b1a6..917a76975c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7c98ab469dd76beea196cfceff2ea9f72c9225f15b701ab7965b54cc9064dd8 -size 19505 +oid sha256:3a0a2786bb1184cff104853390793db33b358d8956477455b19eae319f330afc +size 20814 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png index 30b2d63dd7..73e39f4bd6 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d414cb2e4268815943ce6d78fe8d85f576e055bd8ff4f652b7e15a664e3d8cb3 -size 14597 +oid sha256:8be4c40bcec9cb84bd087dc395be694b614f88ff5b44a33dafc3ad87576d23c6 +size 14578 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png index 30b2d63dd7..73e39f4bd6 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d414cb2e4268815943ce6d78fe8d85f576e055bd8ff4f652b7e15a664e3d8cb3 -size 14597 +oid sha256:8be4c40bcec9cb84bd087dc395be694b614f88ff5b44a33dafc3ad87576d23c6 +size 14578 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png index b9a56226c4..700079a0db 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfe8f753f87d3b67ad3ffde246dba0438120bb2a28efe033cbfd2204664fee95 -size 27633 +oid sha256:267cc528f2fdaba66bfad4f8c8622087b76c2e3409f5fda8ce25009039278a22 +size 64356 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png index 4c08d0a7f4..2149a46ba7 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e361247dd137a828f1170fc1497ace365e04bb551edcd4984c26b15c26fe65f1 -size 33011 +oid sha256:7f5831741183467f1d05517097f2617aee405a9d6752cdf8a8e193e5851376a3 +size 30904 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png index 8e10c8a8aa..158c181240 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39ecc9a50285df492417f5f22adcc391be2dcad0cc388efb756274c83aba077d -size 39030 +oid sha256:de98370531bc9342539bbf98b6f3534b72e327a94e34b1c6d827e2330291340c +size 39235 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png index 506cb4f837..4ae1e1c6cf 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd4903a8a383c5fe3c8c5bfeaf26ab16d3c785fccaab5b66de8b31f3380e9272 -size 14104 +oid sha256:c08080c2814f8e8273949b39359ad105f0305ee6c7b91ddf9b437ce925489b40 +size 14125 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png index 7214efd2c9..fd5c2af6e6 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:043da4a779363f5162b1fbb6b1159ab3ae3f6a1635473146a5b73242525b5e53 -size 390373 +oid sha256:66437179fb0b851d4d4d647d00cab94cc7422d625f559839c675b378dbf1af38 +size 389408 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png index a8777362d7..4a43ce31da 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d5f183f53f9e8d0dbbae473f2f853f4372dbff15b1d6ea17e78b4770781fa34 -size 35468 +oid sha256:8165bcb4b0d52a227aad4e1f3951fc3628ef647947ef2584ed50d8ede8a6a344 +size 38248 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png index 70f2f64c13..f3d0c19a9f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:514f67acd325e01d010466786eb85e7db8071c36e2f21454be8f30c4a6a57425 -size 32356 +oid sha256:c9efb4ed1ee82bba30351bf213f0873637e8194140a3bbea669321bf76bc6483 +size 31449 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png index e18fc2c87b..6d8afe1140 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a6a599e68f0955d84ea737603f0db83be412433691f7b7b3729a01999808830 -size 25827 +oid sha256:5d2882d79b9f66726c6d16c8c6fc84cb9f65a5674222fe85794229dc5ba12a6b +size 24679 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png index 1ad4e9e788..12d5df3fa1 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2c00187eb25b297f7debb7424969e5535e48366d139b7f073b6a7628f155d60 -size 390395 +oid sha256:da172fdf40dc8702bc6dcb89bcc75e93bd279f6bbb9454f5283febe4da25d399 +size 389440 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png index 9511942ab8..e3b32d36d3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94520145bde5de6a0d7820dd44d7f0243d9fb06eca062b19b72cb2457abdfb7d -size 95438 +oid sha256:bb79e754f9b4caeb40508bdc067d68a4e115e8a50467fc006be6f5db0684ea5b +size 94672 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png index 475bdd1991..40ff36cd94 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82710704b6daff1c1e12a4a3c782f68744c0d6b3ee30b3fa1e38739176c6eaef -size 397724 +oid sha256:7ad6e45382dec9bb27593b6e2ed92ed633204479040ccebaf4d362bb2f41fec7 +size 396403 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png index 685149c94d..6b0463bba7 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1547883dfc7ae3d742d2da2f6e5c6c2d6182d6039b3ce6075dbbe1d24f4ba341 -size 23370 +oid sha256:77913c010877d13d82196182d32e19168e77102d2f245ab321c2224a7108768a +size 21874 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png index 57c533a361..3603786361 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66add7cb3b696075b7e931b06d1d8472cfddc2fc1902081864c3d88754f7404a -size 6702 +oid sha256:69b5d7572ae6e4ff084867fac1bae41a55c75c9a5236cb6ccb4c31b89ef77898 +size 5442 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png index 2142adf1d5..6b6e6a655d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a86ec4a40a63f646e62b1c6a3e481f0b68be442923e39b6750836ae4e5ac3045 -size 15577 +oid sha256:f8d3e3f8733424870b254be90599ed1ff6ba784089600bcb200fbef62c81537c +size 14562 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png index 8f3f77a739..32e7fcef89 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0abf1fdae34ea898f817f9667a02adb551d5e3bb528f73b4c2c01826f4ca375a -size 15881 +oid sha256:d1785d90957316791969f047e66bd779da62d675004914099f2af2b69bebe405 +size 14700 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png index a155681e56..dc33c0aef4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7642e9e20a551e59f4529c52fa7fbe5b3f4dfcb8c26caeb716ccb2bdfc63dde -size 27176 +oid sha256:1ddcd8e9e20de4171a3d9f8175806a268723e47a14dca431849c2c29edaf5d0b +size 26267 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png index bd1487d158..d115aaa7ed 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fd53c24dd38a12b4ceefa54e1d6d096d8f443d1cdd1d1f1cc71f92c1d603a51 -size 27419 +oid sha256:035ef0079af6e9825a52b86e2eab50667404a66d70dd2756596b60cc1cea376a +size 26404 From 1e0938099fe6a7425b894249a0cadfb1145cf2d8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2024 17:26:27 +0100 Subject: [PATCH 192/203] Fix color issue. --- .../libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt index 01aba3c20d..0837bd73b2 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt @@ -88,8 +88,8 @@ private fun VideoInfoRow( .background( brush = Brush.verticalGradient( colors = listOf( - Color.White.copy(alpha = 0f), - Color.White, + ElementTheme.colors.bgCanvasDefault.copy(alpha = 0f), + ElementTheme.colors.bgCanvasDefault, ) ) ) From e5a057652d03fbd395e9e203dd206d9a60820c13 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 19 Dec 2024 16:37:47 +0000 Subject: [PATCH 193/203] Update screenshots --- ...s.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png index 9c2e83b3ac..d58f4c3155 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebe27622d5f1928701e441847dacded71a7de30df249253af663d45365e2ac40 -size 6261 +oid sha256:0f6835bd79d18d202c1d21b00a1afa4fa2c7cbfaf8f586a1dd1f48afdd5f69e5 +size 7644 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png index 09686dc48d..dc363be75b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc2262a853ee32773e273b0795fdac5da1551fe0b104fcd08f43843b70123873 -size 5418 +oid sha256:452afc2e04191eb82de772597ee97987eda5667ff56ecb684bb3b9e0bef90435 +size 6737 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png index 917a76975c..5c067f246f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a0a2786bb1184cff104853390793db33b358d8956477455b19eae319f330afc -size 20814 +oid sha256:fcb5bc041286ba863ae982b2ad03873a76e48ed6ebd5d35c82dea269d86363a7 +size 21189 From 66a79aef6b55ea5530d5068fd95b9728e04d13cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:40:23 +0000 Subject: [PATCH 194/203] Update media3 to v1.5.1 --- 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 8273f4af5b..4797bdd92f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ constraintlayout = "2.2.0" constraintlayout_compose = "1.1.0" lifecycle = "2.8.7" activity = "1.9.3" -media3 = "1.5.0" +media3 = "1.5.1" camera = "1.4.1" # Compose From 189cc5c58eccd4438cb704cccaf0ecf986870f12 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 19 Dec 2024 19:31:53 +0100 Subject: [PATCH 195/203] knock requests : add KnockRequestsException --- .../impl/data/KnockRequestsException.kt | 13 +++++++++++++ .../knockrequests/impl/data/KnockRequestsService.kt | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt new file mode 100644 index 0000000000..0880233ff6 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.data + +sealed class KnockRequestsException : Exception() { + data object AcceptAllPartiallyFailed : KnockRequestsException() + data object KnockRequestNotFound : KnockRequestsException() +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt index d8a594f148..fb08821387 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt @@ -105,7 +105,7 @@ class KnockRequestsService( if (results.all { it.isSuccess }) { Result.success(Unit) } else { - Result.failure(IllegalStateException("Failed to accept all knock requests")) + Result.failure(KnockRequestsException.AcceptAllPartiallyFailed) } } @@ -140,6 +140,6 @@ class KnockRequestsService( } } } - - private fun knockRequestNotFoundResult() = Result.failure(IllegalArgumentException("Knock request not found")) } + +private fun knockRequestNotFoundResult() = Result.failure(KnockRequestsException.KnockRequestNotFound) From 602b891bd0be5b1c0e7eba9057c2afca1a668b35 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 19 Dec 2024 19:34:11 +0100 Subject: [PATCH 196/203] knock requests : rename fixture aKnockRequestPresentable --- .../KnockRequestsBannerStateProvider.kt | 20 ++++++++--------- .../impl/data/KnockRequestFixture.kt | 2 +- .../list/KnockRequestsListStateProvider.kt | 22 +++++++++---------- .../banner/KnockRequestsBannerViewTest.kt | 8 +++---- .../impl/list/KnockRequestsListViewTest.kt | 16 +++++++------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt index 460d1c7462..0324239fd9 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -9,7 +9,7 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable -import io.element.android.features.knockrequests.impl.data.aKnockRequest +import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable import kotlinx.collections.immutable.toImmutableList class KnockRequestsBannerStateProvider : PreviewParameterProvider { @@ -18,7 +18,7 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider = listOf(aKnockRequest()), + knockRequests: List = listOf(aKnockRequestPresentable()), displayAcceptError: Boolean = false, canAccept: Boolean = true, isVisible: Boolean = true, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt index 5fbc2bb047..cfecb8355e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt @@ -10,7 +10,7 @@ package io.element.android.features.knockrequests.impl.data import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId -fun aKnockRequest( +fun aKnockRequestPresentable( eventId: EventId = EventId("\$eventId"), userId: UserId = UserId("@jacob_ross:example.com"), displayName: String? = "Jacob Ross", diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index f6d5aba7f6..7b5ee23353 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -10,7 +10,7 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable -import io.element.android.features.knockrequests.impl.data.aKnockRequest +import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId @@ -31,14 +31,14 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider() - val knockRequest = aKnockRequest() + val knockRequest = aKnockRequestPresentable() rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf(knockRequest)), @@ -62,7 +62,7 @@ class KnockRequestsListViewTest { @Test fun `clicking on decline emit the expected event`() { val eventsRecorder = EventsRecorder() - val knockRequest = aKnockRequest() + val knockRequest = aKnockRequestPresentable() rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf(knockRequest)), @@ -76,7 +76,7 @@ class KnockRequestsListViewTest { @Test fun `clicking on decline and ban emit the expected event`() { val eventsRecorder = EventsRecorder() - val knockRequest = aKnockRequest() + val knockRequest = aKnockRequestPresentable() rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf(knockRequest)), @@ -90,7 +90,7 @@ class KnockRequestsListViewTest { @Test fun `clicking on accept all emit the expected event`() { val eventsRecorder = EventsRecorder() - val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), @@ -104,7 +104,7 @@ class KnockRequestsListViewTest { @Test fun `retry on async view retry emit the expected event`() { val eventsRecorder = EventsRecorder() - val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), @@ -120,7 +120,7 @@ class KnockRequestsListViewTest { @Test fun `canceling async view emit the expected event`() { val eventsRecorder = EventsRecorder() - val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), @@ -136,7 +136,7 @@ class KnockRequestsListViewTest { @Test fun `confirming async view emit the expected event`() { val eventsRecorder = EventsRecorder() - val knockRequests = persistentListOf(aKnockRequest(), aKnockRequest()) + val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) rule.setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), From f4f669cbbbcd16a3daa73c45dde35c6d4aade3b9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 19 Dec 2024 20:08:14 +0100 Subject: [PATCH 197/203] knock requests : rename KnockRequestsActionTarget to KnockRequestAction --- .../impl/list/KnockRequestsListPresenter.kt | 42 +++++++++---------- .../impl/list/KnockRequestsListState.kt | 14 +++---- .../list/KnockRequestsListStateProvider.kt | 10 ++--- .../impl/list/KnockRequestsListView.kt | 42 +++++++++---------- .../list/KnockRequestsListPresenterTest.kt | 24 +++++------ .../impl/list/KnockRequestsListViewTest.kt | 6 +-- 6 files changed, 69 insertions(+), 69 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index 13c63d2d02..6ea13f16a1 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -30,7 +30,7 @@ class KnockRequestsListPresenter @Inject constructor( @Composable override fun present(): KnockRequestsListState { val asyncAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - var actionTarget by remember { mutableStateOf(KnockRequestsActionTarget.None) } + var currentAction by remember { mutableStateOf(KnockRequestsAction.None) } val permissions by knockRequestsService.permissionsFlow.collectAsState() val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState() @@ -40,36 +40,36 @@ class KnockRequestsListPresenter @Inject constructor( fun handleEvents(event: KnockRequestsListEvents) { when (event) { KnockRequestsListEvents.AcceptAll -> { - actionTarget = KnockRequestsActionTarget.AcceptAll + currentAction = KnockRequestsAction.AcceptAll } is KnockRequestsListEvents.Accept -> { - actionTarget = KnockRequestsActionTarget.Accept(event.knockRequest) + currentAction = KnockRequestsAction.Accept(event.knockRequest) } is KnockRequestsListEvents.Decline -> { - actionTarget = KnockRequestsActionTarget.Decline(event.knockRequest) + currentAction = KnockRequestsAction.Decline(event.knockRequest) } is KnockRequestsListEvents.DeclineAndBan -> { - actionTarget = KnockRequestsActionTarget.DeclineAndBan(event.knockRequest) + currentAction = KnockRequestsAction.DeclineAndBan(event.knockRequest) } KnockRequestsListEvents.ResetCurrentAction -> { asyncAction.value = AsyncAction.Uninitialized - actionTarget = KnockRequestsActionTarget.None + currentAction = KnockRequestsAction.None } KnockRequestsListEvents.RetryCurrentAction -> { - coroutineScope.executeAction(actionTarget, asyncAction, isActionConfirmed = true) + coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true) } KnockRequestsListEvents.ConfirmCurrentAction -> { - coroutineScope.executeAction(actionTarget, asyncAction, isActionConfirmed = true) + coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true) } } } - LaunchedEffect(actionTarget) { - executeAction(actionTarget, asyncAction, isActionConfirmed = false) + LaunchedEffect(currentAction) { + executeAction(currentAction, asyncAction, isActionConfirmed = false) } return KnockRequestsListState( knockRequests = knockRequests, - actionTarget = actionTarget, + currentAction = currentAction, permissions = permissions, asyncAction = asyncAction.value, eventSink = ::handleEvents @@ -77,35 +77,35 @@ class KnockRequestsListPresenter @Inject constructor( } private fun CoroutineScope.executeAction( - actionTarget: KnockRequestsActionTarget, + currentAction: KnockRequestsAction, asyncAction: MutableState>, isActionConfirmed: Boolean, ) = launch { - when (actionTarget) { - is KnockRequestsActionTarget.Accept -> { + when (currentAction) { + is KnockRequestsAction.Accept -> { runUpdatingState(asyncAction) { - knockRequestsService.acceptKnockRequest(actionTarget.knockRequest) + knockRequestsService.acceptKnockRequest(currentAction.knockRequest) } } - is KnockRequestsActionTarget.Decline -> { + is KnockRequestsAction.Decline -> { if (isActionConfirmed) { runUpdatingState(asyncAction) { - knockRequestsService.declineKnockRequest(actionTarget.knockRequest) + knockRequestsService.declineKnockRequest(currentAction.knockRequest) } } else { asyncAction.value = AsyncAction.ConfirmingNoParams } } - is KnockRequestsActionTarget.DeclineAndBan -> { + is KnockRequestsAction.DeclineAndBan -> { if (isActionConfirmed) { runUpdatingState(asyncAction) { - knockRequestsService.declineAndBanKnockRequest(actionTarget.knockRequest) + knockRequestsService.declineAndBanKnockRequest(currentAction.knockRequest) } } else { asyncAction.value = AsyncAction.ConfirmingNoParams } } - is KnockRequestsActionTarget.AcceptAll -> { + is KnockRequestsAction.AcceptAll -> { if (isActionConfirmed) { runUpdatingState(asyncAction) { knockRequestsService.acceptAllKnockRequests() @@ -114,7 +114,7 @@ class KnockRequestsListPresenter @Inject constructor( asyncAction.value = AsyncAction.ConfirmingNoParams } } - KnockRequestsActionTarget.None -> Unit + KnockRequestsAction.None -> Unit } } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 3447034afd..fa33b074a5 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -16,7 +16,7 @@ import kotlinx.collections.immutable.ImmutableList data class KnockRequestsListState( val knockRequests: AsyncData>, - val actionTarget: KnockRequestsActionTarget, + val currentAction: KnockRequestsAction, val asyncAction: AsyncAction, val permissions: KnockRequestPermissions, val eventSink: (KnockRequestsListEvents) -> Unit, @@ -25,10 +25,10 @@ data class KnockRequestsListState( } @Immutable -sealed interface KnockRequestsActionTarget { - data object None : KnockRequestsActionTarget - data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsActionTarget - data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsActionTarget - data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsActionTarget - data object AcceptAll : KnockRequestsActionTarget +sealed interface KnockRequestsAction { + data object None : KnockRequestsAction + data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsAction + data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsAction + data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsAction + data object AcceptAll : KnockRequestsAction } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 7b5ee23353..a8d898b08e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -65,7 +65,7 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), - actionTarget: KnockRequestsActionTarget = KnockRequestsActionTarget.None, + currentAction: KnockRequestsAction = KnockRequestsAction.None, asyncAction: AsyncAction = AsyncAction.Uninitialized, permissions: KnockRequestPermissions = KnockRequestPermissions( canAccept = true, @@ -142,7 +142,7 @@ fun aKnockRequestsListState( eventSink: (KnockRequestsListEvents) -> Unit = {}, ) = KnockRequestsListState( knockRequests = knockRequests, - actionTarget = actionTarget, + currentAction = currentAction, asyncAction = asyncAction, permissions = permissions, eventSink = eventSink, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 2606bf26c4..09f916ae09 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -153,7 +153,7 @@ private fun KnockRequestsListContent( else -> Unit } KnockRequestsActionsView( - actionTarget = state.actionTarget, + currentAction = state.currentAction, asyncAction = state.asyncAction, onConfirm = { state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) @@ -181,7 +181,7 @@ private fun KnockRequestsListContent( @Composable private fun KnockRequestsActionsView( - actionTarget: KnockRequestsActionTarget, + currentAction: KnockRequestsAction, asyncAction: AsyncAction, onConfirm: () -> Unit, onDismiss: () -> Unit, @@ -195,20 +195,20 @@ private fun KnockRequestsActionsView( onErrorDismiss = onDismiss, confirmationDialog = { KnockRequestActionConfirmation( - actionTarget = actionTarget, + currentAction = currentAction, onSubmit = onConfirm, onDismiss = onDismiss, ) }, progressDialog = { - KnockRequestActionProgress(target = actionTarget) + KnockRequestActionProgress(target = currentAction) }, errorMessage = { - when (actionTarget) { - is KnockRequestsActionTarget.Accept -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) - is KnockRequestsActionTarget.Decline -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description) - is KnockRequestsActionTarget.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description) - KnockRequestsActionTarget.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_failed_alert_description) + when (currentAction) { + is KnockRequestsAction.Accept -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description) + is KnockRequestsAction.Decline -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description) + is KnockRequestsAction.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description) + KnockRequestsAction.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_failed_alert_description) else -> "" } }, @@ -219,25 +219,25 @@ private fun KnockRequestsActionsView( @Composable private fun KnockRequestActionConfirmation( - actionTarget: KnockRequestsActionTarget, + currentAction: KnockRequestsAction, onSubmit: () -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { - val (title, content, submitText) = when (actionTarget) { - KnockRequestsActionTarget.AcceptAll -> Triple( + val (title, content, submitText) = when (currentAction) { + KnockRequestsAction.AcceptAll -> Triple( stringResource(R.string.screen_knock_requests_list_accept_all_alert_title), stringResource(R.string.screen_knock_requests_list_accept_all_alert_description), stringResource(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title), ) - is KnockRequestsActionTarget.Decline -> Triple( + is KnockRequestsAction.Decline -> Triple( stringResource(R.string.screen_knock_requests_list_decline_alert_title), - stringResource(R.string.screen_knock_requests_list_decline_alert_description, actionTarget.knockRequest.getBestName()), + stringResource(R.string.screen_knock_requests_list_decline_alert_description, currentAction.knockRequest.getBestName()), stringResource(R.string.screen_knock_requests_list_decline_alert_confirm_button_title), ) - is KnockRequestsActionTarget.DeclineAndBan -> Triple( + is KnockRequestsAction.DeclineAndBan -> Triple( stringResource(R.string.screen_knock_requests_list_ban_alert_title), - stringResource(R.string.screen_knock_requests_list_ban_alert_description, actionTarget.knockRequest.getBestName()), + stringResource(R.string.screen_knock_requests_list_ban_alert_description, currentAction.knockRequest.getBestName()), stringResource(R.string.screen_knock_requests_list_ban_alert_confirm_button_title), ) else -> return @@ -254,14 +254,14 @@ private fun KnockRequestActionConfirmation( @Composable private fun KnockRequestActionProgress( - target: KnockRequestsActionTarget, + target: KnockRequestsAction, modifier: Modifier = Modifier, ) { val progressText = when (target) { - is KnockRequestsActionTarget.Accept -> stringResource(R.string.screen_knock_requests_list_accept_loading_title) - is KnockRequestsActionTarget.Decline -> stringResource(R.string.screen_knock_requests_list_decline_loading_title) - is KnockRequestsActionTarget.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_ban_loading_title) - KnockRequestsActionTarget.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_loading_title) + is KnockRequestsAction.Accept -> stringResource(R.string.screen_knock_requests_list_accept_loading_title) + is KnockRequestsAction.Decline -> stringResource(R.string.screen_knock_requests_list_decline_loading_title) + is KnockRequestsAction.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_ban_loading_title) + KnockRequestsAction.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_loading_title) else -> return } ProgressDialog( diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt index 010a921515..d74155ead1 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt @@ -69,7 +69,7 @@ class KnockRequestsListPresenterTest { skipItems(1) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.Accept(knockRequestPresentable)) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Accept(knockRequestPresentable)) assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> @@ -79,7 +79,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None) assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() } assert(acceptLambda).isCalledOnce() @@ -103,7 +103,7 @@ class KnockRequestsListPresenterTest { skipItems(1) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.Accept(knockRequestPresentable)) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Accept(knockRequestPresentable)) assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> @@ -120,7 +120,7 @@ class KnockRequestsListPresenterTest { skipItems(1) awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None) assertThat(state.knockRequests.dataOrNull()).hasSize(1) } assert(acceptLambda).isCalledExactly(2) @@ -144,7 +144,7 @@ class KnockRequestsListPresenterTest { skipItems(1) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.Decline(knockRequestPresentable)) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Decline(knockRequestPresentable)) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) } @@ -158,7 +158,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None) assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() } } @@ -182,7 +182,7 @@ class KnockRequestsListPresenterTest { skipItems(1) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.DeclineAndBan(knockRequestPresentable)) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.DeclineAndBan(knockRequestPresentable)) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) } @@ -196,7 +196,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None) assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() } } @@ -223,7 +223,7 @@ class KnockRequestsListPresenterTest { } skipItems(1) awaitItem().also { state -> - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.AcceptAll) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) } @@ -237,7 +237,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None) assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty() } } @@ -265,7 +265,7 @@ class KnockRequestsListPresenterTest { } skipItems(1) awaitItem().also { state -> - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.AcceptAll) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) } @@ -279,7 +279,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(state.actionTarget).isEqualTo(KnockRequestsActionTarget.None) + assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None) assertThat(state.knockRequests.dataOrNull()).hasSize(1) } } diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt index 2de89aef58..af2bfefd16 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt @@ -109,7 +109,7 @@ class KnockRequestsListViewTest { aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), asyncAction = AsyncAction.Failure(Throwable("Failed to accept all")), - actionTarget = KnockRequestsActionTarget.AcceptAll, + currentAction = KnockRequestsAction.AcceptAll, eventSink = eventsRecorder, ), ) @@ -125,7 +125,7 @@ class KnockRequestsListViewTest { aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), asyncAction = AsyncAction.Failure(Throwable("Failed to accept all")), - actionTarget = KnockRequestsActionTarget.AcceptAll, + currentAction = KnockRequestsAction.AcceptAll, eventSink = eventsRecorder, ), ) @@ -141,7 +141,7 @@ class KnockRequestsListViewTest { aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), asyncAction = AsyncAction.ConfirmingNoParams, - actionTarget = KnockRequestsActionTarget.AcceptAll, + currentAction = KnockRequestsAction.AcceptAll, eventSink = eventsRecorder, ), ) From f95caea809208b6c11477c7efbe82f20d3bf8087 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2024 10:15:40 +0100 Subject: [PATCH 198/203] Fix rendering issue in the toolbar. --- .../mediaviewer/impl/viewer/MediaViewerStateProvider.kt | 4 ++-- .../libraries/mediaviewer/impl/viewer/MediaViewerView.kt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 63c82be7ba..863c996b9d 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -41,8 +41,8 @@ open class MediaViewerStateProvider : PreviewParameterProvider ) }, aVideoMediaInfo( - senderName = "Sally Sanderson", - dateSent = "21 NOV, 2024", + senderName = "A very long name so that it will be truncated and will not be displayed on multiple lines", + dateSent = "A very very long date that will be truncated and will not be displayed on multiple lines", caption = "A caption", ).let { aMediaViewerState( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index d3347b9812..4a15f95cea 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -336,11 +336,15 @@ private fun MediaViewerTopBar( text = senderName, style = ElementTheme.typography.fontBodyMdMedium, color = ElementTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) Text( text = dateSent, style = ElementTheme.typography.fontBodySmRegular, color = ElementTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } } From f2bc736c532aa1b771f6e72ee8d6efb760686695 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 20 Dec 2024 10:22:41 +0100 Subject: [PATCH 199/203] fix(timeline) : dispatch timeline creations trying to avoid ANRs --- .../libraries/matrix/impl/room/RustMatrixRoom.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index e672cf9394..d8bc7bf579 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -191,8 +191,8 @@ class RustMatrixRoom( override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId) - override suspend fun timelineFocusedOnEvent(eventId: EventId): Result { - return runCatching { + override suspend fun timelineFocusedOnEvent(eventId: EventId): Result = withContext(roomDispatcher) { + runCatching { innerRoom.timelineFocusedOnEvent( eventId = eventId.value, numContextEvents = 50u, @@ -209,8 +209,8 @@ class RustMatrixRoom( } } - override suspend fun pinnedEventsTimeline(): Result { - return runCatching { + override suspend fun pinnedEventsTimeline(): Result = withContext(roomDispatcher) { + runCatching { innerRoom.pinnedEventsTimeline( internalIdPrefix = "pinned_events", maxEventsToLoad = 100u, @@ -225,8 +225,8 @@ class RustMatrixRoom( } } - override suspend fun mediaTimeline(): Result { - return runCatching { + override suspend fun mediaTimeline(): Result = withContext(roomDispatcher) { + runCatching { innerRoom.messageFilteredTimeline( internalIdPrefix = "MediaGallery_", allowedMessageTypes = listOf( From 70d983ceaf837b1b184f26de6f3ee48aa5ed0c20 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 20 Dec 2024 09:28:11 +0000 Subject: [PATCH 200/203] Update screenshots --- ...libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png index 6b0463bba7..d94989c757 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77913c010877d13d82196182d32e19168e77102d2f245ab321c2224a7108768a -size 21874 +oid sha256:d10cb9be5b5139f0fdfdfb11cc3d3eca1955297180e5db8142bfea6250f20d73 +size 25811 From 6afd8b356a382bf23acc434f921c1f61a653da0b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2024 11:39:48 +0100 Subject: [PATCH 201/203] Fix gallery title --- .../mediaviewer/impl/gallery/MediaGalleryStateProvider.kt | 4 +++- .../libraries/mediaviewer/impl/gallery/MediaGalleryView.kt | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index 87e7599991..8876917bf6 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -25,7 +25,9 @@ import kotlinx.collections.immutable.toImmutableList open class MediaGalleryStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aMediaGalleryState(), + aMediaGalleryState( + roomName = "A long room name that will be truncated", + ), aMediaGalleryState(groupedMediaItems = AsyncData.Loading()), aMediaGalleryState(groupedMediaItems = AsyncData.Success(aGroupedMediaItems())), aMediaGalleryState( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index eec45a0b22..6053695029 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -93,6 +94,8 @@ fun MediaGalleryView( Text( text = state.roomName, style = ElementTheme.typography.aliasScreenTitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) }, navigationIcon = { From d42411293f05c287e3824c1ef885085e3302a281 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 20 Dec 2024 10:51:27 +0000 Subject: [PATCH 202/203] Update screenshots --- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png index f7d527bf6d..b17c686b18 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1853de49049bcd1448cc4e7c4a38ed8ab3cf11c3d11b1ddf296f2b6a37e985b4 -size 15428 +oid sha256:8b04072b9e16342e333c2a10e6462420216b4192226c97f0d4bd5b5ada1aecef +size 18791 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png index fa9f362ab4..8baa855f7b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:739e2618bac9233b0ff7335d734d7fb594e3ee8860f9e61ef80d2dc4d7736a27 -size 15026 +oid sha256:f9d7fecddc7aff63c795dcad68665dc1771544f5facda5a838b1f3391655ee49 +size 18306 From 8e6261ed2444bc3bef534003822fd3cc1eaddef6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 20 Dec 2024 14:40:53 +0100 Subject: [PATCH 203/203] Adding fastlane file for version 0.7.6 --- fastlane/metadata/android/en-US/changelogs/40007060.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40007060.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40007060.txt b/fastlane/metadata/android/en-US/changelogs/40007060.txt new file mode 100644 index 0000000000..1bc0b2f8e6 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40007060.txt @@ -0,0 +1,2 @@ +Main changes in this version: media browser and bug fixes. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file