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
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"
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 {
diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md
index e234275af6..738bd12430 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 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/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/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(
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/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/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt
index 3b5fb67540..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
@@ -55,6 +56,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 +100,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 +440,15 @@ 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,
+ ),
+ waveform = (content as? TimelineItemVoiceContent)?.waveform,
),
mediaSource = mediaSource,
thumbnailSource = thumbnailSource,
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 411ff37c8f..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
@@ -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()
@@ -170,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/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..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
@@ -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(),
@@ -192,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/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/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt
index f700dcc6b1..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
@@ -11,30 +11,30 @@ 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
-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_delete, destructive = true)
- data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options)
- 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),
+ 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),
+ 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/actionlist/model/TimelineItemActionComparator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt
index 8eef2d7619..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
@@ -7,21 +7,25 @@
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,
TimelineItemAction.ReplyInThread,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.Unpin,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
- TimelineItemAction.CopyText,
+ TimelineItemAction.EditPoll,
TimelineItemAction.AddCaption,
TimelineItemAction.EditCaption,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
+ TimelineItemAction.Unpin,
+ TimelineItemAction.CopyText,
TimelineItemAction.CopyCaption,
TimelineItemAction.RemoveCaption,
TimelineItemAction.ViewSource,
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)
}
}
}
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/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/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/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt
index b312024ebb..d34f63ecbd 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt
@@ -36,7 +36,13 @@ open class TimelineItemEncryptedContentProvider : PreviewParameterProvider {
@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/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/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..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
@@ -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,13 +173,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -215,13 +219,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.ReplyInThread,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -263,12 +268,13 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -308,13 +314,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -355,13 +362,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -403,14 +411,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -448,14 +457,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.ReplyInThread,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -496,14 +506,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
)
@@ -542,14 +553,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.CopyLink,
TimelineItemAction.AddCaption,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
@@ -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(
@@ -599,8 +612,8 @@ class ActionListPresenterTest {
TimelineItemAction.Forward,
// Not here
// TimelineItemAction.AddCaption,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
@@ -641,14 +654,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.CopyLink,
TimelineItemAction.EditCaption,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyCaption,
TimelineItemAction.RemoveCaption,
TimelineItemAction.ViewSource,
@@ -691,13 +705,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyCaption,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -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,14 +824,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.Redact,
)
@@ -855,13 +872,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
+ TimelineItemAction.CopyLink,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -909,14 +927,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Unpin,
- TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
+ TimelineItemAction.CopyLink,
+ TimelineItemAction.Unpin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -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,14 +1066,15 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.EndPoll,
TimelineItemAction.Reply,
- TimelineItemAction.Pin,
+ TimelineItemAction.EditPoll,
TimelineItemAction.CopyLink,
- TimelineItemAction.Edit,
+ TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -1089,13 +1110,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.EndPoll,
TimelineItemAction.Reply,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -1131,12 +1153,13 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -1174,13 +1197,14 @@ class ActionListPresenterTest {
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
+ sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
- TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
+ TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -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/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" })
+ }
+ }
+}
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/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(
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/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/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/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/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..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,8 +24,8 @@ 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 io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.advanceUntilIdle
@@ -38,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)
+ }
}
}
@@ -76,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)
+ }
}
}
@@ -107,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)
+ }
}
}
@@ -127,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()
@@ -155,30 +150,31 @@ 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 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()
- advanceUntilIdle()
- logoutCallRecorder.assertions().isCalledOnce()
-
- initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false))
- assertThat(awaitItem().isSimpleSlidingSyncEnabled).isFalse()
- assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse()
- advanceUntilIdle()
- 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)
+ }
}
}
@@ -186,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()
+ }
}
}
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/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/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..f43c081ea3 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()
@@ -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,
)
)
@@ -633,9 +636,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 +653,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/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/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/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/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/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2943814b01..e62f743f79 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,10 +3,10 @@
[versions]
# Project
-android_gradle_plugin = "8.7.2"
-kotlin = "2.0.21"
+android_gradle_plugin = "8.7.3"
+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
@@ -22,17 +22,17 @@ 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"
+compose_bom = "2024.12.01"
composecompiler = "1.5.15"
# Coroutines
coroutines = "1.9.0"
# Accompanist
-accompanist = "0.36.0"
+accompanist = "0.37.0"
# Test
test_core = "1.6.1"
@@ -50,10 +50,10 @@ wysiwyg = "2.37.14"
telephoto = "0.14.0"
# Dependency analysis
-dependencyAnalysis = "2.5.0"
+dependencyAnalysis = "2.6.1"
# DI
-dagger = "2.53"
+dagger = "2.53.1"
anvil = "0.4.0"
# Auto service
@@ -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
@@ -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"
@@ -169,11 +169,11 @@ 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"
-matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.70"
+matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.72"
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" }
@@ -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"
@@ -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"
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
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) {
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..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,9 @@
package io.element.android.libraries.core.extensions
+import java.text.Normalizer
+import java.util.Locale
+
fun Boolean.toOnOff() = if (this) "ON" else "OFF"
fun Boolean.to01() = if (this) "1" else "0"
@@ -68,3 +71,21 @@ 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()
+ }
+ }
+}
+
+fun String.withoutAccents(): String {
+ return Normalizer.normalize(this, Normalizer.Form.NFD)
+ .replace("\\p{Mn}+".toRegex(), "")
+}
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..5632962582
--- /dev/null
+++ b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.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.api
+
+interface DateFormatter {
+ fun format(
+ timestamp: Long?,
+ mode: DateFormatterMode = DateFormatterMode.Full,
+ useRelative: Boolean = false,
+ ): String
+}
+
+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.
+ */
+ 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..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()
@@ -16,15 +16,30 @@ setupAnvil()
android {
namespace = "io.element.android.libraries.dateformatter.impl"
+ testOptions {
+ unitTests {
+ isIncludeAndroidResources = true
+ }
+ }
+
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)
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..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
@@ -7,57 +7,64 @@
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)
+ locale: 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)
- private val dateWithYearFormatter: DateTimeFormatter by lazy {
- val pattern = DateFormat.getBestDateTimePattern(locale, "dd.MM.yyyy") ?: "dd.MM.yyyy"
- DateTimeFormatter.ofPattern(pattern, locale)
- }
-
- private val dateWithFullFormatFormatter: DateTimeFormatter by lazy {
- DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)
+ override fun onLocaleChange() {
+ Timber.w("Locale changed, updating formatters")
+ dateTimeFormatters = DateTimeFormatters(Locale.getDefault())
}
internal fun formatTime(localDateTime: LocalDateTime): String {
- return onlyTimeFormatter.format(localDateTime.toJavaLocalDateTime())
+ return dateTimeFormatters.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 +82,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/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/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-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-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/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..8dd0c61d9f
--- /dev/null
+++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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 kotlinx.datetime.Instant
+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 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"
+ 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")
+ }
+}
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..b7bf9d818e
--- /dev/null
+++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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 kotlinx.datetime.Instant
+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 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"
+ 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")
+ }
+}
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/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..dd1572fde6
--- /dev/null
+++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.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 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.
+ */
+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 },
+ locale = Locale.getDefault(),
+ )
+ 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/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
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/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/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..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
@@ -13,9 +13,7 @@ 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,
R.drawable.ic_plus_composer,
R.drawable.ic_stop,
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 @@
-
-
-
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 @@
-
-
-
-
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/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt
index b1bc3648d7..01f43588a9 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt
@@ -12,4 +12,5 @@ enum class CurrentUserMembership {
JOINED,
LEFT,
KNOCKED,
+ BANNED,
}
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 d028c609ed..8dbd78fab3 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
@@ -241,6 +241,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/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/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/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
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..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
}
@@ -39,6 +41,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)
}
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 2d96a8c970..029f6ae508 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
@@ -69,6 +69,7 @@ fun RustMembership.map(): CurrentUserMembership = when (this) {
RustMembership.JOINED -> CurrentUserMembership.JOINED
RustMembership.LEFT -> CurrentUserMembership.LEFT
Membership.KNOCKED -> CurrentUserMembership.KNOCKED
+ RustMembership.BANNED -> CurrentUserMembership.BANNED
}
fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) {
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 588927f434..64c4ba72c9 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
@@ -248,7 +248,7 @@ class RustMatrixRoom(
RoomMessageEventMessageType.VIDEO,
RoomMessageEventMessageType.AUDIO,
),
- dateDividerMode = DateDividerMode.DAILY,
+ dateDividerMode = DateDividerMode.MONTHLY,
).let { inner ->
createTimeline(inner, mode = Timeline.Mode.MEDIA)
}
@@ -582,6 +582,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/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/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/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..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
@@ -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,10 +21,11 @@ class UtdTrackerTest {
val fakeAnalyticsService = FakeAnalyticsService()
val sut = UtdTracker(fakeAnalyticsService)
sut.onUtd(
- UnableToDecryptInfo(
+ aRustUnableToDecryptInfo(
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()
@@ -46,7 +51,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,
@@ -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()
@@ -71,7 +80,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,
@@ -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()
@@ -96,7 +109,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,
@@ -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,
)
)
}
@@ -119,7 +136,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,
@@ -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
new file mode 100644
index 0000000000..775934716f
--- /dev/null
+++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.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.matrix.impl.fixtures.factories
+
+import org.matrix.rustcomponents.sdk.UnableToDecryptInfo
+import uniffi.matrix_sdk_crypto.UtdCause
+
+internal fun aRustUnableToDecryptInfo(
+ eventId: String,
+ timeToDecryptMs: ULong? = null,
+ cause: UtdCause = UtdCause.UNKNOWN,
+ 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,
+ )
+}
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..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
@@ -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,14 @@ 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 +107,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(
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 18b804455d..bea207eb8a 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
@@ -585,6 +585,10 @@ class FakeMatrixRoom(
fun givenRoomMembersState(state: MatrixRoomMembersState) {
membersStateFlow.value = state
}
+
+ override suspend fun clearEventCacheStorage(): Result {
+ return Result.success(Unit)
+ }
}
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
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 `default test`() = runTest {
- // TODO
+ 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 `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) }
+ }
+}
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..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
@@ -23,6 +23,8 @@ data class MediaInfo(
val senderName: String?,
val senderAvatar: String?,
val dateSent: String?,
+ val dateSentFull: String?,
+ val waveform: List?,
) : Parcelable
fun anImageMediaInfo(
@@ -30,6 +32,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 +43,15 @@ fun anImageMediaInfo(
senderName = senderName,
senderAvatar = null,
dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
)
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 +62,8 @@ fun aVideoMediaInfo(
senderName = senderName,
senderAvatar = null,
dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
)
fun aPdfMediaInfo(
@@ -63,6 +71,7 @@ fun aPdfMediaInfo(
caption: String? = null,
senderName: String? = null,
dateSent: String? = null,
+ dateSentFull: String? = null,
): MediaInfo = MediaInfo(
filename = filename,
caption = caption,
@@ -73,12 +82,15 @@ fun aPdfMediaInfo(
senderName = senderName,
senderAvatar = null,
dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
)
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,14 +101,20 @@ fun anApkMediaInfo(
senderName = senderName,
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",
@@ -104,4 +122,27 @@ fun anAudioMediaInfo(
senderName = senderName,
senderAvatar = null,
dateSent = dateSent,
+ 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/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/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt
index f9611a7023..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
@@ -53,6 +53,8 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint
senderName = null,
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/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..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
@@ -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
@@ -39,19 +40,27 @@ 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
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,
@@ -78,7 +87,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(
@@ -90,7 +99,9 @@ class EventItemFactory @Inject constructor(
senderId = event.sender,
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
senderAvatar = event.senderProfile.getAvatarUrl(),
- dateSent = sentTime,
+ dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
),
mediaSource = type.source,
)
@@ -106,7 +117,9 @@ class EventItemFactory @Inject constructor(
senderId = event.sender,
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
senderAvatar = event.senderProfile.getAvatarUrl(),
- dateSent = sentTime,
+ dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
),
mediaSource = type.source,
)
@@ -122,7 +135,9 @@ class EventItemFactory @Inject constructor(
senderId = event.sender,
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
senderAvatar = event.senderProfile.getAvatarUrl(),
- dateSent = sentTime,
+ dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
),
mediaSource = type.source,
thumbnailSource = null,
@@ -139,7 +154,9 @@ class EventItemFactory @Inject constructor(
senderId = event.sender,
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
senderAvatar = event.senderProfile.getAvatarUrl(),
- dateSent = sentTime,
+ dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = null,
),
mediaSource = type.source,
thumbnailSource = null,
@@ -156,13 +173,15 @@ class EventItemFactory @Inject constructor(
senderId = event.sender,
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
senderAvatar = event.senderProfile.getAvatarUrl(),
- dateSent = sentTime,
+ 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.Voice(
id = currentTimelineItem.uniqueId,
eventId = currentTimelineItem.eventId,
mediaInfo = MediaInfo(
@@ -174,9 +193,13 @@ class EventItemFactory @Inject constructor(
senderId = event.sender,
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
senderAvatar = event.senderProfile.getAvatarUrl(),
- dateSent = sentTime,
+ dateSent = dateSent,
+ dateSentFull = dateSentFull,
+ waveform = type.details?.waveform.orEmpty(),
),
mediaSource = type.source,
+ duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(),
+ waveform = type.details?.waveform ?: persistentListOf(),
)
}
}
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/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt
index c122e95447..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
@@ -135,7 +135,9 @@ 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
+ 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 58d566dddd..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
@@ -9,14 +9,17 @@ 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
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 {
@@ -61,8 +64,11 @@ open class MediaGalleryStateProvider : PreviewParameterProvider 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,
@@ -229,7 +240,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,
@@ -245,30 +260,50 @@ private fun MediaGalleryFilesList(
eventSink: (MediaGalleryEvents) -> Unit,
onItemClick: (MediaItem.Event) -> Unit,
) {
+ val presenterFactories = LocalMediaItemPresenterFactories.current
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
- items(files) { item ->
+ items(
+ items = files,
+ key = { it.id() },
+ contentType = { it::class.java },
+ ) { item ->
when (item) {
is MediaItem.File -> FileItemView(
- item,
+ modifier = Modifier.animateItem(),
+ file = 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.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)) },
+ onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) },
+ onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(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)
- val latestEventSink by rememberUpdatedState(eventSink)
- LaunchedEffect(item.timestamp) {
- latestEventSink(MediaGalleryEvents.LoadMore(item.direction))
- }
- }
+ is MediaItem.LoadingIndicator -> LoadingMoreIndicator(
+ modifier = Modifier.animateItem(),
+ item = item,
+ eventSink = eventSink,
+ )
}
}
}
@@ -280,28 +315,20 @@ 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),
) {
items(
- imagesAndVideos,
+ items = imagesAndVideos,
span = { item ->
when (item) {
is MediaItem.LoadingIndicator,
- is MediaItem.DateSeparator -> GridItemSpan(columns)
+ is MediaItem.DateSeparator -> GridItemSpan(maxLineSpan)
is MediaItem.Event -> GridItemSpan(1)
}
},
@@ -309,37 +336,40 @@ 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
+ }
+ is MediaItem.Voice -> {
+ // Should not happen
}
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.LoadingIndicator -> {
- LoadingMoreIndicator(item.direction)
- val latestEventSink by rememberUpdatedState(eventSink)
- LaunchedEffect(item.timestamp) {
- latestEventSink(MediaGalleryEvents.LoadMore(item.direction))
- }
- }
+ 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(
+ modifier = Modifier.animateItem(),
+ item = item,
+ eventSink = eventSink,
+ )
}
}
}
@@ -347,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
@@ -370,6 +401,10 @@ private fun LoadingMoreIndicator(
)
}
}
+ val latestEventSink by rememberUpdatedState(eventSink)
+ LaunchedEffect(item.timestamp) {
+ latestEventSink(MediaGalleryEvents.LoadMore(item.direction))
+ }
}
}
@@ -383,7 +418,11 @@ private fun ErrorContent(error: Throwable) {
}
@Composable
-private fun EmptyContent() {
+private fun EmptyContent(
+ titleRes: Int,
+ subtitleRes: Int,
+ icon: ImageVector,
+) {
Box(
modifier = Modifier.fillMaxSize(),
) {
@@ -392,9 +431,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),
)
}
}
@@ -428,9 +467,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/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt
index f43387fdb6..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
@@ -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,22 @@ 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,
+ ) : 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(
val id: UniqueId,
val eventId: EventId?,
@@ -66,6 +83,8 @@ fun MediaItem.id(): UniqueId {
is MediaItem.Image -> id
is MediaItem.Video -> id
is MediaItem.File -> id
+ is MediaItem.Audio -> id
+ is MediaItem.Voice -> id
}
}
@@ -74,6 +93,8 @@ fun MediaItem.Event.eventId(): EventId? {
is MediaItem.Image -> eventId
is MediaItem.Video -> eventId
is MediaItem.File -> eventId
+ is MediaItem.Audio -> eventId
+ is MediaItem.Voice -> eventId
}
}
@@ -82,6 +103,8 @@ fun MediaItem.Event.mediaInfo(): MediaInfo {
is MediaItem.Image -> mediaInfo
is MediaItem.Video -> mediaInfo
is MediaItem.File -> mediaInfo
+ is MediaItem.Audio -> mediaInfo
+ is MediaItem.Voice -> mediaInfo
}
}
@@ -90,6 +113,8 @@ fun MediaItem.Event.mediaSource(): MediaSource {
is MediaItem.Image -> mediaSource
is MediaItem.Video -> mediaSource
is MediaItem.File -> mediaSource
+ is MediaItem.Audio -> mediaSource
+ is MediaItem.Voice -> mediaSource
}
}
@@ -98,5 +123,7 @@ fun MediaItem.Event.thumbnailSource(): MediaSource? {
is MediaItem.Image -> thumbnailSource
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 6706dd08c8..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,10 +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/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/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/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..dd06fa1bf8
--- /dev/null
+++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.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.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
+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.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.Text
+import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem
+
+@Composable
+fun AudioItemView(
+ audio: MediaItem.Audio,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ ) {
+ Spacer(modifier = Modifier.height(20.dp))
+ FilenameRow(
+ audio = audio,
+ onClick = onClick,
+ )
+ val caption = audio.mediaInfo.caption
+ if (caption != null) {
+ CaptionView(caption)
+ } else {
+ Spacer(modifier = Modifier.height(20.dp))
+ }
+ 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.bgActionSecondaryRest,
+ shape = CircleShape,
+ )
+ .size(32.dp)
+ .padding(6.dp),
+ imageVector = Icons.Outlined.GraphicEq,
+ contentDescription = null,
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ 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,
+ )
+ }
+ }
+}
+
+@PreviewsDayNight
+@Composable
+internal fun AudioItemViewPreview(
+ @PreviewParameter(MediaItemAudioProvider::class) audio: MediaItem.Audio,
+) = ElementPreview {
+ AudioItemView(
+ audio = audio,
+ onClick = {},
+ )
+}
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 99ff456296..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),
+ .fillMaxWidth()
+ .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()
}
}
@@ -78,24 +69,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,
)
@@ -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
new file mode 100644
index 0000000000..5d4ebe0b94
--- /dev/null
+++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.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.anAudioMediaInfo
+import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem
+
+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,
+ ),
+ )
+}
+
+fun aMediaItemAudio(
+ id: UniqueId = UniqueId("fileId"),
+ filename: String = "filename",
+ caption: String? = null,
+): MediaItem.Audio {
+ return MediaItem.Audio(
+ id = id,
+ eventId = null,
+ mediaInfo = anAudioMediaInfo(
+ filename = filename,
+ caption = caption,
+ ),
+ mediaSource = MediaSource(""),
+ )
+}
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..5864914dda
--- /dev/null
+++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.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
+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.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,
+ onInfoClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .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))
+ }
+ ActionIconsRow(
+ onShareClick = onShareClick,
+ onDownloadClick = onDownloadClick,
+ onInfoClick = onInfoClick,
+ )
+ HorizontalDivider()
+ }
+}
+
+@Composable
+private fun VoiceInfoRow(
+ state: VoiceMessageState,
+ voice: MediaItem.Voice,
+) {
+ fun playPause() {
+ state.eventSink(VoiceMessageEvents.PlayPause)
+ }
+
+ 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,
+ ) {
+ 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 = if (state.progress > 0f) state.time else voice.duration ?: 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),
+ 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 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(
+ 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/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..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
@@ -46,6 +46,8 @@ class AndroidLocalMediaFactory @Inject constructor(
senderName = mediaInfo.senderName,
senderAvatar = mediaInfo.senderAvatar,
dateSent = mediaInfo.dateSent,
+ dateSentFull = mediaInfo.dateSentFull,
+ waveform = mediaInfo.waveform,
)
override fun createFromUri(
@@ -63,6 +65,8 @@ class AndroidLocalMediaFactory @Inject constructor(
senderName = null,
senderAvatar = null,
dateSent = null,
+ dateSentFull = null,
+ waveform = null,
)
private fun createFromUri(
@@ -75,6 +79,8 @@ class AndroidLocalMediaFactory @Inject constructor(
senderName: String?,
senderAvatar: String?,
dateSent: String?,
+ dateSentFull: String?,
+ waveform: List?,
): LocalMedia {
val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream
val fileName = name ?: context.getFileName(uri) ?: ""
@@ -92,6 +98,8 @@ class AndroidLocalMediaFactory @Inject constructor(
senderName = senderName,
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..05c96cc8af
--- /dev/null
+++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt
@@ -0,0 +1,366 @@
+/*
+ * 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.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.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.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
+import kotlinx.coroutines.delay
+
+@SuppressLint("UnsafeOptInUsageError")
+@Composable
+fun MediaAudioView(
+ localMediaViewState: LocalMediaViewState,
+ bottomPaddingInPixels: Int,
+ localMedia: LocalMedia?,
+ info: MediaInfo?,
+ modifier: Modifier = Modifier,
+) {
+ val exoPlayer = rememberExoPlayer()
+ 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 = remember {
+ 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 = {
+ exoPlayer.seekToEnsurePlaying((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(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ info = info,
+ metadata = metadata,
+ )
+ }
+ }
+ MediaPlayerControllerView(
+ state = mediaPlayerControllerState,
+ onTogglePlay = {
+ exoPlayer.togglePlay()
+ },
+ onSeekChange = {
+ exoPlayer.seekToEnsurePlaying(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
+private fun AudioInfoView(
+ info: MediaInfo?,
+ metadata: MediaMetadata?,
+ modifier: Modifier = Modifier,
+) {
+ 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
+ )
+ }
+ }
+}
+
+@PreviewsDayNight
+@Composable
+internal fun MediaAudioViewPreview(
+ @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/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/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/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 54%
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 c4e4b913a7..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,12 +5,18 @@
* 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
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/player/MediaPlayerControllerStateProvider.kt
similarity index 84%
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 78059bd4eb..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
@@ -18,6 +18,9 @@ open class MediaPlayerControllerStateProvider : PreviewParameterProvider= 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,
+ )
+ }
+ }
}
}
}
@@ -211,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++
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 8bed5dfc49..61e4f78176 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
@@ -10,6 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.viewer
import android.net.Uri
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.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo
import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo
@@ -100,6 +101,16 @@ open class MediaViewerStateProvider : PreviewParameterProvider
aMediaViewerState(
mediaBottomSheetState = aMediaDeleteConfirmationState(),
),
+ anAudioMediaInfo(
+ waveForm = aWaveForm(),
+ ).let {
+ aMediaViewerState(
+ downloadedMedia = AsyncData.Success(
+ LocalMedia(Uri.EMPTY, it)
+ ),
+ mediaInfo = it,
+ )
+ },
)
}
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..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,10 +1,20 @@
- "Obrázky a videa nahraná do této místnosti budou zobrazeny zde."
- "Zatím nebyla nahrána žádná média"
+ "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"
"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"
+ "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-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-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-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-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 bd961fb941..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,15 +2,19 @@
"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"
+ "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"
"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"
+ "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 1fcc528dc5..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,15 +2,19 @@
"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"
+ "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"
"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"
+ "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/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"
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..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
@@ -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
@@ -51,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
@@ -162,7 +162,9 @@ class DefaultEventItemFactoryTest {
senderId = A_USER_ID,
senderName = "alice",
senderAvatar = null,
- dateSent = A_FORMATTED_DATE,
+ dateSent = "0 Day false",
+ dateSentFull = "0 Full false",
+ waveform = null,
),
mediaSource = MediaSource(""),
)
@@ -209,7 +211,9 @@ class DefaultEventItemFactoryTest {
senderId = A_USER_ID,
senderName = "alice",
senderAvatar = null,
- dateSent = A_FORMATTED_DATE,
+ dateSent = "0 Day false",
+ dateSentFull = "0 Full false",
+ waveform = null,
),
mediaSource = MediaSource(""),
thumbnailSource = null,
@@ -241,7 +245,7 @@ class DefaultEventItemFactoryTest {
)
)
assertThat(result).isEqualTo(
- MediaItem.File(
+ MediaItem.Audio(
id = A_UNIQUE_ID,
eventId = AN_EVENT_ID,
mediaInfo = MediaInfo(
@@ -253,7 +257,9 @@ class DefaultEventItemFactoryTest {
senderId = A_USER_ID,
senderName = "alice",
senderAvatar = null,
- dateSent = A_FORMATTED_DATE,
+ dateSent = "0 Day false",
+ dateSentFull = "0 Full false",
+ waveform = null,
),
mediaSource = MediaSource(""),
)
@@ -301,7 +307,9 @@ class DefaultEventItemFactoryTest {
senderId = A_USER_ID,
senderName = "alice",
senderAvatar = null,
- dateSent = A_FORMATTED_DATE,
+ dateSent = "0 Day false",
+ dateSentFull = "0 Full false",
+ waveform = null,
),
mediaSource = MediaSource(""),
thumbnailSource = null,
@@ -330,7 +338,7 @@ class DefaultEventItemFactoryTest {
),
details = AudioDetails(
duration = 456.seconds,
- waveform = persistentListOf(),
+ waveform = persistentListOf(1f, 2f),
)
)
)
@@ -338,7 +346,7 @@ class DefaultEventItemFactoryTest {
)
)
assertThat(result).isEqualTo(
- MediaItem.File(
+ MediaItem.Voice(
id = A_UNIQUE_ID,
eventId = AN_EVENT_ID,
mediaInfo = MediaInfo(
@@ -350,9 +358,13 @@ class DefaultEventItemFactoryTest {
senderId = A_USER_ID,
senderName = "alice",
senderAvatar = null,
- dateSent = A_FORMATTED_DATE,
+ dateSent = "0 Day false",
+ dateSentFull = "0 Full false",
+ waveform = listOf(1f, 2f).toImmutableList(),
),
mediaSource = MediaSource(""),
+ duration = "7:36",
+ waveform = listOf(1f, 2f).toImmutableList(),
)
)
}
@@ -397,7 +409,9 @@ class DefaultEventItemFactoryTest {
senderId = A_USER_ID,
senderName = "alice",
senderAvatar = null,
- dateSent = A_FORMATTED_DATE,
+ dateSent = "0 Day false",
+ dateSentFull = "0 Full false",
+ waveform = null,
),
mediaSource = MediaSource(""),
thumbnailSource = null,
@@ -409,5 +423,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/gallery/MediaItemsPostProcessorTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessorTest.kt
index 75a911f1dc..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
@@ -11,11 +11,13 @@ 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
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
@@ -23,6 +25,12 @@ 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 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"))
@@ -68,6 +76,7 @@ class MediaItemsPostProcessorTest {
fun `process will reorder files`() {
test(
mediaItems = listOf(
+ audio1,
file3,
file2,
file1,
@@ -76,9 +85,10 @@ class MediaItemsPostProcessorTest {
expectedImageAndVideoItems = emptyList(),
expectedFileItems = listOf(
date1,
- file1,
- file2,
+ audio1,
file3,
+ file2,
+ file1,
),
)
}
@@ -94,9 +104,9 @@ class MediaItemsPostProcessorTest {
),
expectedImageAndVideoItems = listOf(
date1,
- image1,
- image2,
image3,
+ image2,
+ image1,
),
expectedFileItems = emptyList(),
)
@@ -106,6 +116,7 @@ class MediaItemsPostProcessorTest {
fun `process will split images, videos and files`() {
test(
mediaItems = listOf(
+ audio1,
file1,
image1,
video1,
@@ -113,11 +124,12 @@ class MediaItemsPostProcessorTest {
),
expectedImageAndVideoItems = listOf(
date1,
- video1,
image1,
+ video1,
),
expectedFileItems = listOf(
date1,
+ audio1,
file1,
),
)
@@ -155,31 +167,43 @@ class MediaItemsPostProcessorTest {
fun `process will handle complex case`() {
test(
mediaItems = listOf(
- file1,
- image1,
- video1,
- date1,
file3,
date3,
video3,
video2,
date2,
+ voice3,
+ voice2,
+ voice1,
+ audio3,
+ audio2,
+ audio1,
+ file1,
+ image1,
+ video1,
+ date1,
loading1,
),
expectedImageAndVideoItems = listOf(
- date1,
- video1,
- image1,
date2,
- video2,
video3,
+ video2,
+ date1,
+ image1,
+ video1,
loading1,
),
expectedFileItems = listOf(
- date1,
- file1,
date3,
file3,
+ date1,
+ voice3,
+ voice2,
+ voice1,
+ audio3,
+ audio2,
+ audio1,
+ file1,
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 f60c43572e..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
@@ -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,9 @@ class AndroidLocalMediaFactoryTest {
senderId = A_USER_ID,
senderName = A_USER_NAME,
senderAvatar = null,
- dateSent = "12:34"
+ dateSent = "12:34",
+ 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 c41435afc0..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
@@ -40,7 +40,9 @@ class FakeLocalMediaFactory(
senderId = null,
senderName = null,
senderAvatar = null,
- dateSent = null
+ dateSent = null,
+ dateSentFull = null,
+ waveform = null,
)
return aLocalMedia(uri, mediaInfo)
}
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 ab38b9b148..1c68bef70a 100644
--- a/libraries/ui-strings/src/main/res/values/localazy.xml
+++ b/libraries/ui-strings/src/main/res/values/localazy.xml
@@ -330,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"
@@ -344,6 +360,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."
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"))
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,],
];
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. " +
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/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
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..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:e67a540966100272311381e87011149cdb15c8191a6f2bbc40d1febff999c431
-size 24067
+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 f500ff60dd..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:65023f7233f112547ccd0850c321b2d6610cfb6a1371c494f68722e75874f871
-size 45915
+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 c80b8639e0..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:e2c2e01bd133c7b84e381141b6baf22783cb585cb3af9f790785dbf8aeaeeed6
-size 47644
+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 4088be4e1e..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:9bd6fb63059cebc7dc7b850b5aa73549ab9846eda68b1b6d9a4f8e0716d42c3c
-size 40419
+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 b3dbf77d1d..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:6b4e4a075bcb4b95455c27ce2e5f9f1bdac4a1aca22ad944dd53fdbaeb6ca970
-size 44550
+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 87c07faab3..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:1a2bc7e9098301d17e72a61a7baf01e52aa11566e1da4ffe3b43a66fa37652b2
-size 42103
+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 25da7b8bf1..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:f097af2773ff2ecf70a69e4292dc118b4c8616f1c8f979e7d121e86a16ef3072
-size 38506
+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 2bf210d590..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:8f49870a5333cbaf1c6c3ac5ddcb85290d04efc79ffec93d8dc39b59f9712820
-size 42391
+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 6de43e00ee..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:883f160afa3fc3d2c0be5098205ea616875084ee0f122ebf994d0fee53a8ecd1
-size 39847
+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 cf03f3c12f..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:7790c892daeee8910ce17caac2c957d09fffec0d41a51b3adc1d5bef8dcee1a1
-size 42261
+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 63da19cb01..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:bc7b733680a0d86ee1231345e58641fb3a208da21de62a50f624d9ae04c6e140
-size 31661
+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 7e43e760b2..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:20504466241e36817557812f6b378aceab9c2e271a596bd3d037c6be41af7c54
-size 23624
+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 1667fe5ff0..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:48028e4c3ca7e9b871683165f029430bcd4e0fc2411e4ffc83a93abb641d96a2
-size 45132
+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 e44f324015..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:b040fe47521f5d9d76d7892412bb2b5cf7e2b93b951f5de5f772503797fb6b24
-size 46548
+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 5797dc5345..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:6db046a97f14f2df3db2415d71a02ffd6e706fe73def67c21f0d844be279da59
-size 39617
+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 9b837817e6..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:7ee53243923d7b5adcc2094d3fe6bb87be00a179cae11966dcb230f2c3e8246e
-size 43681
+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 6d0c2a905b..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:b88e54ba4743e1e245b5a4c7d6b4045f816241f6cfc8716b8cede0b62666222b
-size 41286
+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 c479ebef74..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:d88b4d82bb0d416aff7b32647479181ac9702c36c08370e7671d6e5ef80687c7
-size 37825
+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 885d6afaed..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:b3d4dd1766a3f46acd86bdd5ec920c56a5e5f897c81c1477df10ba59dcd3d5b7
-size 41554
+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 13b4a76016..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:842fbea74e53c8804fd0a3e9fe96dcb21b5c91a099e1de33d8ec983fd9a8a80a
-size 39112
+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 18e022f292..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:1311ec5e008b44d81e6556164f780c57dea3e4721cc47caccdbf337dd4027eb7
-size 41402
+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 99c6f765a5..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:d2cf27d2f053f33c67d944a8f45e0206165b2db2f3b959eb9e0bb943f84fe6a1
-size 30657
+oid sha256:f72f6ec83324b0b6e3719e1e0f59387cb0376149decbb780970dcb7f219a1551
+size 33053
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
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.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
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.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
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.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..02a746e1d8
--- /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: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
new file mode 100644
index 0000000000..9ccb8d78d2
--- /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: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
new file mode 100644
index 0000000000..aea3483115
--- /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: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
new file mode 100644
index 0000000000..d79c6a8d25
--- /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: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
new file mode 100644
index 0000000000..04df2c1c11
--- /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: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
new file mode 100644
index 0000000000..11c187832f
--- /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: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.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..e1069af79c
--- /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:9d0b3f44a9a0ed9ab16192b23ccf95b7d34abccd025bb4a7ccd8ebb7a9379965
+size 10824
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..3e5ffd3185
--- /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:73fecb12c33892a9bea41e5a4bbbf4db43990e44b2a36b96d03d7a81046c8f92
+size 10076
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..7d108d9701
--- /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: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
new file mode 100644
index 0000000000..543d484800
--- /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: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
new file mode 100644
index 0000000000..80ea9026f2
--- /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: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
new file mode 100644
index 0000000000..53ec45dc2a
--- /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: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
new file mode 100644
index 0000000000..6f74347dfc
--- /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: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
new file mode 100644
index 0000000000..bad0c59624
--- /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: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
new file mode 100644
index 0000000000..24de50d08d
--- /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: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
new file mode 100644
index 0000000000..2c81f6a60f
--- /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:8b71ca3fcc1f94d89264f3d5c5751f46681014b662bcf7d910263dfb23178e0f
+size 8623
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_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_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en.png
index 7a192f89f4..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:ce8fec32ec1a902edb12d9580ca73d0cc5a9838f91d31dbbf8329326b9fa1a68
-size 39676
+oid sha256:c8b23c6c8f47f7e49cdb56098408873aa9d3f02af22cae01add580f0e623678d
+size 34814
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_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_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
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..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:946183ccbaf14471579f3b48861715d04ed45d6352d36126a419de7e6f362bfd
-size 37632
+oid sha256:e361247dd137a828f1170fc1497ace365e04bb551edcd4984c26b15c26fe65f1
+size 33011
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.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_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
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
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
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" : [
diff --git a/tools/sdk/build_rust_sdk.sh b/tools/sdk/build_rust_sdk.sh
index f0c0378333..7c65604167 100755
--- a/tools/sdk/build_rust_sdk.sh
+++ b/tools/sdk/build_rust_sdk.sh
@@ -59,6 +59,13 @@ 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}}"
+
# 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 +78,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