From 6b2c26ccb8ba2ece5958fd1a66216b3f4a4c0137 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 4 Jul 2024 18:00:36 +0200 Subject: [PATCH 001/115] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 97ac5aeaf5..68512dfc62 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 16 +private const val versionPatch = 17 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 332210094a81aee5594ab81b65d51c8201cc2a66 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 4 Jul 2024 18:02:18 +0200 Subject: [PATCH 002/115] Misc : remove changelog.d folder --- changelog.d/.gitignore | 1 - changelog.d/2869.feature | 1 - changelog.d/2916.misc | 1 - changelog.d/3051.misc | 1 - changelog.d/3053.misc | 1 - changelog.d/3068.misc | 1 - changelog.d/3073.bugfix | 1 - changelog.d/3081.bugfix | 1 - changelog.d/3082.bugfix | 1 - changelog.d/3083.bugfix | 1 - changelog.d/3085.bugfix | 1 - changelog.d/3086.bugfix | 1 - 12 files changed, 12 deletions(-) delete mode 100644 changelog.d/.gitignore delete mode 100644 changelog.d/2869.feature delete mode 100644 changelog.d/2916.misc delete mode 100644 changelog.d/3051.misc delete mode 100644 changelog.d/3053.misc delete mode 100644 changelog.d/3068.misc delete mode 100644 changelog.d/3073.bugfix delete mode 100644 changelog.d/3081.bugfix delete mode 100644 changelog.d/3082.bugfix delete mode 100644 changelog.d/3083.bugfix delete mode 100644 changelog.d/3085.bugfix delete mode 100644 changelog.d/3086.bugfix diff --git a/changelog.d/.gitignore b/changelog.d/.gitignore deleted file mode 100644 index b722e9e13e..0000000000 --- a/changelog.d/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!.gitignore \ No newline at end of file diff --git a/changelog.d/2869.feature b/changelog.d/2869.feature deleted file mode 100644 index bfda19096b..0000000000 --- a/changelog.d/2869.feature +++ /dev/null @@ -1 +0,0 @@ -Store and restore drafts for each room. diff --git a/changelog.d/2916.misc b/changelog.d/2916.misc deleted file mode 100644 index b87146dd58..0000000000 --- a/changelog.d/2916.misc +++ /dev/null @@ -1 +0,0 @@ -Use a more natural date format for day dividers in the timeline. Also improve the time format for last messages in the room list. diff --git a/changelog.d/3051.misc b/changelog.d/3051.misc deleted file mode 100644 index 032d0c8dbe..0000000000 --- a/changelog.d/3051.misc +++ /dev/null @@ -1 +0,0 @@ -Resolve display names in mentions in real time, also send mentions with user ids as the fallback text for the link representation of the mentions. diff --git a/changelog.d/3053.misc b/changelog.d/3053.misc deleted file mode 100644 index 6a22cc363a..0000000000 --- a/changelog.d/3053.misc +++ /dev/null @@ -1 +0,0 @@ -Alert for incoming call even if notifications are disabled diff --git a/changelog.d/3068.misc b/changelog.d/3068.misc deleted file mode 100644 index add7772252..0000000000 --- a/changelog.d/3068.misc +++ /dev/null @@ -1 +0,0 @@ -Updated Rust SDK to `v0.2.28`. Fixed incompatibilities. diff --git a/changelog.d/3073.bugfix b/changelog.d/3073.bugfix deleted file mode 100644 index 2f7dbf19f9..0000000000 --- a/changelog.d/3073.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix feature flags not being able to be toggle in developer settings in release builds. diff --git a/changelog.d/3081.bugfix b/changelog.d/3081.bugfix deleted file mode 100644 index 37baf6cca2..0000000000 --- a/changelog.d/3081.bugfix +++ /dev/null @@ -1 +0,0 @@ - Let roles and permissions screens work for invited room members too. diff --git a/changelog.d/3082.bugfix b/changelog.d/3082.bugfix deleted file mode 100644 index 248b35ad7f..0000000000 --- a/changelog.d/3082.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix image rendering after clear cache diff --git a/changelog.d/3083.bugfix b/changelog.d/3083.bugfix deleted file mode 100644 index e3dfaf1da0..0000000000 --- a/changelog.d/3083.bugfix +++ /dev/null @@ -1 +0,0 @@ -Improve room filters behavior diff --git a/changelog.d/3085.bugfix b/changelog.d/3085.bugfix deleted file mode 100644 index bfad218bd2..0000000000 --- a/changelog.d/3085.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make sure we replace the 'answer call' pending intent on ringing call notifications. diff --git a/changelog.d/3086.bugfix b/changelog.d/3086.bugfix deleted file mode 100644 index da39ba616f..0000000000 --- a/changelog.d/3086.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make sure we don't use the main dispatcher while closing the bug report request, as it can lead to crashes in strict mode. From e2b7f26f57a9226d99ab3608dee728c05e48a701 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:14:22 +0000 Subject: [PATCH 003/115] Update plugin sonarqube to v5.1.0.4882 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51760bb7c5..21b15a75da 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -230,4 +230,4 @@ paparazzi = "app.cash.paparazzi:1.3.4" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" } -sonarqube = "org.sonarqube:5.0.0.4638" +sonarqube = "org.sonarqube:5.1.0.4882" From a2cf690359974c4c11418b919cd62eea8883ed94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 22:56:43 +0000 Subject: [PATCH 004/115] Update dependency io.element.android:compound-android to v0.0.7 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51760bb7c5..8acaa7a1a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -152,7 +152,7 @@ coil = { module = "io.coil-kt:coil", version.ref = "coil" } coil_compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil_gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } coil_test = { module = "io.coil-kt:coil-test", version.ref = "coil" } -compound = { module = "io.element.android:compound-android", version = "0.0.6" } +compound = { module = "io.element.android:compound-android", version = "0.0.7" } datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" } kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7" From 85a22eb7191609482b3bad7577c9af13a467e555 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 5 Jul 2024 07:34:11 +0000 Subject: [PATCH 005/115] Update screenshots --- .../libraries.designsystem.icons_IconsCompound_Day_1_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Day_2_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Day_3_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Day_4_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Night_1_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Night_2_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Night_3_en.png | 4 ++-- .../libraries.designsystem.icons_IconsCompound_Night_4_en.png | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png index 1b539d7880..0fde12dd3d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ee22a8d3a8d4851a988c6b53cdb323ef50f864d98b09e9f914791ee1f613f25 -size 63180 +oid sha256:600a0df590b80df7f4f3dcf7f697ebd45b914bcf860d05038e067a5657799a23 +size 63392 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png index 382fde4506..6598c4d736 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:400cd9f286bcf0c4037b13209fe2b3589f0215405bac36a658915bd1ebf8c84b -size 72434 +oid sha256:1bc9b1c9fa047d016db2cb8559deea47792da4413c29621485f2151da6878bd4 +size 74584 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png index 8c8210de2a..3aaec56642 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7d9cc1114354469076eb1883e4c33d25c8ec19e02882536809d5b2900e2231d -size 70679 +oid sha256:df184fb2e30d7cde379b4f568e9794ebaa54a95941d46c60ddb7686cdf9322e9 +size 69564 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png index e62a2ad4a2..437f01e95d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13d9539255fb304ef336f135db5e319f6f794cb082cbddc2f9e6e8f4ee6e5cd2 -size 55942 +oid sha256:5ef100d3a881907e99e855910e8e2ca1dce6687a24300816e1e875efc192b655 +size 61777 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png index 12aee68c59..c742359c86 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:972f5ef9c5abfe5c1e34168074ce4596672966b1f1a884f1ca0c41aae8dae9a3 -size 60335 +oid sha256:0e2e56d615432a239fc391b8d9632217cba95664c1b2413e4599989a219eea78 +size 60417 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png index 67564039dd..2f7a0daac3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d6a11ab81325155a8cd6139f10b2114be3e613914d80ff258588b59f77a3270 -size 69479 +oid sha256:87ed00992118d5ad764df2e5a2da26ad6f58c1b1919cb274283c89bf6d5a627e +size 71622 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png index 0057bdf4d9..36ae54c217 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1991b0ddf8f5f69556ca905936a1617b22ef4c0cb6a3509972a190a07701b592 -size 67391 +oid sha256:99df01ff57fe4a6f2922d1e7820da79ffd0fdca4d8a1384d5148a3f8cfa4d38c +size 66698 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png index 9e87b6fce3..bdb9d4c829 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8a1be2a0d1c9a8c0e1e510a4e1b3771038f687a94acedd7712810c0dcaa841c -size 54419 +oid sha256:2c786eed63d7b18bd4c424dcf574d12a5f3c52da5f2eb7a870bbd79523e9a1fd +size 59187 From f5ad2abdd3914ede3bcbb267b6fdb3c123d325c2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Jul 2024 09:49:42 +0200 Subject: [PATCH 006/115] Add icon for "Mark as read" and "Mark as unread" action. --- .../roomlist/impl/RoomListContextMenu.kt | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) 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 582c5e083b..01e370bc1a 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 @@ -96,36 +96,35 @@ private fun RoomListModalBottomSheetContent( } ) if (contextMenu.markAsUnreadFeatureFlagEnabled) { - ListItem( - headlineContent = { - Text( - text = stringResource( - id = if (contextMenu.hasNewContent) { - R.string.screen_roomlist_mark_as_read - } else { - R.string.screen_roomlist_mark_as_unread - } - ), - style = MaterialTheme.typography.bodyLarge, - ) - }, - modifier = Modifier.clickable { - if (contextMenu.hasNewContent) { - onRoomMarkReadClick() - } else { - onRoomMarkUnreadClick() - } - }, - /* TODO Design - leadingContent = ListItemContent.Icon( - iconSource = IconSource.Vector( - CompoundIcons.Settings, - contentDescription = stringResource(id = CommonStrings.common_settings) - ) - ), - */ - style = ListItemStyle.Primary, - ) + if (contextMenu.hasNewContent) { + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_roomlist_mark_as_read), + style = MaterialTheme.typography.bodyLarge, + ) + }, + onClick = onRoomMarkReadClick, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector(CompoundIcons.MarkAsRead()) + ), + style = ListItemStyle.Primary, + ) + } else { + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_roomlist_mark_as_unread), + style = MaterialTheme.typography.bodyLarge, + ) + }, + onClick = onRoomMarkUnreadClick, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector(CompoundIcons.MarkAsUnread()) + ), + style = ListItemStyle.Primary, + ) + } } ListItem( headlineContent = { From 02224b5576835cd27b2fec80f87ccf3a5b578e23 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 5 Jul 2024 08:23:51 +0000 Subject: [PATCH 007/115] Update screenshots --- ...roomlist.impl_RoomListModalBottomSheetContent_Day_0_en.png | 4 ++-- ...roomlist.impl_RoomListModalBottomSheetContent_Day_1_en.png | 4 ++-- ...roomlist.impl_RoomListModalBottomSheetContent_Day_2_en.png | 4 ++-- ...omlist.impl_RoomListModalBottomSheetContent_Night_0_en.png | 4 ++-- ...omlist.impl_RoomListModalBottomSheetContent_Night_1_en.png | 4 ++-- ...omlist.impl_RoomListModalBottomSheetContent_Night_2_en.png | 4 ++-- .../images/features.roomlist.impl_RoomListView_Day_3_en.png | 4 ++-- .../images/features.roomlist.impl_RoomListView_Day_4_en.png | 4 ++-- .../images/features.roomlist.impl_RoomListView_Day_5_en.png | 4 ++-- .../images/features.roomlist.impl_RoomListView_Night_3_en.png | 4 ++-- .../images/features.roomlist.impl_RoomListView_Night_4_en.png | 4 ++-- .../images/features.roomlist.impl_RoomListView_Night_5_en.png | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en.png index 4393fc055b..ae834e3ae5 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7249fe5316ce5b2db50489bef2d2ce95d8328507410548e8f6bcb9b33104d2c -size 18545 +oid sha256:87ce5ffee7e77cf6c67fe2918604d901755897739a7aa250adc85f8dca8fa384 +size 19027 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en.png index 73108c3e5e..95cba114c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc916f49b9eec8c1da8525ad86a6cb806c8b9fd4af5d18c32691433bb152a11e -size 20350 +oid sha256:177ebd3790437dad70044a183be5f109e457e7be2ef297bee9a214f41f136d54 +size 20824 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en.png index 56ba5317cf..badae519ce 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11a0303a1d1f885db2dbda1a770518602c0ead94baf5500a3c06a4482c095a88 -size 20615 +oid sha256:bfd039feb28f9322719e4508c8fdc3a723deda4b5ab5f5035b668d764e398e99 +size 21087 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en.png index 507c15196b..9c78d03df2 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46a75f785463c85ab9a6038669cdd41d64dc043eca222f6029ef3b51bc1014c6 -size 17952 +oid sha256:5f89e40f49b0d0b9167aad620842f75005c9b37ca52c86bd0c902b79e387bd14 +size 18330 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en.png index 9f6d90cbba..80b137bf05 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06c03db06ec5ecb8e66b60beefb8ecc1ebcb820d2aa1dd9ea6dd2e294bc2b62f -size 19654 +oid sha256:55c2e188b78a4019955cbf765dde70b771b94972939fe785c4134f6f2cb258b6 +size 20042 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en.png index ebe9f368b7..4fd3feb683 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54d7136e55a632186446badaab12be68afb9a062deb8433c5f6f1a0685653097 -size 19856 +oid sha256:22a0bd0eafdbbaa00b48d120b36cb048dd73b8ddaf39235bb146d79a84898c52 +size 20243 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_3_en.png index 01eba5b2e2..ac6cb1833d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daae8460b864ca17688a7e01165787f0c2610b82ff5e56645d03a3a32f4dc62d -size 22263 +oid sha256:1745dfe97efbb62bc1bcc0a6e019e48e07dbc474396d9d58e11784522e519049 +size 22733 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_4_en.png index fb098d0524..56cea808ac 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f73427a7770fa810299e156191e475cc70c0d19538dfa9c472f79e30b71230ab -size 22000 +oid sha256:50a54a812331ec421e5b111a2aac07e0a75474d750bcd65f78d05c5485b028b1 +size 22474 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_5_en.png index 08a5dc7188..3faa564978 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84ecc8f273245781423c398495913cb770513d9401e9522d5a58cdf5ec360d26 -size 19900 +oid sha256:69e7b908f96969480ea4502f54f63f3066f1ef3c67171404f2aec294c25d7e3b +size 20358 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_3_en.png index 1381a3bf62..797634155f 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8405c8fb37bf9021b15e03bf5744d7021ff47d9133e302025b35602c2063f0a -size 20397 +oid sha256:0c5474b5be24946927efb2c6bf7a67a5ea378a63e16714ebe0a4cfbf76794bdf +size 20775 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_4_en.png index 97bbc53c56..86b29dc98b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddfa75b7c5332287cb498d769eee8ede8f234b0347c552c6435aedcfcd626039 -size 20170 +oid sha256:43d72cf8272c38c6646c19e876f109d3e22f9aa91bd39af9a78bd15cc47bbf82 +size 20542 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_5_en.png index f778ba87a4..5de6b5467b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e08c05bf0c42b2b546c3dc3a8573f2a3294cf63059c495cf284b7302228b3e7 -size 18202 +oid sha256:4fc6ca5ca4298f69e1a036c164a9a832ebf9595f6ec92d81243d90ddb1cbc1a6 +size 18591 From d000f956b6730e241204c2e72e39d5d03566ea6a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Jul 2024 12:00:04 +0200 Subject: [PATCH 008/115] Add Konsist test to ensure that we invoke callback method on all the Callback instances. --- .../tests/konsist/KonsistCallbackTest.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt new file mode 100644 index 0000000000..4937595ef9 --- /dev/null +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.konsist + +import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.verify.assertFalse +import org.junit.Test + +class KonsistCallbackTest { + @Test + fun `we should not invoke Callback Input directly, we should use forEach`() { + Konsist + .scopeFromProduction() + .files + .assertFalse { + it.text.contains("callback?.") + } + } +} From 53ff50331649943e7f77f78ae5382c544733768f Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 Jul 2024 12:01:49 +0200 Subject: [PATCH 009/115] Changelog for version 0.4.16 --- CHANGES.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 4046f06e0b..c2c2641b34 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,77 @@ +Changes in Element X v0.4.16 (2024-07-05) +========================================= + +### ✨ Features +* Avatar cluster for DM by @bmarty in https://github.com/element-hq/element-x-android/pull/3069 +* Feature : Draft support by @ganfra in https://github.com/element-hq/element-x-android/pull/3099 +* Timeline : re-enable edition of local echo by @ganfra in https://github.com/element-hq/element-x-android/pull/3126 +* Draft : add volatile storage when moving to edit mode. by @ganfra in https://github.com/element-hq/element-x-android/pull/3132 + +### 🙌 Improvements +* Give locale and theme to Element Call by @bmarty in https://github.com/element-hq/element-x-android/pull/3118 +* Let the SDK retrieve and parse Element well known content by @bmarty in https://github.com/element-hq/element-x-android/pull/3127 + +### 🐛 Bugfixes +* Let role and permissions screens works for invited room members too. by @bmarty in https://github.com/element-hq/element-x-android/pull/3081 +* Fix image rendering after clear cache by @bmarty in https://github.com/element-hq/element-x-android/pull/3082 +* Replace the 'answer' PendingIntent in ringing call notifications by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3093 +* Use IO dispatcher for cleanup in bug reporter by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3092 +* Fix `@room` mentions crashing in debug builds by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3107 +* Auth : fix restore session when there is no network. by @ganfra in https://github.com/element-hq/element-x-android/pull/3109 +* Alert for incoming call even if notifications are disabled - WAITING FOR FINAL PRODUCT DECISION by @bmarty in https://github.com/element-hq/element-x-android/pull/3053 +* Fix incorrect 'device verified' screen when app was opened with no network connection by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3110 +* Draft : also clear draft when composer is blank by @ganfra in https://github.com/element-hq/element-x-android/pull/3115 +* Timeline : fix text item not refreshed when content change by @ganfra in https://github.com/element-hq/element-x-android/pull/3123 +* FFs can now be toggled in release builds too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3101 +* Fix crash when getting the system ringtone for ringing calls by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3131 +* Bugfix : avoid potential NPE on verification service. by @ganfra in https://github.com/element-hq/element-x-android/pull/3140 + +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3114 +* Sync Strings - Add Greek translations by @ElementBot in https://github.com/element-hq/element-x-android/pull/3133 + +### 🧱 Build +* Let GitHub generates the release notes by @bmarty in https://github.com/element-hq/element-x-android/pull/3105 +* Fix F-Droid reproducible build. by @bmarty in https://github.com/element-hq/element-x-android/pull/3106 +* Element enterprise (EE) foundations by @bmarty in https://github.com/element-hq/element-x-android/pull/3025 +* Fix Element Enterprise nightly build and publication using App Distribution by @bmarty in https://github.com/element-hq/element-x-android/pull/3130 +* Improve screenshot testing with ComposablePreviewScanner by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3125 + +### Dependency upgrades +* Update dependency com.posthog:posthog-android to v3.4.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3060 +* Update danger/danger-js action to v12.3.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3059 +* Update dependency com.freeletics.flowredux:compose to v1.2.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3067 +* Update dependency com.google.firebase:firebase-bom to v33.1.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3062 +* Update dependency androidx.test.ext:junit to v1.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3088 +* Update test.core to v1.6.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3090 +* Remove dependencies androidx.test.espresso:espresso-core and androidx.appcompat:appcompat by @renovate in https://github.com/element-hq/element-x-android/pull/3087 +* Update wysiwyg to v2.37.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3094 +* Update dependency androidx.test:runner to v1.6.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3089 +* Update test.core to v1.6.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3104 +* Update dependency androidx.test:runner to v1.6.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3103 +* Update dependency androidx.test.ext:junit to v1.2.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3102 +* Update dependency com.google.truth:truth to v1.4.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3108 +* Update dependency com.posthog:posthog-android to v3.4.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3111 +* Update dependency io.nlopez.compose.rules:detekt to v0.4.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3116 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.29 by @renovate in https://github.com/element-hq/element-x-android/pull/3119 +* Update plugin dependencycheck to v10 by @renovate in https://github.com/element-hq/element-x-android/pull/3128 +* Update plugin dependencycheck to v10.0.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3129 +* Update dependency io.sentry:sentry-android to v7.11.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3122 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.30 by @renovate in https://github.com/element-hq/element-x-android/pull/3138 + +### Others +* Feature/fga/sending queue iteration by @ganfra in https://github.com/element-hq/element-x-android/pull/3054 +* Use full date format for day dividers in timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3057 +* Let Dms use other member color. by @bmarty in https://github.com/element-hq/element-x-android/pull/3058 +* Resolve display names in mentions in real time by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3051 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3077 +* Improve the way we cut the bubble layout to give space for the sender Avatar by @bmarty in https://github.com/element-hq/element-x-android/pull/3080 +* Upgrade build tools and fix `pg-map-id` for F-Droid by @bmarty in https://github.com/element-hq/element-x-android/pull/3084 +* Improve room filtering behavior. by @bmarty in https://github.com/element-hq/element-x-android/pull/3083 +* Adapt our code to the new authentication APIs in the Rust SDK by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3068 +* Add temporary icon for Element Enterprise by @bmarty in https://github.com/element-hq/element-x-android/pull/3134 +* Improve click behavior on room timeline title by @bmarty in https://github.com/element-hq/element-x-android/pull/3064 + Changes in Element X v0.4.15 (2024-06-19) ========================================= From a7de751f0ec46fcc8e3116f78edfd8f0f9e6a613 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Jul 2024 12:13:21 +0200 Subject: [PATCH 010/115] Ensure that all the callback instances are invoked. --- .../messages/impl/MessagesFlowNode.kt | 11 +++--- .../features/messages/impl/MessagesNode.kt | 34 +++++++++++-------- .../securebackup/impl/SecureBackupFlowNode.kt | 6 ++-- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 132b8020da..3999a5fe0c 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 @@ -107,6 +107,7 @@ class MessagesFlowNode @AssistedInject constructor( plugins = plugins ) { data class Inputs(val focusedEventId: EventId?) : NodeInputs + private val inputs = inputs() sealed interface NavTarget : Parcelable { @@ -148,7 +149,7 @@ class MessagesFlowNode @AssistedInject constructor( data class EditPoll(val eventId: EventId) : NavTarget } - private val callback = plugins().firstOrNull() + private val callbacks = plugins() private val mentionSpanProvider = mentionSpanProviderFactory.create(room.sessionId.value) @@ -167,7 +168,7 @@ class MessagesFlowNode @AssistedInject constructor( is NavTarget.Messages -> { val callback = object : MessagesNode.Callback { override fun onRoomDetailsClick() { - callback?.onRoomDetailsClick() + callbacks.forEach { it.onRoomDetailsClick() } } override fun onEventClick(event: TimelineItem.Event): Boolean { @@ -179,11 +180,11 @@ class MessagesFlowNode @AssistedInject constructor( } override fun onUserDataClick(userId: UserId) { - callback?.onUserDataClick(userId) + callbacks.forEach { it.onUserDataClick(userId) } } override fun onPermalinkClick(data: PermalinkData) { - callback?.onPermalinkClick(data) + callbacks.forEach { it.onPermalinkClick(data) } } override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { @@ -250,7 +251,7 @@ class MessagesFlowNode @AssistedInject constructor( val inputs = ForwardMessagesNode.Inputs(navTarget.eventId) val callback = object : ForwardMessagesNode.Callback { override fun onForwardedToSingleRoom(roomId: RoomId) { - this@MessagesFlowNode.callback?.onForwardedToSingleRoom(roomId) + callbacks.forEach { it.onForwardedToSingleRoom(roomId) } } } createNode(buildContext, listOf(inputs, callback)) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index d1e3f87fe3..b2ee1053a6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -78,7 +78,7 @@ class MessagesNode @AssistedInject constructor( private val timelineController: TimelineController, ) : Node(buildContext, plugins = plugins), MessagesNavigator { private val presenter = presenterFactory.create(this) - private val callback = plugins().firstOrNull() + private val callbacks = plugins() data class Inputs(val focusedEventId: EventId?) : NodeInputs @@ -113,19 +113,25 @@ class MessagesNode @AssistedInject constructor( } private fun onRoomDetailsClick() { - callback?.onRoomDetailsClick() + callbacks.forEach { it.onRoomDetailsClick() } } private fun onEventClick(event: TimelineItem.Event): Boolean { - return callback?.onEventClick(event).orFalse() + // Note: cannot use `callbacks.all { it.onEventClick(event) }` because: + // - if callbacks is empty, it will return true and we want to return false. + // - if a callback returns false, the other callback will not be invoked. + return callbacks.takeIf { it.isNotEmpty() } + ?.map { it.onEventClick(event) } + ?.all { it } + .orFalse() } private fun onPreviewAttachments(attachments: ImmutableList) { - callback?.onPreviewAttachments(attachments) + callbacks.forEach { it.onPreviewAttachments(attachments) } } private fun onUserDataClick(userId: UserId) { - callback?.onUserDataClick(userId) + callbacks.forEach { it.onUserDataClick(userId) } } private fun onLinkClick( @@ -137,7 +143,7 @@ class MessagesNode @AssistedInject constructor( is PermalinkData.UserLink -> { // Open the room member profile, it will fallback to // the user profile if the user is not in the room - callback?.onUserDataClick(permalink.userId) + callbacks.forEach { it.onUserDataClick(permalink.userId) } } is PermalinkData.RoomLink -> { handleRoomLinkClick(permalink, eventSink) @@ -159,36 +165,36 @@ class MessagesNode @AssistedInject constructor( context.toast("Already viewing this room!") } } else { - callback?.onPermalinkClick(roomLink) + callbacks.forEach { it.onPermalinkClick(roomLink) } } } override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { - callback?.onShowEventDebugInfoClick(eventId, debugInfo) + callbacks.forEach { it.onShowEventDebugInfoClick(eventId, debugInfo) } } override fun onForwardEventClick(eventId: EventId) { - callback?.onForwardEventClick(eventId) + callbacks.forEach { it.onForwardEventClick(eventId) } } override fun onReportContentClick(eventId: EventId, senderId: UserId) { - callback?.onReportMessage(eventId, senderId) + callbacks.forEach { it.onReportMessage(eventId, senderId) } } override fun onEditPollClick(eventId: EventId) { - callback?.onEditPollClick(eventId) + callbacks.forEach { it.onEditPollClick(eventId) } } private fun onSendLocationClick() { - callback?.onSendLocationClick() + callbacks.forEach { it.onSendLocationClick() } } private fun onCreatePollClick() { - callback?.onCreatePollClick() + callbacks.forEach { it.onCreatePollClick() } } private fun onJoinCallClick() { - callback?.onJoinCallClick(room.roomId) + callbacks.forEach { it.onJoinCallClick(room.roomId) } } @Composable diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt index e03aecad43..f54bfaee96 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt @@ -81,7 +81,7 @@ class SecureBackupFlowNode @AssistedInject constructor( data object CreateNewRecoveryKey : NavTarget } - private val callback = plugins().firstOrNull() + private val callbacks = plugins() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { @@ -130,8 +130,8 @@ class SecureBackupFlowNode @AssistedInject constructor( NavTarget.EnterRecoveryKey -> { val callback = object : SecureBackupEnterRecoveryKeyNode.Callback { override fun onEnterRecoveryKeySuccess() { - if (callback != null) { - callback.onDone() + if (callbacks.isNotEmpty()) { + callbacks.forEach { it.onDone() } } else { backstack.pop() } From 35af93d7f0f425a85375a131353b4c413c1c7399 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Jul 2024 12:29:14 +0200 Subject: [PATCH 011/115] Set targetSDK to 34 --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 68512dfc62..df1e90a378 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -62,7 +62,7 @@ object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch val versionName = "$versionMajor.$versionMinor.$versionPatch" const val compileSdk = 34 - const val targetSdk = 33 + const val targetSdk = 34 // When updating the `minSdk`, make sure to update the value of `minSdkVersion` in the file `tools/release/release.sh` val minSdk = if (isEnterpriseBuild) 26 else 24 val javaCompileVersion = JavaVersion.VERSION_17 From 1f69722bdd030c212bf8a8848ddaa8408a89b3b4 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Fri, 5 Jul 2024 15:15:18 +0200 Subject: [PATCH 012/115] Remove Showkase processor not found warning from Danger (#3148) Instead create a new rule to check if the package name is included in `ComposablePreviewProvider`. --- .../kotlin/base/ComposablePreviewProvider.kt | 20 +++++---- tools/danger/dangerfile.js | 42 ++++++++++++++----- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt b/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt index a35d863671..5267ca6ba9 100644 --- a/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt +++ b/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt @@ -23,18 +23,20 @@ import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreview import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview +// Make sure we don't import Compound previews by mistake +private val PACKAGE_TREES = arrayOf( + "io.element.android.features", + "io.element.android.libraries", + "io.element.android.services", + "io.element.android.appicon", + "io.element.android.appnav", + "io.element.android.x", +) + object ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { private val values: List>> by lazy { AndroidComposablePreviewScanner() - .scanPackageTrees( - "io.element.android.features", - "io.element.android.libraries", - "io.element.android.services", - "io.element.android.appicon", - "io.element.android.appnav", - "io.element.android.x", - // Make sure we don't import Compound previews by mistake - ) + .scanPackageTrees(*PACKAGE_TREES) .getPreviews() .withIndex() .toList() diff --git a/tools/danger/dangerfile.js b/tools/danger/dangerfile.js index 024e661c67..ae8c4cf002 100644 --- a/tools/danger/dangerfile.js +++ b/tools/danger/dangerfile.js @@ -125,19 +125,39 @@ const filesWithPreviews = editedFiles.filter(file => file.endsWith(".kt")).filte return previewAnnotations.some((ann) => content.includes(ann)); }) -const buildFilesWithMissingProcessor = filesWithPreviews.map(file => { - let parent = path.dirname(file); - while (fs.statSync(path.join(parent, 'build.gradle.kts'), {throwIfNoEntry: false}) === undefined) { - parent = path.dirname(parent); +const composablePreviewProviderContents = fs.readFileSync('tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt'); +const packageTreesRegex = /private val PACKAGE_TREES = arrayOf\(([\w\W]+?)\n\)/gm; +const packageTreesMatch = packageTreesRegex.exec(composablePreviewProviderContents)[1]; +const scannedPreviewPackageTrees = packageTreesMatch + .replaceAll("\"", "") + .replaceAll(",", "") + .split('\n').map((line) => line.trim()) + .filter((line) => line.length > 0); + +const previewPackagesNotIncludedInScreenshotTests = filesWithPreviews.map((file) => { + const content = fs.readFileSync(file); + const packageRegex = /package\s+([a-zA-Z0-9.]+)/; + const packageMatch = packageRegex.exec(content); + + if (!packageMatch || packageMatch.length != 2) { + return null; } - return path.join(parent, 'build.gradle.kts'); -}).filter((value, index, array) => array.indexOf(value) === index).filter(buildFile => { - const content = fs.readFileSync(buildFile); - return !content.includes('ksp(libs.showkase.processor)'); -}) -if (buildFilesWithMissingProcessor.length > 0) { - warn("You have made changes to a file containing a `@Preview` annotated function but its module doesn't include the showkase processor. Missing processor in: " + buildFilesWithMissingProcessor.join(", ")) + return packageMatch[1]; + + +}).filter((package) => { + if (!package) { + return false; + } + if (!scannedPreviewPackageTrees.some((prefix) => package.includes(prefix))) { + return true; + } +}); + +if (previewPackagesNotIncludedInScreenshotTests.length > 0) { + const packagesList = previewPackagesNotIncludedInScreenshotTests.map((p) => '- `' + p + '`').join("\n"); + warn("You have made changes to a file containing a `@Preview` annotated function but its package name prefix is not included in the `ComposablePreviewProvider`.\nPackages missing in `tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt`: \n" + packagesList); } // Check for pngs on resources From 1a99a9f0e6bef1f92b84e389ba07599f1bdd91b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Jul 2024 16:24:57 +0200 Subject: [PATCH 013/115] Format file. No other change. --- features/call/impl/src/main/AndroidManifest.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/features/call/impl/src/main/AndroidManifest.xml b/features/call/impl/src/main/AndroidManifest.xml index 354ea7533d..4edfb73362 100644 --- a/features/call/impl/src/main/AndroidManifest.xml +++ b/features/call/impl/src/main/AndroidManifest.xml @@ -77,10 +77,11 @@ - @@ -90,9 +91,10 @@ android:exported="false" android:foregroundServiceType="phoneCall" /> - + From 68efc918bafc957b7850bc8f155a52ea9aec0bc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:25:42 +0000 Subject: [PATCH 014/115] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.31 (#3145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependency org.matrix.rustcomponents:sdk-android to v0.2.31 * Use new Rust client side sorting API * Make `RoomListEntriesUpdate.describe()` an extension function * Remove `RoomListSummary.Filled` and `RoomListSummary.Empty` * Fix icon sizes to pass the lint checks * Update screenshots --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jorge Martín Co-authored-by: ElementBot --- .../ic_launcher_background_enterprise.webp | Bin 10400 -> 2458 bytes .../ic_launcher_background_enterprise.webp | Bin 4836 -> 1428 bytes .../ic_launcher_background_enterprise.webp | Bin 19754 -> 4050 bytes .../ic_launcher_background_enterprise.webp | Bin 46592 -> 7436 bytes .../ic_launcher_foreground_enterprise.webp | Bin 17626 -> 4816 bytes .../ic_launcher_background_enterprise.webp | Bin 86550 -> 14080 bytes .../forward/ForwardMessagesPresenterTest.kt | 6 +- ...EditDefaultNotificationSettingPresenter.kt | 14 +- .../EditDefaultNotificationSettingState.kt | 2 +- ...DefaultNotificationSettingStateProvider.kt | 17 +-- .../EditDefaultNotificationSettingView.kt | 10 +- ...efaultNotificationSettingsPresenterTest.kt | 9 +- .../features/roomlist/impl/RoomListEvents.kt | 1 - .../roomlist/impl/RoomListPresenter.kt | 13 -- .../impl/components/RoomListContentView.kt | 24 +--- .../impl/datasource/RoomListDataSource.kt | 12 +- .../datasource/RoomListRoomSummaryFactory.kt | 36 +---- .../impl/search/RoomListSearchDataSource.kt | 2 - .../roomlist/impl/RoomListPresenterTest.kt | 52 +------- .../search/RoomListSearchPresenterTest.kt | 8 +- gradle/libs.versions.toml | 2 +- .../matrix/api/roomlist/RoomListService.kt | 6 - .../matrix/api/roomlist/RoomSummary.kt | 14 +- .../libraries/matrix/impl/RustMatrixClient.kt | 2 +- .../impl/roomlist/RoomListEntriesUpdateExt.kt | 57 ++++++++ .../impl/roomlist/RoomListExtensions.kt | 5 +- .../matrix/impl/roomlist/RoomListFilter.kt | 14 +- .../roomlist/RoomSummaryDetailsFactory.kt | 6 +- .../impl/roomlist/RoomSummaryListProcessor.kt | 43 +++--- .../impl/roomlist/RustRoomListService.kt | 18 --- .../impl/roomlist/RoomListFilterTest.kt | 45 +++---- .../roomlist/RoomSummaryListProcessorTest.kt | 123 +++++++++++++++--- .../matrix/test/room/RoomSummaryFixture.kt | 31 ++--- .../test/roomlist/FakeRoomListService.kt | 7 - .../components/RoomSummaryDetailsProvider.kt | 8 +- .../matrix/ui/components/SelectedRoom.kt | 10 +- .../matrix/ui/model/RoomSummaryExtension.kt | 4 +- .../roomselect/impl/RoomSelectEvents.kt | 4 +- .../roomselect/impl/RoomSelectPresenter.kt | 4 +- .../impl/RoomSelectSearchDataSource.kt | 5 +- .../roomselect/impl/RoomSelectState.kt | 6 +- .../impl/RoomSelectStateProvider.kt | 6 +- .../roomselect/impl/RoomSelectView.kt | 14 +- .../impl/RoomSelectPresenterTest.kt | 11 +- .../images/appicon.enterprise_Icon_en.png | 4 +- .../appicon.enterprise_RoundIcon_en.png | 4 +- 46 files changed, 302 insertions(+), 357 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt diff --git a/appicon/enterprise/src/main/res/mipmap-hdpi/ic_launcher_background_enterprise.webp b/appicon/enterprise/src/main/res/mipmap-hdpi/ic_launcher_background_enterprise.webp index f051ae3c8182f854f9f54015821d7140788a3f40..45f77567486efa5adb1683e35c6ed074e187e8ea 100644 GIT binary patch literal 2458 zcmV;L31#+DNk&GJ2><|BMM6+kP&iD62><{uN5ByP2}NxrA=!i9`8Uj9Afo>XSBwll zSTKqtDN;(PCocjC__qgpNErC&{{&3j0|H5aoRDj`;g+uHCFJB^X((vhhDpkwb$2L4 z!~_7?_-06=W;QDW9lo?|+YZ|{7jnnn87KIVlQEoQ7<^A)yhJ8#lYR4VxD4C2jU>7I zFMLlG6JWWKBtep-;GgEHut!$&V19u*4Xtg=2ndS&9}Pj4MW+w}f5e~AhB|2{C<;ILi0_t^Fhd%N9Ma;t1LTKl-v z={TK0tz}?MNDXK`InPJi4?cw>Ywy@Vx%J8%O~p-YhMk$(8D|`wV5!_mF5Rr;QfmEv z*nZ?57LILS;ohg z0aF?Lq6knRxdU_qjRR#kdnd!#mW4@05lbem7gn)%6|g0KAmPGXrT{TiL>`@R80z3S zr;kpU+Tnp^$e83p2hf}=x>ZFb4nZ7Fr~@%=Zl=?TT00Cw@vcZOez_!<4*uBBGCxq= z2r1%(GWgmvJ6*0!oify+H+kGIm*JMVj8wOzDk2K=fi*buop^N1a{K-dsVhA7%$7-}u`QlXnBTwsMC6%`ao-SG{ig-DE7t(96><2MI3 z;lfnQ|x)1GvlFnqwk@z+t=qaZ5}Y2AB+3$t9Pfsc(-zqN1QUhyXIsQ5Zpn zOAM8@bCY^w@+E_w-0}S~Kb3bIS2)O<1c1RQ;H0!tmNIDS0!iUAKC4?U6BWS&!ef&N z#DJ_YO7it@U#cu+Nv;6X%kb={-=m^D5IOu1n-6dSsG%^*Ao1Zhqw;Qzg!ih;R6Y;n z00NT0hR2Y_Nc@XVf_v8pd;v1Pyj^f&S`kH38^LAgO~Uj1w|5~en~1~!b=>4uxX|PR z-$PF!cBs5TQt*JyAl%1a^5D)T{ARAnfdv2O4GDBpo-w%iz{!yUdHNUN4rH;-aJk%x z!YG*`ys}1;F^$6~>N2%|I>uX+}WC_>5@x>?476x%avlA}Mm?W*Htxczc$evl8awwB=;;gEpbvkdM@ zxFDWHltxcAuAYjr`dk0tv%Hs4t`|}w@L|~|_3s0-;R2UK8eE)H?lg+d$)0-E>&yG( zrD`fSq9E{dDtwH6(4jdcG>CUOC{DGj(ec};yF`^YIKU|?Ol}K$K`utIK|fTRt6Jt& zd9pJ8FOqlsw?RIZ}#-udFRAzAsp$ z_7#wXyxCNQ=9FlI+EPb#6uYUT8+9F4+pQHX@k&(80ki-ih<}wUHcA_+nJ5 zK9TqumMm#iLP*H3y;hgqLuABlormM<4(hn<$TAg|%k^%(Y@NGNy=^mI-u~JoZ53IEWrr*#zNqcH?^11>TO09E_T8NyUXh)j2|H?r z#gt*pwkrIepth&%Czb_|Mu}TSABPT-R+Y+mYK`zZZA77m$r9nEB1}jh?HPU2F`t-l&#Cl zPIjKSx@PyDvfs9P+crvjv_WZ*5(JDd?1=XG%4*v;WpPa|+rI5Lm)y2bXx(3CASD9L zKm!s#WsOB^w{+YliJNr4%UfHU>|#p~v?4V3h`IPgXs7{yG781Awal(x5^cSkx3n!c z+Mpnoi$WxW#PNp8E<82%jcsCL(zlkaL!}uu(#sM+0io`dm}A7Lh(t8xN%UDAVaroY zT1(WT&8=91^0X)y)J=jz1S4t=z8Jd@)sp^1lYdQ2uu z-zA2T(x0G8i`bk$&#g0Q}+^$Ac5C?bFY`DhS+O9abW9A~&r YnK+B71rYJK37|L3k%0BYfv-A51#IT4)Bpeg literal 10400 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 KA#fWzH1Yw8p&!Tq diff --git a/appicon/enterprise/src/main/res/mipmap-xhdpi/ic_launcher_background_enterprise.webp b/appicon/enterprise/src/main/res/mipmap-xhdpi/ic_launcher_background_enterprise.webp index 4dbc6db06628700b9c37d152664356391afb3c70..f73e1d5ff4d5b9243efbcc645cb97fc8b123f2c7 100644 GIT binary patch literal 4050 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 ECXRXU)&Kwi literal 19754 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 J-$krb!vRUVJc|GT diff --git a/appicon/enterprise/src/main/res/mipmap-xxhdpi/ic_launcher_background_enterprise.webp b/appicon/enterprise/src/main/res/mipmap-xxhdpi/ic_launcher_background_enterprise.webp index b635d5cbb567e87a807086c15ed36d916e80ac85..65027bb480f22f5375f8cbdc9f8f4cd4b3ac5b51 100644 GIT binary patch literal 7436 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 KqYv0lS7`vnA=SA6 literal 46592 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

GA2@yrOJA=j2|%La6;(4CV73<~(jO9kGCG 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 XtN3$912~w^`8-zOr#RFOHDt8_j*wy` diff --git a/appicon/enterprise/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_enterprise.webp b/appicon/enterprise/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_enterprise.webp index 2250307a038692fa985bc971c6d2082db1092c26..90b491a343cd7f46964efe1d34c2644b7ded943b 100644 GIT binary patch delta 4174 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 sjYzx%dd#~oIj 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 yQZ4A$Sq1%C((t-jT=#gXpm8PoY^3G%& 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 F`vFW&FYy2X diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt index e30011999f..ffa100d95e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails +import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.LiveTimelineProvider import io.element.android.tests.testutils.WarmUpRule @@ -64,7 +64,7 @@ class ForwardMessagesPresenterTest { presenter.present() }.test { skipItems(1) - val summary = aRoomSummaryDetails() + val summary = aRoomSummary() presenter.onRoomSelected(listOf(summary.roomId)) val forwardingState = awaitItem() assertThat(forwardingState.forwardAction.isLoading()).isTrue() @@ -88,7 +88,7 @@ class ForwardMessagesPresenterTest { presenter.present() }.test { skipItems(1) - val summary = aRoomSummaryDetails() + val summary = aRoomSummary() presenter.onRoomSelected(listOf(summary.roomId)) skipItems(1) val failedForwardState = awaitItem() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 3f1f62aafc..36f4b8f00b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -66,7 +66,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val roomsWithUserDefinedMode: MutableState> = remember { + val roomsWithUserDefinedMode: MutableState> = remember { mutableStateOf(listOf()) } @@ -115,7 +115,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( .launchIn(this) } - private fun CoroutineScope.observeRoomSummaries(roomsWithUserDefinedMode: MutableState>) { + private fun CoroutineScope.observeRoomSummaries(roomsWithUserDefinedMode: MutableState>) { roomListService.allRooms .summaries .onEach { @@ -126,18 +126,18 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( private fun CoroutineScope.updateRoomsWithUserDefinedMode( summaries: List, - roomsWithUserDefinedMode: MutableState> + roomsWithUserDefinedMode: MutableState> ) = launch { val roomWithUserDefinedRules: Set = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet() val sortedSummaries = summaries - .filterIsInstance() + .filterIsInstance() .filter { - val room = matrixClient.getRoom(it.details.roomId) ?: return@filter false - roomWithUserDefinedRules.contains(it.identifier()) && isOneToOne == room.isOneToOne + val room = matrixClient.getRoom(it.roomId) ?: return@filter false + roomWithUserDefinedRules.contains(it.roomId.value) && isOneToOne == room.isOneToOne } // locale sensitive sorting - .sortedWith(compareBy(Collator.getInstance()) { it.details.name }) + .sortedWith(compareBy(Collator.getInstance()) { it.name }) roomsWithUserDefinedMode.value = sortedSummaries } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index 68b29232b0..99d7ccbf79 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -24,7 +24,7 @@ import kotlinx.collections.immutable.ImmutableList data class EditDefaultNotificationSettingState( val isOneToOne: Boolean, val mode: RoomNotificationMode?, - val roomsWithUserDefinedMode: ImmutableList, + val roomsWithUserDefinedMode: ImmutableList, val changeNotificationSettingAction: AsyncAction, val displayMentionsOnlyDisclaimer: Boolean, val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 703050227e..293375abfe 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails import kotlinx.collections.immutable.persistentListOf @@ -53,13 +52,11 @@ private fun anEditDefaultNotificationSettingsState( private fun aRoomSummary( name: String?, -) = RoomSummary.Filled( - aRoomSummaryDetails( - roomId = RoomId("!roomId:domain"), - name = name, - avatarUrl = null, - isDirect = false, - lastMessage = null, - notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, - ) +) = aRoomSummaryDetails( + roomId = RoomId("!roomId:domain"), + name = name, + avatarUrl = null, + isDirect = false, + lastMessage = null, + notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index c69970bd3f..2adf34a98b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -89,7 +89,7 @@ fun EditDefaultNotificationSettingView( if (state.roomsWithUserDefinedMode.isNotEmpty()) { PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_edit_custom_settings_section_title)) { state.roomsWithUserDefinedMode.forEach { summary -> - val subtitle = when (summary.details.userDefinedNotificationMode) { + val subtitle = when (summary.userDefinedNotificationMode) { RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_notification_settings_edit_mode_all_messages) RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> { stringResource(id = R.string.screen_notification_settings_edit_mode_mentions_and_keywords) @@ -99,7 +99,7 @@ fun EditDefaultNotificationSettingView( } ListItem( headlineContent = { - val roomName = summary.details.name + val roomName = summary.name Text( text = roomName ?: stringResource(id = CommonStrings.common_no_room_name), fontStyle = FontStyle.Italic.takeIf { roomName == null } @@ -110,14 +110,14 @@ fun EditDefaultNotificationSettingView( }, leadingContent = ListItemContent.Custom { CompositeAvatar( - avatarData = summary.details.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting), - heroes = summary.details.heroes.map { user -> + avatarData = summary.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting), + heroes = summary.heroes.map { user -> user.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting) }.toPersistentList() ) }, onClick = { - openRoomNotificationSettings(summary.details.roomId) + openRoomNotificationSettings(summary.roomId) } ) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt index 0d5921047c..784bec715b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt @@ -23,13 +23,12 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails +import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -72,11 +71,11 @@ class EditDefaultNotificationSettingsPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - roomListService.postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails(notificationMode = RoomNotificationMode.ALL_MESSAGES)))) + roomListService.postAllRooms(listOf(aRoomSummary(notificationMode = RoomNotificationMode.ALL_MESSAGES))) val loadedState = consumeItemsUntilPredicate { state -> - state.roomsWithUserDefinedMode.any { it.details.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES } + state.roomsWithUserDefinedMode.any { it.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES } }.last() - assertThat(loadedState.roomsWithUserDefinedMode.any { it.details.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue() + assertThat(loadedState.roomsWithUserDefinedMode.any { it.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue() } } 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 aa1e8e2832..7919fd2274 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 @@ -20,7 +20,6 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.libraries.matrix.api.core.RoomId sealed interface RoomListEvents { - data class UpdateVisibleRange(val range: IntRange) : RoomListEvents data object DismissRequestVerificationPrompt : RoomListEvents data object DismissRecoveryKeyPrompt : RoomListEvents data object ToggleSearchResults : RoomListEvents 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 e85dcac2ee..8d838441e6 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 @@ -78,8 +78,6 @@ import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import javax.inject.Inject -private const val EXTENDED_RANGE_SIZE = 40 - class RoomListPresenter @Inject constructor( private val client: MatrixClient, private val networkMonitor: NetworkMonitor, @@ -124,7 +122,6 @@ class RoomListPresenter @Inject constructor( fun handleEvents(event: RoomListEvents) { when (event) { - is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range) RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true RoomListEvents.DismissRecoveryKeyPrompt -> securityBannerDismissed = true RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility) @@ -286,16 +283,6 @@ class RoomListPresenter @Inject constructor( } } } - - private fun updateVisibleRange(range: IntRange) { - if (range.isEmpty()) return - val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2 - val extendedRangeStart = (range.first - midExtendedRangeSize).coerceAtLeast(0) - // Safe to give bigger size than room list - val extendedRangeEnd = range.last + midExtendedRangeSize - val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd) - client.roomListService.updateAllRoomsVisibleRange(extendedRange) - } } @VisibleForTesting diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index 377606cacf..ad4a7b0c26 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -30,17 +30,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons @@ -171,25 +165,9 @@ private fun RoomsViewList( modifier: Modifier = Modifier, ) { val lazyListState = rememberLazyListState() - val visibleRange by remember { - derivedStateOf { - val layoutInfo = lazyListState.layoutInfo - val firstItemIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: 0 - val size = layoutInfo.visibleItemsInfo.size - firstItemIndex until firstItemIndex + size - } - } - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - eventSink(RoomListEvents.UpdateVisibleRange(visibleRange)) - return super.onPostFling(consumed, available) - } - } - } LazyColumn( state = lazyListState, - modifier = modifier.nestedScroll(nestedScrollConnection), + modifier = modifier, // FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80 contentPadding = PaddingValues(bottom = 80.dp) ) { diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index e2508446e7..21d3da1eef 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -54,7 +54,7 @@ class RoomListDataSource @Inject constructor( private val lock = Mutex() private val diffCache = MutableListDiffCache() private val diffCacheUpdater = DiffCacheUpdater(diffCache = diffCache, detectMoves = true) { old, new -> - old?.identifier() == new?.identifier() + old?.roomId == new?.roomId } fun launchIn(coroutineScope: CoroutineScope) { @@ -96,12 +96,8 @@ class RoomListDataSource @Inject constructor( } private fun buildAndCacheItem(roomSummaries: List, index: Int): RoomListRoomSummary? { - val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) { - is RoomSummary.Empty -> RoomListRoomSummaryFactory.createPlaceholder(roomSummary.identifier) - is RoomSummary.Filled -> roomListRoomSummaryFactory.create(roomSummary) - null -> null - } - diffCache[index] = roomListRoomSummary - return roomListRoomSummary + val roomListSummary = roomSummaries.getOrNull(index)?.let { roomListRoomSummaryFactory.create(it) } + diffCache[index] = roomListSummary + return roomListSummary } } 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 18ba73bdc1..3cde0908b4 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 @@ -20,16 +20,12 @@ 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.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter -import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.toInviteSender -import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -37,37 +33,7 @@ class RoomListRoomSummaryFactory @Inject constructor( private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, ) { - companion object { - fun createPlaceholder(id: String): RoomListRoomSummary { - return RoomListRoomSummary( - id = id, - roomId = RoomId(id), - displayType = RoomSummaryDisplayType.PLACEHOLDER, - name = "Short name", - timestamp = "hh:mm", - lastMessage = "Last message for placeholder", - avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem), - numberOfUnreadMessages = 0, - numberOfUnreadMentions = 0, - numberOfUnreadNotifications = 0, - isMarkedUnread = false, - userDefinedNotificationMode = null, - hasRoomCall = false, - isDirect = false, - isFavorite = false, - inviteSender = null, - isDm = false, - canonicalAlias = null, - heroes = persistentListOf(), - ) - } - } - - fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary { - return create(roomSummary.details) - } - - private fun create(details: RoomSummaryDetails): RoomListRoomSummary { + fun create(details: RoomSummary): RoomListRoomSummary { val avatarData = details.getAvatarData(size = AvatarSize.RoomListItem) return RoomListRoomSummary( id = details.roomId.value, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchDataSource.kt index 165c92bff8..286254952c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchDataSource.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService -import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList @@ -48,7 +47,6 @@ class RoomListSearchDataSource @Inject constructor( val roomSummaries: Flow> = roomList.filteredSummaries .map { roomSummaries -> roomSummaries - .filterIsInstance() .map(roomSummaryFactory::create) .toPersistentList() } 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 919eb87045..b70db9c87b 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 @@ -68,7 +68,7 @@ import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo -import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled +import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -183,7 +183,7 @@ class RoomListPresenterTest { roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) roomListService.postAllRooms( listOf( - aRoomSummaryFilled( + aRoomSummary( numUnreadMentions = 1, numUnreadMessages = 2, ) @@ -203,48 +203,6 @@ class RoomListPresenterTest { } } - @Test - fun `present - update visible range`() = runTest { - val roomListService = FakeRoomListService() - val matrixClient = FakeMatrixClient( - roomListService = roomListService - ) - val scope = CoroutineScope(coroutineContext + SupervisorJob()) - val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - roomListService.postAllRooms(listOf(aRoomSummaryFilled())) - val loadedState = awaitItem() - // check initial value - assertThat(roomListService.latestSlidingSyncRange).isNull() - // Test empty range - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(1, 0))) - assertThat(roomListService.latestSlidingSyncRange).isNull() - // Update visible range and check that range is transmitted to the SDK after computation - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 0))) - assertThat(roomListService.latestSlidingSyncRange) - .isEqualTo(IntRange(0, 20)) - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 1))) - assertThat(roomListService.latestSlidingSyncRange) - .isEqualTo(IntRange(0, 21)) - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(19, 29))) - assertThat(roomListService.latestSlidingSyncRange) - .isEqualTo(IntRange(0, 49)) - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(49, 59))) - assertThat(roomListService.latestSlidingSyncRange) - .isEqualTo(IntRange(29, 79)) - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 159))) - assertThat(roomListService.latestSlidingSyncRange) - .isEqualTo(IntRange(129, 179)) - loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259))) - assertThat(roomListService.latestSlidingSyncRange) - .isEqualTo(IntRange(129, 279)) - cancelAndIgnoreRemainingEvents() - scope.cancel() - } - } - @Test fun `present - handle DismissRequestVerificationPrompt`() = runTest { val scope = CoroutineScope(context = coroutineContext + SupervisorJob()) @@ -449,7 +407,7 @@ class RoomListPresenterTest { val notificationSettingsService = FakeNotificationSettingsService() val roomListService = FakeRoomListService() roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) - roomListService.postAllRooms(listOf(aRoomSummaryFilled(notificationMode = userDefinedMode))) + roomListService.postAllRooms(listOf(aRoomSummary(notificationMode = userDefinedMode))) val matrixClient = FakeMatrixClient( roomListService = roomListService, notificationSettingsService = notificationSettingsService @@ -594,7 +552,7 @@ class RoomListPresenterTest { val matrixClient = FakeMatrixClient( roomListService = roomListService, ) - val roomSummary = aRoomSummaryFilled( + val roomSummary = aRoomSummary( currentUserMembership = CurrentUserMembership.INVITED ) roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) @@ -610,7 +568,7 @@ class RoomListPresenterTest { }.last() val roomListRoomSummary = state.contentAsRooms().summaries.first { - it.id == roomSummary.identifier() + it.id == roomSummary.roomId.value } state.eventSink(RoomListEvents.AcceptInvite(roomListRoomSummary)) state.eventSink(RoomListEvents.DeclineInvite(roomListRoomSummary)) 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 89c843e222..d1fe481e77 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 @@ -28,8 +28,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService -import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled +import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope @@ -117,10 +116,7 @@ class RoomListSearchPresenterTest { assertThat(state.results).isEmpty() } roomListService.postAllRooms( - listOf( - RoomSummary.Empty("1"), - aRoomSummaryFilled() - ) + listOf(aRoomSummary()) ) awaitItem().let { state -> assertThat(state.results).hasSize(1) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8acaa7a1a5..8ca82f8679 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -162,7 +162,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" 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.30" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.31" 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" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index ca2d21a706..4f9c4c0c5a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -59,12 +59,6 @@ interface RoomListService { */ val allRooms: DynamicRoomList - /** - * Will set the visible range of all rooms. - * This is useful to load more data when the user scrolls down. - */ - fun updateAllRoomsVisibleRange(range: IntRange) - /** * The sync indicator as a flow. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt index 1e38b00b61..6841af9721 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt @@ -24,19 +24,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.user.MatrixUser -sealed interface RoomSummary { - data class Empty(val identifier: String) : RoomSummary - data class Filled(val details: RoomSummaryDetails) : RoomSummary - - fun identifier(): String { - return when (this) { - is Empty -> identifier - is Filled -> details.roomId.value - } - } -} - -data class RoomSummaryDetails( +data class RoomSummary( val roomId: RoomId, val name: String?, val canonicalAlias: RoomAlias?, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index c8574a7934..453bd51cba 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -311,7 +311,7 @@ class RustMatrixClient( withTimeout(timeout) { roomListService.allRooms.summaries .filter { roomSummaries -> - roomSummaries.map { it.identifier() }.contains(roomId.value) + roomSummaries.map { it.roomId }.contains(roomId) } .first() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt new file mode 100644 index 0000000000..c17b8d63c8 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomlist + +import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate + +internal fun RoomListEntriesUpdate.describe(): String { + return when (this) { + is RoomListEntriesUpdate.Set -> { + "Set #$index to '${value.displayName()}'" + } + is RoomListEntriesUpdate.Append -> { + "Append ${values.map { "'" + it.displayName() + "'" }}" + } + is RoomListEntriesUpdate.PushBack -> { + "PushBack '${value.displayName()}'" + } + is RoomListEntriesUpdate.PushFront -> { + "PushFront '${value.displayName()}'" + } + is RoomListEntriesUpdate.Insert -> { + "Insert at #$index: '${value.displayName()}'" + } + is RoomListEntriesUpdate.Remove -> { + "Remove #$index" + } + is RoomListEntriesUpdate.Reset -> { + "Reset all to ${values.map { "'" + it.displayName() + "'" }}" + } + RoomListEntriesUpdate.PopBack -> { + "PopBack" + } + RoomListEntriesUpdate.PopFront -> { + "PopFront" + } + RoomListEntriesUpdate.Clear -> { + "Clear" + } + is RoomListEntriesUpdate.Truncate -> { + "Truncate to $length items" + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index d987e332c8..ad987e5cae 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -76,7 +76,7 @@ internal fun RoomListInterface.entriesFlow( } } val result = entriesWithDynamicAdapters(pageSize.toUInt(), listener) - val controller = result.controller + val controller = result.controller() controller.setFilter(initialFilterKind) roomListDynamicEvents.onEach { controllerEvents -> when (controllerEvents) { @@ -92,7 +92,8 @@ internal fun RoomListInterface.entriesFlow( } }.launchIn(this) awaitClose { - result.entriesStream.cancelAndDestroy() + result.entriesStream().cancelAndDestroy() + controller.destroy() result.destroy() } }.catch { 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 d7f2acf87d..bc66bb6b78 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 @@ -26,21 +26,19 @@ val RoomListFilter.predicate is RoomListFilter.Any -> { _: RoomSummary -> true } RoomListFilter.None -> { _: RoomSummary -> false } RoomListFilter.Category.Group -> { roomSummary: RoomSummary -> - roomSummary is RoomSummary.Filled && !roomSummary.details.isDirect && !roomSummary.isInvited() + !roomSummary.isDirect && !roomSummary.isInvited() } RoomListFilter.Category.People -> { roomSummary: RoomSummary -> - roomSummary is RoomSummary.Filled && roomSummary.details.isDirect && !roomSummary.isInvited() + roomSummary.isDirect && !roomSummary.isInvited() } RoomListFilter.Favorite -> { roomSummary: RoomSummary -> - roomSummary is RoomSummary.Filled && roomSummary.details.isFavorite && !roomSummary.isInvited() + roomSummary.isFavorite && !roomSummary.isInvited() } RoomListFilter.Unread -> { roomSummary: RoomSummary -> - roomSummary is RoomSummary.Filled && - !roomSummary.isInvited() && - (roomSummary.details.numUnreadNotifications > 0 || roomSummary.details.isMarkedUnread) + !roomSummary.isInvited() && (roomSummary.numUnreadNotifications > 0 || roomSummary.isMarkedUnread) } is RoomListFilter.NormalizedMatchRoomName -> { roomSummary: RoomSummary -> - roomSummary is RoomSummary.Filled && roomSummary.details.name.orEmpty().contains(pattern, ignoreCase = true) + roomSummary.name.orEmpty().contains(pattern, ignoreCase = true) } RoomListFilter.Invite -> { roomSummary: RoomSummary -> roomSummary.isInvited() @@ -61,4 +59,4 @@ fun List.filter(filter: RoomListFilter): List { } } -private fun RoomSummary.isInvited() = this is RoomSummary.Filled && this.details.currentUserMembership == CurrentUserMembership.INVITED +private fun RoomSummary.isInvited() = currentUserMembership == CurrentUserMembership.INVITED diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index f45c843694..1361d74a3d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -18,7 +18,7 @@ package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper import io.element.android.libraries.matrix.impl.room.elementHeroes import io.element.android.libraries.matrix.impl.room.map @@ -28,12 +28,12 @@ import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.use class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { - suspend fun create(roomListItem: RoomListItem): RoomSummaryDetails { + suspend fun create(roomListItem: RoomListItem): RoomSummary { val roomInfo = roomListItem.roomInfo() val latestRoomMessage = roomListItem.latestEvent()?.use { roomMessageFactory.create(it) } - return RoomSummaryDetails( + return RoomSummary( roomId = RoomId(roomInfo.id), name = roomInfo.displayName, canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index 3cfd01f54c..8e4899d942 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -22,11 +22,10 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate -import org.matrix.rustcomponents.sdk.RoomListEntry +import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListServiceInterface import org.matrix.rustcomponents.sdk.use import timber.log.Timber -import java.util.UUID import kotlin.coroutines.CoroutineContext class RoomSummaryListProcessor( @@ -50,15 +49,17 @@ class RoomSummaryListProcessor( suspend fun rebuildRoomSummaries() { updateRoomSummaries { forEachIndexed { i, summary -> - this[i] = when (summary) { - is RoomSummary.Empty -> summary - is RoomSummary.Filled -> buildAndCacheRoomSummaryForIdentifier(summary.identifier()) + val result = buildAndCacheRoomSummaryForIdentifier(summary.roomId.value) + if (result != null) { + this[i] = result } } } } private suspend fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { + // Remove this comment to debug changes in the room list + // Timber.d("Apply room list update: ${update.describe()}") when (update) { is RoomListEntriesUpdate.Append -> { val roomSummaries = update.values.map { @@ -104,27 +105,25 @@ class RoomSummaryListProcessor( } } - private suspend fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary { - return when (entry) { - RoomListEntry.Empty -> buildEmptyRoomSummary() - is RoomListEntry.Filled -> buildAndCacheRoomSummaryForIdentifier(entry.roomId) - is RoomListEntry.Invalidated -> { - roomSummariesByIdentifier[entry.roomId] ?: buildAndCacheRoomSummaryForIdentifier(entry.roomId) - } - } + private suspend fun buildSummaryForRoomListEntry(entry: RoomListItem): RoomSummary { + return buildAndCacheRoomSummaryForRoomListItem(entry) } - private fun buildEmptyRoomSummary(): RoomSummary { - return RoomSummary.Empty(UUID.randomUUID().toString()) + private suspend fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary? { + val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem -> + buildAndCacheRoomSummaryForRoomListItem(roomListItem) + } + if (builtRoomSummary != null) { + roomSummariesByIdentifier[identifier] = builtRoomSummary + } else { + roomSummariesByIdentifier.remove(identifier) + } + return builtRoomSummary } - private suspend fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary { - val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem -> - RoomSummary.Filled( - details = roomSummaryDetailsFactory.create(roomListItem) - ) - } ?: buildEmptyRoomSummary() - roomSummariesByIdentifier[builtRoomSummary.identifier()] = builtRoomSummary + private suspend fun buildAndCacheRoomSummaryForRoomListItem(roomListItem: RoomListItem): RoomSummary { + val builtRoomSummary = roomSummaryDetailsFactory.create(roomListItem = roomListItem) + roomSummariesByIdentifier[builtRoomSummary.roomId.value] = builtRoomSummary return builtRoomSummary } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index 8b8ccc3926..d2b80a84a9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -29,10 +29,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import org.matrix.rustcomponents.sdk.RoomListException -import org.matrix.rustcomponents.sdk.RoomListInput -import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListServiceState import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator import timber.log.Timber @@ -73,20 +69,6 @@ internal class RustRoomListService( allRooms.loadAllIncrementally(sessionCoroutineScope) } - override fun updateAllRoomsVisibleRange(range: IntRange) { - Timber.v("setVisibleRange=$range") - sessionCoroutineScope.launch { - try { - val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) - innerRoomListService.applyInput( - RoomListInput.Viewport(ranges) - ) - } catch (exception: RoomListException) { - Timber.e(exception, "Failed updating visible range") - } - } - } - override val syncIndicator: StateFlow = innerRoomListService.syncIndicator() .map { it.toSyncIndicator() } 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 02fb9cd24b..5687cd0c14 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 @@ -19,46 +19,31 @@ package io.element.android.libraries.matrix.impl.roomlist import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.roomlist.RoomListFilter -import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails -import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled +import io.element.android.libraries.matrix.test.room.aRoomSummary import kotlinx.coroutines.test.runTest import org.junit.Test class RoomListFilterTest { - private val regularRoom = aRoomSummaryFilled( - aRoomSummaryDetails( - isDirect = false - ) + private val regularRoom = aRoomSummary( + isDirect = false ) - private val directRoom = aRoomSummaryFilled( - aRoomSummaryDetails( - isDirect = true - ) + private val directRoom = aRoomSummary( + isDirect = true ) - private val favoriteRoom = aRoomSummaryFilled( - aRoomSummaryDetails( - isFavorite = true - ) + private val favoriteRoom = aRoomSummary( + isFavorite = true ) - private val markedAsUnreadRoom = aRoomSummaryFilled( - aRoomSummaryDetails( - isMarkedUnread = true - ) + private val markedAsUnreadRoom = aRoomSummary( + isMarkedUnread = true ) - private val unreadNotificationRoom = aRoomSummaryFilled( - aRoomSummaryDetails( - numUnreadNotifications = 1 - ) + private val unreadNotificationRoom = aRoomSummary( + numUnreadNotifications = 1 ) - private val roomToSearch = aRoomSummaryFilled( - aRoomSummaryDetails( - name = "Room to search" - ) + private val roomToSearch = aRoomSummary( + name = "Room to search" ) - private val invitedRoom = aRoomSummaryFilled( - aRoomSummaryDetails( - currentUserMembership = CurrentUserMembership.INVITED - ) + private val invitedRoom = aRoomSummary( + currentUserMembership = CurrentUserMembership.INVITED ) private val roomSummaries = listOf( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt index 6f42553ced..394cc5d379 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt @@ -18,23 +18,31 @@ package io.element.android.libraries.matrix.impl.roomlist import com.google.common.truth.Truth.assertThat import com.sun.jna.Pointer +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_NAME +import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import org.matrix.rustcomponents.sdk.EventTimelineItem +import org.matrix.rustcomponents.sdk.Membership +import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.RoomHero +import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomList import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate -import org.matrix.rustcomponents.sdk.RoomListEntry -import org.matrix.rustcomponents.sdk.RoomListInput import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListServiceInterface import org.matrix.rustcomponents.sdk.RoomListServiceStateListener import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener +import org.matrix.rustcomponents.sdk.RoomMember +import org.matrix.rustcomponents.sdk.RoomNotificationMode import org.matrix.rustcomponents.sdk.TaskHandle // NOTE: this class is using a fake implementation of a Rust SDK interface which returns actual Rust objects with pointers. @@ -44,33 +52,34 @@ class RoomSummaryListProcessorTest { @Test fun `Append adds new entries at the end of the list`() = runTest { - summaries.value = listOf(aRoomSummaryFilled()) + summaries.value = listOf(aRoomSummary()) val processor = createProcessor() - processor.postUpdate(listOf(RoomListEntriesUpdate.Append(listOf(RoomListEntry.Empty, RoomListEntry.Empty, RoomListEntry.Empty)))) + val newEntry = FakeRoomListItem(A_ROOM_ID_2) + processor.postUpdate(listOf(RoomListEntriesUpdate.Append(listOf(newEntry, newEntry, newEntry)))) assertThat(summaries.value.count()).isEqualTo(4) - assertThat(summaries.value.subList(1, 4).all { it is RoomSummary.Empty }).isTrue() + assertThat(summaries.value.subList(1, 4).all { it.roomId == A_ROOM_ID_2 }).isTrue() } @Test fun `PushBack adds a new entry at the end of the list`() = runTest { summaries.value = listOf(aRoomSummaryFilled()) val processor = createProcessor() - processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(RoomListEntry.Empty))) + processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(FakeRoomListItem(A_ROOM_ID_2)))) assertThat(summaries.value.count()).isEqualTo(2) - assertThat(summaries.value.last()).isInstanceOf(RoomSummary.Empty::class.java) + assertThat(summaries.value.last().roomId).isEqualTo(A_ROOM_ID_2) } @Test fun `PushFront inserts a new entry at the start of the list`() = runTest { summaries.value = listOf(aRoomSummaryFilled()) val processor = createProcessor() - processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(RoomListEntry.Empty))) + processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(FakeRoomListItem(A_ROOM_ID_2)))) assertThat(summaries.value.count()).isEqualTo(2) - assertThat(summaries.value.first()).isInstanceOf(RoomSummary.Empty::class.java) + assertThat(summaries.value.first().roomId).isEqualTo(A_ROOM_ID_2) } @Test @@ -79,10 +88,10 @@ class RoomSummaryListProcessorTest { val processor = createProcessor() val index = 0 - processor.postUpdate(listOf(RoomListEntriesUpdate.Set(index.toUInt(), RoomListEntry.Empty))) + processor.postUpdate(listOf(RoomListEntriesUpdate.Set(index.toUInt(), FakeRoomListItem(A_ROOM_ID_2)))) assertThat(summaries.value.count()).isEqualTo(1) - assertThat(summaries.value[index]).isInstanceOf(RoomSummary.Empty::class.java) + assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_2) } @Test @@ -91,10 +100,10 @@ class RoomSummaryListProcessorTest { val processor = createProcessor() val index = 0 - processor.postUpdate(listOf(RoomListEntriesUpdate.Insert(index.toUInt(), RoomListEntry.Empty))) + processor.postUpdate(listOf(RoomListEntriesUpdate.Insert(index.toUInt(), FakeRoomListItem(A_ROOM_ID_2)))) assertThat(summaries.value.count()).isEqualTo(2) - assertThat(summaries.value[index]).isInstanceOf(RoomSummary.Empty::class.java) + assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_2) } @Test @@ -106,7 +115,7 @@ class RoomSummaryListProcessorTest { processor.postUpdate(listOf(RoomListEntriesUpdate.Remove(index.toUInt()))) assertThat(summaries.value.count()).isEqualTo(1) - assertThat((summaries.value[index] as RoomSummary.Filled).identifier()).isEqualTo(A_ROOM_ID_2.value) + assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_2) } @Test @@ -118,7 +127,7 @@ class RoomSummaryListProcessorTest { processor.postUpdate(listOf(RoomListEntriesUpdate.PopBack)) assertThat(summaries.value.count()).isEqualTo(1) - assertThat((summaries.value[index] as RoomSummary.Filled).identifier()).isEqualTo(A_ROOM_ID.value) + assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID) } @Test @@ -130,7 +139,7 @@ class RoomSummaryListProcessorTest { processor.postUpdate(listOf(RoomListEntriesUpdate.PopFront)) assertThat(summaries.value.count()).isEqualTo(1) - assertThat((summaries.value[index] as RoomSummary.Filled).identifier()).isEqualTo(A_ROOM_ID_2.value) + assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_2) } @Test @@ -152,7 +161,7 @@ class RoomSummaryListProcessorTest { processor.postUpdate(listOf(RoomListEntriesUpdate.Truncate(1u))) assertThat(summaries.value.count()).isEqualTo(1) - assertThat((summaries.value[index] as RoomSummary.Filled).identifier()).isEqualTo(A_ROOM_ID.value) + assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID) } private fun TestScope.createProcessor() = RoomSummaryListProcessor( @@ -168,8 +177,6 @@ class RoomSummaryListProcessorTest { return RoomList(Pointer.NULL) } - override suspend fun applyInput(input: RoomListInput) = Unit - override fun room(roomId: String): RoomListItem { return RoomListItem(Pointer.NULL) } @@ -183,3 +190,81 @@ class RoomSummaryListProcessorTest { } } } + +private fun aRustRoomInfo( + id: String = A_ROOM_ID.value, + displayName: String = A_ROOM_NAME, + rawName: String = A_ROOM_NAME, + topic: String? = null, + avatarUrl: String? = null, + isDirect: Boolean = false, + isPublic: Boolean = false, + isSpace: Boolean = false, + isTombstoned: Boolean = false, + isFavourite: Boolean = false, + canonicalAlias: String? = null, + alternativeAliases: List = listOf(), + membership: Membership = Membership.JOINED, + inviter: RoomMember? = null, + heroes: List = listOf(), + activeMembersCount: ULong = 0uL, + invitedMembersCount: ULong = 0uL, + joinedMembersCount: ULong = 0uL, + userPowerLevels: Map = mapOf(), + highlightCount: ULong = 0uL, + notificationCount: ULong = 0uL, + userDefinedNotificationMode: RoomNotificationMode? = null, + hasRoomCall: Boolean = false, + activeRoomCallParticipants: List = listOf(), + isMarkedUnread: Boolean = false, + numUnreadMessages: ULong = 0uL, + numUnreadNotifications: ULong = 0uL, + numUnreadMentions: ULong = 0uL, +) = RoomInfo( + id = id, + displayName = displayName, + rawName = rawName, + topic = topic, + avatarUrl = avatarUrl, + isDirect = isDirect, + isPublic = isPublic, + isSpace = isSpace, + isTombstoned = isTombstoned, + isFavourite = isFavourite, + canonicalAlias = canonicalAlias, + alternativeAliases = alternativeAliases, + membership = membership, + inviter = inviter, + heroes = heroes, + activeMembersCount = activeMembersCount, + invitedMembersCount = invitedMembersCount, + joinedMembersCount = joinedMembersCount, + userPowerLevels = userPowerLevels, + highlightCount = highlightCount, + notificationCount = notificationCount, + userDefinedNotificationMode = userDefinedNotificationMode, + hasRoomCall = hasRoomCall, + activeRoomCallParticipants = activeRoomCallParticipants, + isMarkedUnread = isMarkedUnread, + numUnreadMessages = numUnreadMessages, + numUnreadNotifications = numUnreadNotifications, + numUnreadMentions = numUnreadMentions +) + +class FakeRoomListItem( + private val roomId: RoomId, + private val roomInfo: RoomInfo = aRustRoomInfo(id = roomId.value), + private val latestEvent: EventTimelineItem? = null, +) : RoomListItem(NoPointer) { + override fun id(): String { + return roomId.value + } + + override suspend fun roomInfo(): RoomInfo { + return roomInfo + } + + override suspend fun latestEvent(): EventTimelineItem? { + return latestEvent + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 611a68aad6..f5ef3faa0b 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -25,7 +25,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -44,25 +43,19 @@ fun aRoomSummaryFilled( numUnreadMessages: Int = 0, notificationMode: RoomNotificationMode? = null, currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED, -) = RoomSummary.Filled( - aRoomSummaryDetails( - roomId = roomId, - name = name, - isDirect = isDirect, - avatarUrl = avatarUrl, - lastMessage = lastMessage, - numUnreadMentions = numUnreadMentions, - numUnreadMessages = numUnreadMessages, - notificationMode = notificationMode, - currentUserMembership = currentUserMembership, - ) +) = aRoomSummary( + roomId = roomId, + name = name, + isDirect = isDirect, + avatarUrl = avatarUrl, + lastMessage = lastMessage, + numUnreadMentions = numUnreadMentions, + numUnreadMessages = numUnreadMessages, + notificationMode = notificationMode, + currentUserMembership = currentUserMembership, ) -fun aRoomSummaryFilled( - details: RoomSummaryDetails = aRoomSummaryDetails(), -) = RoomSummary.Filled(details) - -fun aRoomSummaryDetails( +fun aRoomSummary( roomId: RoomId = A_ROOM_ID, name: String? = A_ROOM_NAME, isDirect: Boolean = false, @@ -80,7 +73,7 @@ fun aRoomSummaryDetails( isFavorite: Boolean = false, currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED, heroes: List = emptyList(), -) = RoomSummaryDetails( +) = RoomSummary( roomId = roomId, name = name, isDirect = isDirect, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index c489a0bb1d..f8980bb75d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -46,9 +46,6 @@ class FakeRoomListService : RoomListService { syncIndicatorStateFlow.emit(value) } - var latestSlidingSyncRange: IntRange? = null - private set - override fun createRoomList( pageSize: Int, initialFilter: RoomListFilter, @@ -65,10 +62,6 @@ class FakeRoomListService : RoomListService { MutableStateFlow(RoomListFilter.all()) ) - override fun updateAllRoomsVisibleRange(range: IntRange) { - latestSlidingSyncRange = range - } - override val state: StateFlow = roomListStateFlow override val syncIndicator: StateFlow = syncIndicatorStateFlow diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/RoomSummaryDetailsProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/RoomSummaryDetailsProvider.kt index 275848f3fc..c5e117808b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/RoomSummaryDetailsProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/RoomSummaryDetailsProvider.kt @@ -23,11 +23,11 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.message.RoomMessage -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.user.MatrixUser -open class RoomSummaryDetailsProvider : PreviewParameterProvider { - override val values: Sequence +open class RoomSummaryDetailsProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( aRoomSummaryDetails(), aRoomSummaryDetails(name = null), @@ -52,7 +52,7 @@ fun aRoomSummaryDetails( isFavorite: Boolean = false, currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED, heroes: List = emptyList(), -) = RoomSummaryDetails( +) = RoomSummary( roomId = roomId, name = name, canonicalAlias = canonicalAlias, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt index f34b071379..ca7ccce448 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt @@ -43,15 +43,15 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.toImmutableList @Composable fun SelectedRoom( - roomSummary: RoomSummaryDetails, - onRemoveRoom: (RoomSummaryDetails) -> Unit, + roomSummary: RoomSummary, + onRemoveRoom: (RoomSummary) -> Unit, modifier: Modifier = Modifier, ) { Box( @@ -100,10 +100,10 @@ fun SelectedRoom( @PreviewsDayNight @Composable internal fun SelectedRoomPreview( - @PreviewParameter(RoomSummaryDetailsProvider::class) roomSummaryDetails: RoomSummaryDetails + @PreviewParameter(RoomSummaryDetailsProvider::class) roomSummary: RoomSummary ) = ElementPreview { SelectedRoom( - roomSummary = roomSummaryDetails, + roomSummary = roomSummary, onRemoveRoom = {}, ) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomSummaryExtension.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomSummaryExtension.kt index dd86d375f3..fcae5a1a4a 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomSummaryExtension.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomSummaryExtension.kt @@ -18,9 +18,9 @@ package io.element.android.libraries.matrix.ui.model 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.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary -fun RoomSummaryDetails.getAvatarData(size: AvatarSize) = AvatarData( +fun RoomSummary.getAvatarData(size: AvatarSize) = AvatarData( id = roomId.value, name = name, url = avatarUrl, diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt index 76dce0dd5d..6f52bbda22 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt @@ -16,10 +16,10 @@ package io.element.android.libraries.roomselect.impl -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary sealed interface RoomSelectEvents { - data class SetSelectedRoom(val room: RoomSummaryDetails) : RoomSelectEvents + data class SetSelectedRoom(val room: RoomSummary) : RoomSelectEvents // TODO remove to restore multi-selection data object RemoveSelectedRoom : RoomSelectEvents diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt index e56d4dce09..0b8ba3734a 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt @@ -29,7 +29,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.roomselect.api.RoomSelectMode import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -45,7 +45,7 @@ class RoomSelectPresenter @AssistedInject constructor( @Composable override fun present(): RoomSelectState { - var selectedRooms by remember { mutableStateOf(persistentListOf()) } + var selectedRooms by remember { mutableStateOf(persistentListOf()) } var searchQuery by remember { mutableStateOf("") } var isSearchActive by remember { mutableStateOf(false) } diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt index 021c597e1e..2ad805af2d 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList @@ -48,11 +47,9 @@ class RoomSelectSearchDataSource @Inject constructor( source = RoomList.Source.All, ) - val roomSummaries: Flow> = roomList.filteredSummaries + val roomSummaries: Flow> = roomList.filteredSummaries .map { roomSummaries -> roomSummaries - .filterIsInstance() - .map { it.details } .filter { it.currentUserMembership == CurrentUserMembership.JOINED } .distinctBy { it.roomId } // This should be removed once we're sure no duplicate Rooms can be received .toPersistentList() diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt index c451570cf5..8eb15cd556 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt @@ -17,15 +17,15 @@ package io.element.android.libraries.roomselect.impl import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.roomselect.api.RoomSelectMode import kotlinx.collections.immutable.ImmutableList data class RoomSelectState( val mode: RoomSelectMode, - val resultState: SearchBarResultState>, + val resultState: SearchBarResultState>, val query: String, val isSearchActive: Boolean, - val selectedRooms: ImmutableList, + val selectedRooms: ImmutableList, val eventSink: (RoomSelectEvents) -> Unit ) diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt index 2821efd36a..134396cbe2 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails import io.element.android.libraries.roomselect.api.RoomSelectMode import kotlinx.collections.immutable.ImmutableList @@ -52,10 +52,10 @@ open class RoomSelectStateProvider : PreviewParameterProvider { private fun aRoomSelectState( mode: RoomSelectMode = RoomSelectMode.Forward, - resultState: SearchBarResultState> = SearchBarResultState.Initial(), + resultState: SearchBarResultState> = SearchBarResultState.Initial(), query: String = "", isSearchActive: Boolean = false, - selectedRooms: ImmutableList = persistentListOf(), + selectedRooms: ImmutableList = persistentListOf(), ) = RoomSelectState( mode = mode, resultState = resultState, diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt index 7ade4f759d..ba683438e6 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt @@ -56,7 +56,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.ui.components.SelectedRoom import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.roomselect.api.RoomSelectMode @@ -73,13 +73,13 @@ fun RoomSelectView( modifier: Modifier = Modifier, ) { @Suppress("UNUSED_PARAMETER") - fun onRoomRemoved(roomSummaryDetails: RoomSummaryDetails) { + fun onRoomRemoved(roomSummary: RoomSummary) { // TODO toggle selection when multi-selection is enabled state.eventSink(RoomSelectEvents.RemoveSelectedRoom) } @Composable - fun SelectedRoomsHelper(isForwarding: Boolean, selectedRooms: ImmutableList) { + fun SelectedRoomsHelper(isForwarding: Boolean, selectedRooms: ImmutableList) { if (isForwarding) return SelectedRooms( selectedRooms = selectedRooms, @@ -193,8 +193,8 @@ fun RoomSelectView( @Composable private fun SelectedRooms( - selectedRooms: ImmutableList, - onRemoveRoom: (RoomSummaryDetails) -> Unit, + selectedRooms: ImmutableList, + onRemoveRoom: (RoomSummary) -> Unit, modifier: Modifier = Modifier, ) { LazyRow( @@ -210,9 +210,9 @@ private fun SelectedRooms( @Composable private fun RoomSummaryView( - summary: RoomSummaryDetails, + summary: RoomSummary, isSelected: Boolean, - onSelection: (RoomSummaryDetails) -> Unit, + onSelection: (RoomSummary) -> Unit, ) { Row( modifier = Modifier diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt index b4117d3d40..0f4f5267c2 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt @@ -23,8 +23,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService -import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails +import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.tests.testutils.WarmUpRule @@ -69,7 +68,7 @@ class RoomSelectPresenterTest { @Test fun `present - update query`() = runTest { val roomListService = FakeRoomListService().apply { - postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails()))) + postAllRooms(listOf(aRoomSummary())) } val presenter = createRoomSelectPresenter( roomListService = roomListService @@ -78,7 +77,7 @@ class RoomSelectPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(awaitItem().resultState as? SearchBarResultState.Results).isEqualTo(SearchBarResultState.Results(listOf(aRoomSummaryDetails()))) + assertThat(awaitItem().resultState as? SearchBarResultState.Results).isEqualTo(SearchBarResultState.Results(listOf(aRoomSummary()))) initialState.eventSink(RoomSelectEvents.ToggleSearchActive) skipItems(1) initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained")) @@ -98,7 +97,7 @@ class RoomSelectPresenterTest { @Test fun `present - select and remove a room`() = runTest { val roomListService = FakeRoomListService().apply { - postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails()))) + postAllRooms(listOf(aRoomSummary())) } val presenter = createRoomSelectPresenter( roomListService = roomListService, @@ -107,7 +106,7 @@ class RoomSelectPresenterTest { presenter.present() }.test { val initialState = awaitItem() - val summary = aRoomSummaryDetails() + val summary = aRoomSummary() initialState.eventSink(RoomSelectEvents.SetSelectedRoom(summary)) assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(summary)) initialState.eventSink(RoomSelectEvents.RemoveSelectedRoom) diff --git a/tests/uitests/src/test/snapshots/images/appicon.enterprise_Icon_en.png b/tests/uitests/src/test/snapshots/images/appicon.enterprise_Icon_en.png index 1032784bd9..3d1b3338e4 100644 --- a/tests/uitests/src/test/snapshots/images/appicon.enterprise_Icon_en.png +++ b/tests/uitests/src/test/snapshots/images/appicon.enterprise_Icon_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fda6dfc8c57df5456535a8ec33f78830beaab61cc27ba4119e9576c5d641023b -size 47856 +oid sha256:fe7dd8d4416b397b63fa2b3295e94e53af62f731eaeecca43b5df298a3e834d3 +size 17253 diff --git a/tests/uitests/src/test/snapshots/images/appicon.enterprise_RoundIcon_en.png b/tests/uitests/src/test/snapshots/images/appicon.enterprise_RoundIcon_en.png index 1c729b1ba2..37bbd57740 100644 --- a/tests/uitests/src/test/snapshots/images/appicon.enterprise_RoundIcon_en.png +++ b/tests/uitests/src/test/snapshots/images/appicon.enterprise_RoundIcon_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60b169ac392d4b51e8260c595a97dd14f267facd8f428ad69814bd27e502e189 -size 44355 +oid sha256:dd6e7de23ed7048d02a2e7f412158e2372a19c0f6106d0f2876345aadfff906e +size 16247 From d3fb6a2445e85e368bb15d5b3d93e0f03e326f99 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 20:49:58 +0200 Subject: [PATCH 015/115] Update dependency com.squareup:kotlinpoet to v1.18.0 (#3150) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ca82f8679..ccd0edd85a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -180,7 +180,7 @@ maplibre = "org.maplibre.gl:android-sdk:11.0.1" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.0" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.0" opusencoder = "io.element.android:opusencoder:1.1.0" -kotlinpoet = "com.squareup:kotlinpoet:1.17.0" +kotlinpoet = "com.squareup:kotlinpoet:1.18.0" zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics From 542430d77599b228a051a8ad1aba473dd8ff374a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:22:46 +0000 Subject: [PATCH 016/115] Update plugin dependencycheck to v10.0.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccd0edd85a..b8cad9801d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -224,7 +224,7 @@ anvil = { id = "com.squareup.anvil", version.ref = "anvil" } detekt = "io.gitlab.arturbosch.detekt:1.23.6" ktlint = "org.jlleitschuh.gradle.ktlint:12.1.1" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:10.0.1" +dependencycheck = "org.owasp.dependencycheck:10.0.2" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:1.3.4" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } From 0b5893841c6136168e6e8eeb44a9041bc969c27f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 05:13:29 +0000 Subject: [PATCH 017/115] Update dependency org.robolectric:robolectric to v4.13 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccd0edd85a..2d42ff5132 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -143,7 +143,7 @@ test_konsist = "com.lemonappdev:konsist:0.15.1" test_turbine = "app.cash.turbine:turbine:1.1.0" test_truth = "com.google.truth:truth:1.4.3" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.16" -test_robolectric = "org.robolectric:robolectric:4.12.2" +test_robolectric = "org.robolectric:robolectric:4.13" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } test_composable_preview_scanner = "com.github.sergio-sastre.ComposablePreviewScanner:android:0.1.2" From 3a91b4f39d2deb976913bf8d79d82999b817e1d4 Mon Sep 17 00:00:00 2001 From: ElementBot <110224175+ElementBot@users.noreply.github.com> Date: Mon, 8 Jul 2024 08:34:58 +0100 Subject: [PATCH 018/115] Sync Strings (#3156) Co-authored-by: bmarty <3940906+bmarty@users.noreply.github.com> --- .../src/main/res/values-el/translations.xml | 2 +- .../src/main/res/values-et/translations.xml | 2 +- .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-pt/translations.xml | 2 +- .../src/main/res/values-sk/translations.xml | 2 +- .../impl/src/main/res/values/localazy.xml | 2 +- .../src/main/res/values-el/translations.xml | 1 + ...es.messages.impl_MessagesView_Day_5_de.png | 4 +- ...omListModalBottomSheetContent_Day_0_de.png | 4 +- ...omListModalBottomSheetContent_Day_1_de.png | 4 +- ...omListModalBottomSheetContent_Day_2_de.png | 4 +- ...es.roomlist.impl_RoomListView_Day_3_de.png | 4 +- ...es.roomlist.impl_RoomListView_Day_4_de.png | 4 +- ...es.roomlist.impl_RoomListView_Day_5_de.png | 4 +- screenshots/html/data.js | 1121 +++++++++-------- ...advanced_AdvancedSettingsView_Day_0_en.png | 4 +- ...advanced_AdvancedSettingsView_Day_1_en.png | 4 +- ...advanced_AdvancedSettingsView_Day_3_en.png | 4 +- ...vanced_AdvancedSettingsView_Night_0_en.png | 4 +- ...vanced_AdvancedSettingsView_Night_1_en.png | 4 +- ...vanced_AdvancedSettingsView_Night_3_en.png | 4 +- 21 files changed, 596 insertions(+), 590 deletions(-) diff --git a/features/preferences/impl/src/main/res/values-el/translations.xml b/features/preferences/impl/src/main/res/values-el/translations.xml index 0a94a6c3a6..32659bca9f 100644 --- a/features/preferences/impl/src/main/res/values-el/translations.xml +++ b/features/preferences/impl/src/main/res/values-el/translations.xml @@ -13,7 +13,7 @@ "Αποδεικτικά ανάγνωσης" "Εάν απενεργοποιηθεί, τα αποδεικτικά ανάγνωσης δεν θα στέλνονται σε κανέναν. Θα εξακολουθείς να λαμβάνεις αποδεικτικά ανάγνωσης από άλλους χρήστες." "Κοινή χρήση παρουσίας" - "Εάν απενεργοποιηθεί, δεν θα μπορείς να στέλνεις ή να λαμβάνεις αποδεικτικά ανάγνωσης ή ειδοποιήσεις πληκτρολόγησης" + "Εάν απενεργοποιηθεί, δεν θα μπορείς να στέλνεις ή να λαμβάνεις αποδεικτικά ανάγνωσης ή ειδοποιήσεις πληκτρολόγησης." "Ενεργοποίησε την επιλογή για προβολή πηγής μηνυμάτων στη ροή." "Άρση αποκλεισμού" "Θα μπορείς να δεις ξανά όλα τα μηνύματα του." diff --git a/features/preferences/impl/src/main/res/values-et/translations.xml b/features/preferences/impl/src/main/res/values-et/translations.xml index e7700b012a..d25928f42f 100644 --- a/features/preferences/impl/src/main/res/values-et/translations.xml +++ b/features/preferences/impl/src/main/res/values-et/translations.xml @@ -13,7 +13,7 @@ "Lugemisteatised" "Kui lülitad selle valiku välja, siis mitte keegi enam ei saa sinult lugemisteatisi. Küll aga saad sina teiste kasutajate lugemisteatisi." "Jaga oma olekut" - "Kui see eelistus on välja lülitatud, siis sa ei saa ega saada ei lugemisteatisi ega kirjutamise teavitusi" + "Kui see eelistus on välja lülitatud, siis sa ei saa ega saada ei lugemisteatisi ega kirjutamise teavitusi." "Selle eelistuse sisselülitamisel on võimalik ajajoonel vaadata sõnumite lähtekoodi." "Sa pole ühtegi kasutajat blokeerinud" "Eemalda blokeering" diff --git a/features/preferences/impl/src/main/res/values-fr/translations.xml b/features/preferences/impl/src/main/res/values-fr/translations.xml index ccdd333135..2c50c3c83c 100644 --- a/features/preferences/impl/src/main/res/values-fr/translations.xml +++ b/features/preferences/impl/src/main/res/values-fr/translations.xml @@ -13,7 +13,7 @@ "Accusés de lecture" "En cas de désactivation, vos accusés de lecture ne seront pas envoyés aux autres membres. Vous verrez toujours les accusés des autres membres." "Partager la présence" - "Si cette option est désactivée, vous ne pourrez ni envoyer ni recevoir de confirmations de lecture ni de notifications de saisie" + "Si cette option est désactivée, vous ne pourrez ni envoyer ni recevoir de confirmations de lecture ni de notifications de saisie." "Activer cette option pour pouvoir voir la source des messages dans la discussion." "Vous n’avez bloqué personne" "Débloquer" diff --git a/features/preferences/impl/src/main/res/values-pt/translations.xml b/features/preferences/impl/src/main/res/values-pt/translations.xml index db6df3941e..5502334026 100644 --- a/features/preferences/impl/src/main/res/values-pt/translations.xml +++ b/features/preferences/impl/src/main/res/values-pt/translations.xml @@ -13,7 +13,7 @@ "Recibos de leitura" "Se desativada, os teus recibos de leitura não serão enviados a ninguém. Continuas a receber recibos de leitura de outros utilizadores." "Partilhar presença" - "Se desativado, não poderás enviar ou receber recibos de leitura ou notificações de escrita" + "Se desativado, não poderás enviar ou receber recibos de leitura ou notificações de escrita." "Ativa a opção para ver a origem da mensagem na cronologia." "Não tens nenhum utilizador bloqueado" "Desbloquear" diff --git a/features/preferences/impl/src/main/res/values-sk/translations.xml b/features/preferences/impl/src/main/res/values-sk/translations.xml index 3d8eaa5d00..2e26f68322 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -13,7 +13,7 @@ "Potvrdenia o prečítaní" "Ak je táto funkcia vypnutá, vaše potvrdenia o prečítaní sa nebudú nikomu odosielať. Stále budete dostávať potvrdenia o prečítaní od ostatných používateľov." "Zdieľať prítomnosť" - "Ak je vypnuté, nebudete môcť odosielať ani prijímať potvrdenia o prečítaní alebo písať upozornenia" + "Ak je vypnuté, nebudete môcť odosielať ani prijímať potvrdenia o prečítaní alebo upozornenia o písaní" "Povoliť možnosť zobrazenia zdroja správy na časovej osi." "Nemáte žiadnych blokovaných používateľov" "Odblokovať" diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 67cbf87737..da71324b9e 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -13,7 +13,7 @@ "Read receipts" "If turned off, your read receipts won\'t be sent to anyone. You will still receive read receipts from other users." "Share presence" - "If turned off, you won’t be able to send or receive read receipts or typing notifications" + "If turned off, you won’t be able to send or receive read receipts or typing notifications." "Enable option to view message source in the timeline." "You have no blocked users" "Unblock" diff --git a/libraries/eventformatter/impl/src/main/res/values-el/translations.xml b/libraries/eventformatter/impl/src/main/res/values-el/translations.xml index 322842e825..71adc9ed56 100644 --- a/libraries/eventformatter/impl/src/main/res/values-el/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-el/translations.xml @@ -59,4 +59,5 @@ "Αφαίρεσες το θέμα του δωματίου" "Ο χρήστης %1$s έκανε άρση αποκλεισμού στον χρήστη %2$s" "Έκανες άρση αποκλεισμού στον χρήστη %1$s" + "Ο χρήστης %1$s έκανε μια άγνωστη αλλαγή στην ιδιότητα μέλους του." diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png index 0ffa2e4668..f2daa47e63 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c38243f29dc052b0378ce79c97167e34ed33a74830d1da54f60fa826ae7d889f -size 53358 +oid sha256:69b89fb9b9a6cac6d39f7c90e5c31500df005b878de60e2311eeb51d42daea90 +size 53339 diff --git a/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_de.png b/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_de.png index 367bde9317..fb79209144 100644 --- a/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_de.png +++ b/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c273e9ba141d8498367f686b8a3d94289ec82131d463dc2c6842a070ba0e7982 -size 19930 +oid sha256:4994eaf64ea1d1188cdf1a47aec52ee5d45c5827be42c648ac4a27c84f15c602 +size 20349 diff --git a/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_de.png b/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_de.png index e85a7a21bb..4c39ed1a4d 100644 --- a/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_de.png +++ b/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6f63aac984566fda0d99333f557ac52f2b0e5930a94d751994d5a26063321dc -size 21916 +oid sha256:7bad697ed796ca1c8030d3d73c96d1b68ac2db575ac01a50fb4f1220d6e574b8 +size 22349 diff --git a/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_de.png b/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_de.png index ce11cca43d..cf189738de 100644 --- a/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_de.png +++ b/screenshots/de/features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c993bea1ae13f6a0b27048c613ff6aee82ed9413a46d8a96dbbab13b5a517e4 -size 22263 +oid sha256:df2290b52cd16f3910b23a6175e161f20a286508540566f88091828b23d9fe79 +size 22694 diff --git a/screenshots/de/features.roomlist.impl_RoomListView_Day_3_de.png b/screenshots/de/features.roomlist.impl_RoomListView_Day_3_de.png index 6e0e3832c6..0891b89914 100644 --- a/screenshots/de/features.roomlist.impl_RoomListView_Day_3_de.png +++ b/screenshots/de/features.roomlist.impl_RoomListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8830605e63dd7b7aecedb179607cd6446e0103b14515c04a9041e910b9edf14b -size 23934 +oid sha256:6a438cf39d0132430a76ed852a4c3a4013c955c86a4a8cda387928d560f2c629 +size 24411 diff --git a/screenshots/de/features.roomlist.impl_RoomListView_Day_4_de.png b/screenshots/de/features.roomlist.impl_RoomListView_Day_4_de.png index b06e230a83..7e24689018 100644 --- a/screenshots/de/features.roomlist.impl_RoomListView_Day_4_de.png +++ b/screenshots/de/features.roomlist.impl_RoomListView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b57ecab8ece20162a1907dd875a6069b51b0d1a2897bf3fa3b1a551fa7247015 -size 23289 +oid sha256:f4f52e67c86ad0d6e2e0b7e2f6bc45460700c46ae86d4a4d7d31e06fe0cbcfdc +size 23754 diff --git a/screenshots/de/features.roomlist.impl_RoomListView_Day_5_de.png b/screenshots/de/features.roomlist.impl_RoomListView_Day_5_de.png index 0fecfbae53..7a9ab1374b 100644 --- a/screenshots/de/features.roomlist.impl_RoomListView_Day_5_de.png +++ b/screenshots/de/features.roomlist.impl_RoomListView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72538217e219f3c0b73974099ecc75564e8e8fc3f1ada52ce9cd8babb4c28da8 -size 21205 +oid sha256:6fc214b314c23cf5a2437ddcb7fddca5657a4a575e65b95a092f12bd40ef4fd5 +size 21666 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index e83f9e3272..d07d94b26d 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,39 +1,39 @@ // 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",19907,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",19909,], ["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",19907,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",19907,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",19907,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",19907,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",19909,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",19909,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",19909,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",19909,], ["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.login.impl.accountprovider_AccountProviderView_Day_4_en","features.login.impl.accountprovider_AccountProviderView_Night_4_en",0,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",19907,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",19907,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",19907,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",19907,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",19907,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",19907,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",19907,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",19907,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",19907,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",19907,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",19907,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",19907,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",19909,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",19909,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",19909,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",19909,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",19909,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",19909,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",19909,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",19909,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",19909,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",19909,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",19909,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",19909,], ["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",19907,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",19909,], ["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",19907,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",19909,], ["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",19907,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",19909,], ["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",19907,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",19909,], ["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,], @@ -43,11 +43,11 @@ 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","",19907,], -["features.messages.impl.attachments.preview_AttachmentsView_1_en","",19907,], -["features.messages.impl.attachments.preview_AttachmentsView_2_en","",19907,], -["features.messages.impl.attachments.preview_AttachmentsView_3_en","",19907,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",19907,], +["features.messages.impl.attachments.preview_AttachmentsView_0_en","",19909,], +["features.messages.impl.attachments.preview_AttachmentsView_1_en","",19909,], +["features.messages.impl.attachments.preview_AttachmentsView_2_en","",19909,], +["features.messages.impl.attachments.preview_AttachmentsView_3_en","",19909,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",19909,], ["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,], @@ -127,13 +127,13 @@ export const screenshots = [ ["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",19907,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",19907,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",19907,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",19907,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",19907,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",19907,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",19907,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",19909,], ["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,], @@ -144,81 +144,81 @@ 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",19907,], -["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",19907,], -["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",19907,], -["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",19907,], -["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",19907,], +["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",19909,], +["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",19909,], +["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",19909,], +["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",19909,], +["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",19909,], ["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.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",19907,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",19907,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",19907,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",19907,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",19909,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",19909,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",19909,], ["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",19907,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",19907,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",19909,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",19909,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",19907,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",19909,], ["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.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_ConfigureRoomView_Day_0_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_0_en",19907,], -["features.createroom.impl.configureroom_ConfigureRoomView_Day_1_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_1_en",19907,], +["features.createroom.impl.configureroom_ConfigureRoomView_Day_0_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_0_en",19909,], +["features.createroom.impl.configureroom_ConfigureRoomView_Day_1_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_1_en",19909,], ["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",19907,], -["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",19907,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",19909,], +["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",19909,], ["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",19907,], -["features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Day_0_en","features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Night_0_en",19907,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",19907,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",19907,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",19907,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",19907,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",19907,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",19907,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",19907,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",19907,], -["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",19907,], -["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",19907,], -["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",19907,], -["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",19907,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime pickers_en","",19907,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime pickers_en","",19907,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",19909,], +["features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Day_0_en","features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Night_0_en",19909,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",19909,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",19909,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",19909,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",19909,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",19909,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",19909,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",19909,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",19909,], +["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",19909,], +["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",19909,], +["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",19909,], +["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",19909,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime pickers_en","",19909,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime pickers_en","",19909,], ["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",19907,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",19907,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",19907,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",19909,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",19909,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",19909,], ["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",19907,], -["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",19907,], -["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",19907,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",19907,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",19907,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",19907,], -["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",19907,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",19909,], +["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",19909,], +["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",19909,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",19909,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",19909,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",19909,], +["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",19909,], ["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,], @@ -230,12 +230,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",19907,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",19907,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",19907,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",19907,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",19907,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",19907,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",19909,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",19909,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",19909,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",19909,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",19909,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",19909,], ["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,], @@ -245,10 +245,10 @@ 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,], -["features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Day_0_en","features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Night_0_en",19907,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",19907,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",19907,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",19907,], +["features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Day_0_en","features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Night_0_en",19909,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",19909,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",19909,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",19909,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], ["libraries.designsystem.theme.components_FilledButtonLarge_Buttons_en","",0,], @@ -257,15 +257,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",19907,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",19907,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",19907,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",19909,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",19909,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",19909,], ["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",19907,], -["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",19907,], +["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",19909,], +["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",19909,], ["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,], @@ -278,6 +278,8 @@ export const screenshots = [ ["libraries.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_Day_0_en","libraries.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_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.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,], @@ -290,36 +292,36 @@ export const screenshots = [ ["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",19907,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",19909,], ["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",19907,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",19909,], ["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",19907,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",19909,], ["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",19907,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",19909,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",19907,], -["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",19907,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",19909,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], ["libraries.designsystem.components_LabelledOutlinedTextField_Day_0_en","libraries.designsystem.components_LabelledOutlinedTextField_Night_0_en",0,], ["libraries.designsystem.components_LabelledTextField_Day_0_en","libraries.designsystem.components_LabelledTextField_Night_0_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",19907,], -["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",19907,], -["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",19907,], -["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",19907,], -["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",19907,], -["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",19907,], +["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",19909,], +["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",19909,], +["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",19909,], +["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",19909,], +["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",19909,], +["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",19909,], ["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,], @@ -370,28 +372,28 @@ 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",19907,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",19907,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",19907,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",19907,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",19909,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",19909,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",19909,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",19909,], ["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",19907,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",19907,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",19907,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",19907,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",19907,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",19907,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",19907,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",19907,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",19907,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",19907,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",19907,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",19907,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",19907,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",19907,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",19907,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",19909,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",19909,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",19909,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",19909,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",19909,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",19909,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",19909,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",19909,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",19909,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",19909,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",19909,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",19909,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",19909,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",19909,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",19909,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",19907,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",19909,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], ["libraries.matrix.ui.components_MatrixUserHeaderPlaceholder_Day_0_en","libraries.matrix.ui.components_MatrixUserHeaderPlaceholder_Night_0_en",0,], ["libraries.matrix.ui.components_MatrixUserHeader_Day_0_en","libraries.matrix.ui.components_MatrixUserHeader_Night_0_en",0,], @@ -401,7 +403,7 @@ export const screenshots = [ ["libraries.mediaviewer.api.viewer_MediaViewerView_0_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_10_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_2_en","",19907,], +["libraries.mediaviewer.api.viewer_MediaViewerView_2_en","",19909,], ["libraries.mediaviewer.api.viewer_MediaViewerView_3_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_4_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_5_en","",0,], @@ -411,10 +413,10 @@ export const screenshots = [ ["libraries.mediaviewer.api.viewer_MediaViewerView_9_en","",0,], ["libraries.designsystem.theme.components_MediumTopAppBar_App Bars_en","",0,], ["libraries.textcomposer.mentions_MentionSpan_Day_0_en","libraries.textcomposer.mentions_MentionSpan_Night_0_en",0,], -["features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en","features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en",19907,], +["features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en","features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en",19909,], ["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",19907,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",19909,], ["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,], @@ -438,54 +440,55 @@ 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.typing_MessagesViewWithTyping_Day_0_en","features.messages.impl.typing_MessagesViewWithTyping_Night_0_en",19907,], -["features.messages.impl.typing_MessagesViewWithTyping_Day_1_en","features.messages.impl.typing_MessagesViewWithTyping_Night_1_en",19907,], +["features.messages.impl.typing_MessagesViewWithTyping_Day_0_en","features.messages.impl.typing_MessagesViewWithTyping_Night_0_en",19909,], +["features.messages.impl.typing_MessagesViewWithTyping_Day_1_en","features.messages.impl.typing_MessagesViewWithTyping_Night_1_en",19909,], ["features.messages.impl.typing_MessagesViewWithTyping_Day_2_en","features.messages.impl.typing_MessagesViewWithTyping_Night_2_en",0,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",19907,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",19907,], -["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",19907,], -["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",19907,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",19909,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",19909,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",19909,], +["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",19909,], ["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",0,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",19907,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",19907,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",19907,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",19907,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",19907,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",19907,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",19907,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",19907,], -["features.roomlist.impl.migration_MigrationScreenView_Day_0_en","features.roomlist.impl.migration_MigrationScreenView_Night_0_en",19907,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",19909,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",19909,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",19909,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",19909,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",19909,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",19909,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",19909,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",19909,], +["features.roomlist.impl.migration_MigrationScreenView_Day_0_en","features.roomlist.impl.migration_MigrationScreenView_Night_0_en",19909,], ["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",19907,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",19909,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom Sheets_en","",0,], +["appicon.element_MonochromeIcon_en","",0,], ["libraries.designsystem.components.dialogs_MultipleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_MultipleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_MultipleSelectionDialog_Night_0_en",0,], ["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.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",19907,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",19907,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",19907,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",19909,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",19909,], ["features.login.impl.oidc.webview_OidcView_Day_0_en","features.login.impl.oidc.webview_OidcView_Night_0_en",0,], ["features.login.impl.oidc.webview_OidcView_Day_1_en","features.login.impl.oidc.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",19907,], -["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",19907,], -["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",19907,], -["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",19907,], -["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",19907,], +["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",19909,], +["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",19909,], +["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",19909,], +["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",19909,], +["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",19909,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonMedium_Buttons_en","",0,], @@ -498,47 +501,47 @@ export const screenshots = [ ["libraries.designsystem.components_PageTitleWithIconFull_Day_3_en","libraries.designsystem.components_PageTitleWithIconFull_Night_3_en",0,], ["libraries.designsystem.components_PageTitleWithIconFull_Day_4_en","libraries.designsystem.components_PageTitleWithIconFull_Night_4_en",0,], ["libraries.designsystem.components_PageTitleWithIconMinimal_Day_0_en","libraries.designsystem.components_PageTitleWithIconMinimal_Night_0_en",0,], -["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",19907,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",19907,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",19907,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",19907,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",19907,], +["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",19909,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",19909,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",19909,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",19909,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",19909,], ["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",19907,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",19907,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",19907,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",19909,], ["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",19907,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",19907,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",19907,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",19907,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",19907,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",19909,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",19909,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",19909,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",19909,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",19909,], ["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",19907,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",19907,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",19907,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",19907,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",19907,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",19907,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",19907,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",19907,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",19907,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",19907,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",19907,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",19909,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",19909,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",19909,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",19909,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",19909,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",19909,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",19909,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",19909,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",19909,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",19909,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",19909,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceDivider_Preferences_en","",0,], @@ -554,181 +557,181 @@ 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","",19907,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",19907,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",19907,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",19907,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",19909,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",19909,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",19909,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",19909,], ["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","",19907,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",19907,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",19907,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",19907,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",19907,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",19907,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",19907,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",19907,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",19907,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",19907,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",19907,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",19907,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",19909,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",19909,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",19909,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",19909,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",19909,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",19909,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",19909,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",19909,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",19909,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",19909,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",19909,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",19909,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",19907,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",19907,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",19909,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",19909,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",19907,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",19907,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",19907,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",19907,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",19907,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",19907,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",19907,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",19909,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",19909,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",19909,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",19909,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",19909,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",19909,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",19909,], ["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",19907,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",19907,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",19907,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",19907,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",19907,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",19907,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",19907,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",19907,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",19907,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",19909,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",19909,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",19909,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",19909,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",19909,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",19909,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",19909,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",19909,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",19909,], ["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",19907,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",19909,], ["features.roomdetails.impl.components_RoomBadgeNegative_Day_0_en","features.roomdetails.impl.components_RoomBadgeNegative_Night_0_en",0,], ["features.roomdetails.impl.components_RoomBadgeNeutral_Day_0_en","features.roomdetails.impl.components_RoomBadgeNeutral_Night_0_en",0,], ["features.roomdetails.impl.components_RoomBadgePositive_Day_0_en","features.roomdetails.impl.components_RoomBadgePositive_Night_0_en",0,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",19907,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",19907,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",19907,], -["features.roomdetails.impl_RoomDetails_0_en","",19907,], -["features.roomdetails.impl_RoomDetails_10_en","",19907,], -["features.roomdetails.impl_RoomDetails_11_en","",19907,], -["features.roomdetails.impl_RoomDetails_12_en","",19907,], -["features.roomdetails.impl_RoomDetails_1_en","",19907,], -["features.roomdetails.impl_RoomDetails_2_en","",19907,], -["features.roomdetails.impl_RoomDetails_3_en","",19907,], -["features.roomdetails.impl_RoomDetails_4_en","",19907,], -["features.roomdetails.impl_RoomDetails_5_en","",19907,], -["features.roomdetails.impl_RoomDetails_6_en","",19907,], -["features.roomdetails.impl_RoomDetails_7_en","",19907,], -["features.roomdetails.impl_RoomDetails_8_en","",19907,], -["features.roomdetails.impl_RoomDetails_9_en","",19907,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",19907,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",19907,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",19907,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",19907,], -["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",19907,], -["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",19907,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",19909,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",19909,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",19909,], +["features.roomdetails.impl_RoomDetails_0_en","",19909,], +["features.roomdetails.impl_RoomDetails_10_en","",19909,], +["features.roomdetails.impl_RoomDetails_11_en","",19909,], +["features.roomdetails.impl_RoomDetails_12_en","",19909,], +["features.roomdetails.impl_RoomDetails_1_en","",19909,], +["features.roomdetails.impl_RoomDetails_2_en","",19909,], +["features.roomdetails.impl_RoomDetails_3_en","",19909,], +["features.roomdetails.impl_RoomDetails_4_en","",19909,], +["features.roomdetails.impl_RoomDetails_5_en","",19909,], +["features.roomdetails.impl_RoomDetails_6_en","",19909,], +["features.roomdetails.impl_RoomDetails_7_en","",19909,], +["features.roomdetails.impl_RoomDetails_8_en","",19909,], +["features.roomdetails.impl_RoomDetails_9_en","",19909,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",19909,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",19909,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",19909,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",19909,], +["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",19909,], +["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",19909,], ["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",19907,], -["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",19907,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",19907,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",19907,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",19907,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",19907,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",19907,], +["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",19909,], +["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",19909,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",19909,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",19909,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",19909,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",19909,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",19909,], ["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",19907,], -["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",19907,], -["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",19907,], +["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",19909,], +["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",19909,], +["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",19909,], ["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",0,], -["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",19907,], -["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",19907,], -["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",19907,], -["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",19907,], -["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",19907,], -["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",19907,], -["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",19907,], +["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",19909,], +["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",19909,], +["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",19909,], +["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",19909,], +["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",19909,], +["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",19909,], +["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",19909,], ["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",19907,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",19907,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",19907,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",19907,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",19907,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",19907,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",19907,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",19907,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",19907,], +["features.roomlist.impl_RoomListView_Day_9_en","features.roomlist.impl_RoomListView_Night_9_en",19909,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",19909,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",19909,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",19909,], ["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",19907,], -["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",19907,], +["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",19909,], ["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",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",19907,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",19907,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",19909,], ["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",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",19907,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",19907,], -["features.createroom.impl.components_RoomPrivacyOption_Day_0_en","features.createroom.impl.components_RoomPrivacyOption_Night_0_en",19907,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",19907,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",19907,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",19907,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",19907,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",19907,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",19907,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",19909,], +["features.createroom.impl.components_RoomPrivacyOption_Day_0_en","features.createroom.impl.components_RoomPrivacyOption_Night_0_en",19909,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",19909,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",19909,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",19909,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",19909,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",19909,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",19909,], ["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,], @@ -751,10 +754,10 @@ 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",19907,], -["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",19907,], -["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",19907,], -["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",19907,], +["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",19909,], +["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",19909,], +["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",19909,], +["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",19909,], ["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,], @@ -762,62 +765,64 @@ 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",19907,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",19907,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",19907,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",19909,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",19909,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",19909,], +["appicon.enterprise_RoundIcon_en","",0,], +["appicon.element_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",19907,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",19907,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",19907,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",19909,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",19909,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",19909,], ["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","",19907,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search views_en","",19909,], ["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","",19907,], -["features.createroom.impl.components_SearchSingleUserResultItem_en","",19907,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",19907,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",19907,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",19907,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",19907,], -["features.securebackup.impl.enable_SecureBackupEnableView_Day_0_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_0_en",19907,], -["features.securebackup.impl.enable_SecureBackupEnableView_Day_1_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_1_en",19907,], -["features.securebackup.impl.enable_SecureBackupEnableView_Day_2_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_2_en",19907,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",19907,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",19907,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",19907,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",19907,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",19907,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",19907,], +["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",19909,], +["features.createroom.impl.components_SearchSingleUserResultItem_en","",19909,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",19909,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",19909,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",19909,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",19909,], +["features.securebackup.impl.enable_SecureBackupEnableView_Day_0_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_0_en",19909,], +["features.securebackup.impl.enable_SecureBackupEnableView_Day_1_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_1_en",19909,], +["features.securebackup.impl.enable_SecureBackupEnableView_Day_2_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_2_en",19909,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",19909,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",19909,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",19909,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",19909,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",19909,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",19909,], ["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_SelectedUserCannotRemove_Day_0_en","libraries.matrix.ui.components_SelectedUserCannotRemove_Night_0_en",0,], ["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",19907,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",19907,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",19907,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",19907,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",19907,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",19909,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",19909,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",19909,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",19909,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",19909,], ["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,], @@ -827,37 +832,37 @@ 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.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",19907,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",19907,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",19907,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",19907,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",19907,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",19907,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",19909,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",19909,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",19909,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",19909,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",19909,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",19909,], ["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",19907,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",19909,], ["features.messages.impl.actionlist_SheetContent_Day_0_en","features.messages.impl.actionlist_SheetContent_Night_0_en",0,], ["features.messages.impl.timeline.components.reactionsummary_SheetContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_SheetContent_Night_0_en",0,], -["features.messages.impl.actionlist_SheetContent_Day_10_en","features.messages.impl.actionlist_SheetContent_Night_10_en",19907,], +["features.messages.impl.actionlist_SheetContent_Day_10_en","features.messages.impl.actionlist_SheetContent_Night_10_en",19909,], ["features.messages.impl.actionlist_SheetContent_Day_1_en","features.messages.impl.actionlist_SheetContent_Night_1_en",0,], -["features.messages.impl.actionlist_SheetContent_Day_2_en","features.messages.impl.actionlist_SheetContent_Night_2_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_3_en","features.messages.impl.actionlist_SheetContent_Night_3_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_4_en","features.messages.impl.actionlist_SheetContent_Night_4_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_5_en","features.messages.impl.actionlist_SheetContent_Night_5_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_6_en","features.messages.impl.actionlist_SheetContent_Night_6_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_7_en","features.messages.impl.actionlist_SheetContent_Night_7_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_8_en","features.messages.impl.actionlist_SheetContent_Night_8_en",19907,], -["features.messages.impl.actionlist_SheetContent_Day_9_en","features.messages.impl.actionlist_SheetContent_Night_9_en",19907,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",19907,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",19907,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",19907,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",19907,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",19907,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",19907,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",19907,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",19907,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",19907,], +["features.messages.impl.actionlist_SheetContent_Day_2_en","features.messages.impl.actionlist_SheetContent_Night_2_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_3_en","features.messages.impl.actionlist_SheetContent_Night_3_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_4_en","features.messages.impl.actionlist_SheetContent_Night_4_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_5_en","features.messages.impl.actionlist_SheetContent_Night_5_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_6_en","features.messages.impl.actionlist_SheetContent_Night_6_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_7_en","features.messages.impl.actionlist_SheetContent_Night_7_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_8_en","features.messages.impl.actionlist_SheetContent_Night_8_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_9_en","features.messages.impl.actionlist_SheetContent_Night_9_en",19909,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",19909,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",19909,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",19909,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",19909,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",19909,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",19909,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",19909,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",19909,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",19909,], ["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,], @@ -866,7 +871,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",19907,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",19909,], ["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,], @@ -876,34 +881,34 @@ 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",19907,], +["features.location.api.internal_StaticMapPlaceholder_Day_1_en","features.location.api.internal_StaticMapPlaceholder_Night_1_en",19909,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], ["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",19907,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",19909,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",19907,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",19907,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",19907,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",19907,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",19907,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",19907,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",19907,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",19909,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",19909,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",19909,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",19909,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",19909,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",19909,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",19909,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], ["libraries.designsystem.theme.components_TextFieldDark_TextFields_en","",0,], @@ -915,38 +920,38 @@ export const screenshots = [ ["libraries.designsystem.theme.components_TextFieldValueTextFieldDark_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","",19907,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime pickers_en","",19907,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime pickers_en","",19907,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime pickers_en","",19909,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime pickers_en","",19909,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime pickers_en","",19909,], ["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",19907,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",19907,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",19909,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",19909,], ["features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_1_en",0,], ["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_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",19907,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",19909,], ["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",19907,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",19907,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",19907,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",19909,], ["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,], ["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",19907,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",19907,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",19907,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",19909,], ["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",19907,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",19907,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",19909,], ["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,], @@ -955,36 +960,36 @@ 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",19907,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",19909,], ["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",19907,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",19909,], ["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","",19907,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",19909,], ["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_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",19907,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",19907,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",19909,], ["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_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",19907,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",19909,], ["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",19907,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",19907,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",19907,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",19907,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",19907,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",19909,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",19909,], ["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",19907,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",19907,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",19909,], ["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",19907,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",19909,], ["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,], @@ -993,8 +998,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",19907,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",19907,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",19909,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",19909,], ["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,], @@ -1006,7 +1011,7 @@ 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",19907,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",19909,], ["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,], @@ -1028,79 +1033,79 @@ 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_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",19907,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",19909,], ["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",19907,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",19907,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",19907,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",19907,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",19907,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",19907,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",19907,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",19909,], ["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",19907,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",19909,], ["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",19907,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",19909,], ["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",19907,], +["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",19909,], ["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",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",19907,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",19907,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",19909,], ["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",19907,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",19907,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",19907,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",19907,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",19907,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",19907,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",19909,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",19909,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",19909,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",19909,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",19909,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",19909,], ["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","",19907,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",19909,], ["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",19907,], -["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",19907,], -["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",19907,], -["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",19907,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",19909,], +["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",19909,], +["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",19909,], +["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",19909,], ["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",19907,], +["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",19909,], ["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",19907,], +["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",19909,], ["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",0,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",19907,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",19907,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",19907,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",19907,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",19907,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",19907,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",19907,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",19907,], -["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_0_en","features.verifysession.impl_VerifySelfSessionView_Night_0_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_1_en","features.verifysession.impl_VerifySelfSessionView_Night_1_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_2_en","features.verifysession.impl_VerifySelfSessionView_Night_2_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_3_en","features.verifysession.impl_VerifySelfSessionView_Night_3_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_4_en","features.verifysession.impl_VerifySelfSessionView_Night_4_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_5_en","features.verifysession.impl_VerifySelfSessionView_Night_5_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_6_en","features.verifysession.impl_VerifySelfSessionView_Night_6_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_7_en","features.verifysession.impl_VerifySelfSessionView_Night_7_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_8_en","features.verifysession.impl_VerifySelfSessionView_Night_8_en",19907,], -["features.verifysession.impl_VerifySelfSessionView_Day_9_en","features.verifysession.impl_VerifySelfSessionView_Night_9_en",19907,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",19909,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",19909,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",19909,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",19909,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",19909,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",19909,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",19909,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",19909,], +["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_0_en","features.verifysession.impl_VerifySelfSessionView_Night_0_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_1_en","features.verifysession.impl_VerifySelfSessionView_Night_1_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_2_en","features.verifysession.impl_VerifySelfSessionView_Night_2_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_3_en","features.verifysession.impl_VerifySelfSessionView_Night_3_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_4_en","features.verifysession.impl_VerifySelfSessionView_Night_4_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_5_en","features.verifysession.impl_VerifySelfSessionView_Night_5_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_6_en","features.verifysession.impl_VerifySelfSessionView_Night_6_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_7_en","features.verifysession.impl_VerifySelfSessionView_Night_7_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_8_en","features.verifysession.impl_VerifySelfSessionView_Night_8_en",19909,], +["features.verifysession.impl_VerifySelfSessionView_Day_9_en","features.verifysession.impl_VerifySelfSessionView_Night_9_en",19909,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], @@ -1114,12 +1119,12 @@ export const screenshots = [ ["libraries.textcomposer.components_VoiceMessageRecorderButton_Day_0_en","libraries.textcomposer.components_VoiceMessageRecorderButton_Night_0_en",0,], ["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,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_0_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_0_en",19907,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_1_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_1_en",19907,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_2_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_2_en",19907,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_3_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_3_en",19907,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_4_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_4_en",19907,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_0_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_0_en",19909,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_1_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_1_en",19909,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_2_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_2_en",19909,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_3_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_3_en",19909,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_4_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_4_en",19909,], ["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",19907,], +["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",19909,], ["libraries.designsystem.ruler_WithRulers_Day_0_en","libraries.designsystem.ruler_WithRulers_Night_0_en",0,], ]; diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png index 9d295823f1..10c7213abf 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c77815712f1177572f49f9c4cdcb279f6892d583c38ba8a28cd68d6cc61c242a -size 41999 +oid sha256:2d4b39aa22007f8e10f5b64ba96b335223bdb8b9e0ef1d60bb1a2ff3190290f3 +size 42023 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png index 620243eb03..c9564c8a50 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50035b3ffe1134e580a42caaf843e96352bb158fc6c473cfa911c03bfd4469e4 -size 41411 +oid sha256:6bb51fa5e738393685af840444d5b3aca3413b1f8dca5020fd9f3cce7f38219e +size 41435 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png index 88861798d1..17fa444b6e 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecfc8adb3512046af69e945d6e9f736f31c6ab7f5a7563c954b4e4624b25901f -size 41438 +oid sha256:cf4034873a1ea243d39767f7d0b6d24d365dc9a260cffaa0e20d43cdc8d43411 +size 41463 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png index cf16a8e6a9..da0f016060 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d57e421dedb038846f170aafd207b2984b248d1c29bebb194a49bf9ff89aac95 -size 40691 +oid sha256:46ddcc5be668477289909de2667759a3ddf1698790f8f610eecbd94eaf14f81e +size 40714 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png index 21e6ff2f2f..6e5354f4f9 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c71b98e7d0de4b2a289eaf1838136427214d4d18a159f7ca2c67c36fa8a87675 -size 40247 +oid sha256:839c064ea5d6c37e6dc49c1e2970a4dd358d45dbbd7887467d802547032aca1f +size 40272 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png index bfdc237bf1..294c96fe73 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25bf047a72199a5cf471fc12fa8acf309e597b5a47ee8edcc1d3e15ac3f67cbc -size 40365 +oid sha256:3a3bf2dba2a903b36cc2e6e2f0fc6a3830e1d864cc779a20a3faaec1ee71b500 +size 40392 From a005c80fe439dba1be40bbd778b7a82b9b1e145c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Jul 2024 17:49:15 +0200 Subject: [PATCH 019/115] Add support for Picture in Picture for ElementCallActivity --- .../call/impl/src/main/AndroidManifest.xml | 3 +- .../call/impl/pip/PictureInPictureEvents.kt | 21 ++++ .../impl/pip/PictureInPicturePresenter.kt | 105 +++++++++++++++++ .../call/impl/pip/PictureInPictureState.kt | 23 ++++ .../impl/pip/PictureInPictureStateProvider.kt | 29 +++++ .../call/impl/pip/PipSupportProvider.kt | 42 +++++++ .../features/call/impl/ui/CallScreenView.kt | 35 ++++-- .../call/impl/ui/ElementCallActivity.kt | 17 +++ .../call/impl/pip/FakePipSupportProvider.kt | 23 ++++ .../impl/pip/PictureInPicturePresenterTest.kt | 108 ++++++++++++++++++ 10 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt create mode 100644 features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt create mode 100644 features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt create mode 100644 features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt create mode 100644 features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt create mode 100644 features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt create mode 100644 features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt diff --git a/features/call/impl/src/main/AndroidManifest.xml b/features/call/impl/src/main/AndroidManifest.xml index 4edfb73362..bdd88cf47a 100644 --- a/features/call/impl/src/main/AndroidManifest.xml +++ b/features/call/impl/src/main/AndroidManifest.xml @@ -38,10 +38,11 @@ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt new file mode 100644 index 0000000000..da3c08da32 --- /dev/null +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +sealed interface PictureInPictureEvents { + data object EnterPictureInPicture : PictureInPictureEvents +} diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt new file mode 100644 index 0000000000..2c974382d0 --- /dev/null +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +import android.app.Activity +import android.app.PictureInPictureParams +import android.os.Build +import android.util.Rational +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.log.logger.LoggerTag +import timber.log.Timber +import java.lang.ref.WeakReference +import javax.inject.Inject + +private val loggerTag = LoggerTag("PiP") + +class PictureInPicturePresenter @Inject constructor( + pipSupportProvider: PipSupportProvider, +) : Presenter { + private val isPipSupported = pipSupportProvider.isPipSupported() + private var isInPictureInPicture = mutableStateOf(false) + private var hostActivity: WeakReference? = null + + @Composable + override fun present(): PictureInPictureState { + fun handleEvent(event: PictureInPictureEvents) { + when (event) { + PictureInPictureEvents.EnterPictureInPicture -> switchToPip() + } + } + + return PictureInPictureState( + supportPip = isPipSupported, + isInPictureInPicture = isInPictureInPicture.value, + eventSink = ::handleEvent, + ) + } + + fun onCreate(activity: Activity) { + if (isPipSupported) { + Timber.tag(loggerTag.value).d("onCreate: Setting PiP params") + hostActivity = WeakReference(activity) + hostActivity?.get()?.setPictureInPictureParams(getPictureInPictureParams()) + } else { + Timber.tag(loggerTag.value).d("onCreate: PiP is not supported") + } + } + + fun onDestroy() { + Timber.tag(loggerTag.value).d("onDestroy") + hostActivity?.clear() + hostActivity = null + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun getPictureInPictureParams(): PictureInPictureParams { + return PictureInPictureParams.Builder() + // Portrait for calls seems more appropriate + .setAspectRatio(Rational(3, 5)) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + setAutoEnterEnabled(true) + } + } + .build() + } + + /** + * Enters Picture-in-Picture mode. + */ + private fun switchToPip() { + if (isPipSupported) { + Timber.tag(loggerTag.value).d("Switch to PiP mode") + hostActivity?.get()?.enterPictureInPictureMode(getPictureInPictureParams()) + ?.also { Timber.tag(loggerTag.value).d("Switch to PiP mode result: $it") } + } + } + + fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { + Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: $isInPictureInPictureMode") + isInPictureInPicture.value = isInPictureInPictureMode + } + + fun onUserLeaveHint() { + Timber.tag(loggerTag.value).d("onUserLeaveHint") + switchToPip() + } +} diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt new file mode 100644 index 0000000000..e6b86c82f0 --- /dev/null +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +data class PictureInPictureState( + val supportPip: Boolean, + val isInPictureInPicture: Boolean, + val eventSink: (PictureInPictureEvents) -> Unit, +) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt new file mode 100644 index 0000000000..360ee54d3f --- /dev/null +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +fun aPictureInPictureState( + supportPip: Boolean = false, + isInPictureInPicture: Boolean = false, + eventSink: (PictureInPictureEvents) -> Unit = {}, +): PictureInPictureState { + return PictureInPictureState( + supportPip = supportPip, + isInPictureInPicture = isInPictureInPicture, + eventSink = eventSink, + ) +} diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt new file mode 100644 index 0000000000..16dcb8d66c --- /dev/null +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.annotation.ChecksSdkIntAtLeast +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import javax.inject.Inject + +interface PipSupportProvider { + @ChecksSdkIntAtLeast(Build.VERSION_CODES.O) + fun isPipSupported(): Boolean +} + +@ContributesBinding(AppScope::class) +class DefaultPipSupportProvider @Inject constructor( + @ApplicationContext private val context: Context, +) : PipSupportProvider { + override fun isPipSupported(): Boolean { + val hasSystemFeaturePip = context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse() + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasSystemFeaturePip + } +} diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt index 23d0a4769e..c8d202f8cc 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt @@ -36,6 +36,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.viewinterop.AndroidView import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.call.impl.R +import io.element.android.features.call.impl.pip.PictureInPictureEvents +import io.element.android.features.call.impl.pip.PictureInPictureState +import io.element.android.features.call.impl.pip.aPictureInPictureState import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.ProgressDialog @@ -58,25 +61,36 @@ interface CallScreenNavigator { @Composable internal fun CallScreenView( state: CallScreenState, + pipState: PictureInPictureState, requestPermissions: (Array, RequestPermissionCallback) -> Unit, modifier: Modifier = Modifier, ) { + fun handleBack() { + if (pipState.supportPip) { + pipState.eventSink.invoke(PictureInPictureEvents.EnterPictureInPicture) + } else { + state.eventSink(CallScreenEvents.Hangup) + } + } + Scaffold( modifier = modifier, topBar = { - TopAppBar( - title = { Text(stringResource(R.string.element_call)) }, - navigationIcon = { - BackButton( - imageVector = CompoundIcons.Close(), - onClick = { state.eventSink(CallScreenEvents.Hangup) } - ) - } - ) + if (!pipState.isInPictureInPicture) { + TopAppBar( + title = { Text(stringResource(R.string.element_call)) }, + navigationIcon = { + BackButton( + imageVector = CompoundIcons.Close(), + onClick = ::handleBack, + ) + } + ) + } } ) { padding -> BackHandler { - state.eventSink(CallScreenEvents.Hangup) + handleBack() } CallWebView( modifier = Modifier @@ -177,6 +191,7 @@ internal fun CallScreenViewPreview( ) = ElementPreview { CallScreenView( state = state, + pipState = aPictureInPictureState(), requestPermissions = { _, _ -> }, ) } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index 1f4313864d..c770deff60 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -42,6 +42,7 @@ import io.element.android.compound.theme.mapToTheme import io.element.android.features.call.api.CallType import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings +import io.element.android.features.call.impl.pip.PictureInPicturePresenter import io.element.android.features.call.impl.services.CallForegroundService import io.element.android.features.call.impl.utils.CallIntentDataParser import io.element.android.libraries.architecture.bindings @@ -52,6 +53,7 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { @Inject lateinit var callIntentDataParser: CallIntentDataParser @Inject lateinit var presenterFactory: CallScreenPresenter.Factory @Inject lateinit var appPreferencesStore: AppPreferencesStore + @Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter private lateinit var presenter: CallScreenPresenter @@ -86,6 +88,8 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { updateUiMode(resources.configuration) } + pictureInPicturePresenter.onCreate(this) + audioManager = getSystemService(AUDIO_SERVICE) as AudioManager requestAudioFocus() @@ -95,11 +99,13 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { } .collectAsState(initial = Theme.System) val state = presenter.present() + val pipState = pictureInPicturePresenter.present() ElementTheme( darkTheme = theme.isDark() ) { CallScreenView( state = state, + pipState = pipState, requestPermissions = { permissions, callback -> requestPermissionCallback = callback requestPermissionsLauncher.launch(permissions) @@ -114,6 +120,11 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { updateUiMode(newConfig) } + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + pictureInPicturePresenter.onPictureInPictureModeChanged(isInPictureInPictureMode) + } + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setCallType(intent) @@ -131,10 +142,16 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { } } + override fun onUserLeaveHint() { + super.onUserLeaveHint() + pictureInPicturePresenter.onUserLeaveHint() + } + override fun onDestroy() { super.onDestroy() releaseAudioFocus() CallForegroundService.stop(this) + pictureInPicturePresenter.onDestroy() } override fun finish() { diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt new file mode 100644 index 0000000000..5a4dc98275 --- /dev/null +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +class FakePipSupportProvider( + private val isPipSupported: Boolean +) : PipSupportProvider { + override fun isPipSupported() = isPipSupported +} diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt new file mode 100644 index 0000000000..b1d9df68dd --- /dev/null +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.pip + +import android.os.Build.VERSION_CODES +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.call.impl.ui.ElementCallActivity +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +class PictureInPicturePresenterTest { + @Test + @Config(sdk = [VERSION_CODES.N, VERSION_CODES.O, VERSION_CODES.S]) + fun `when pip is not supported, the state value supportPip is false`() = runTest { + val presenter = createPictureInPicturePresenter(supportPip = false) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.supportPip).isFalse() + } + presenter.onDestroy() + } + + @Test + @Config(sdk = [VERSION_CODES.O, VERSION_CODES.S]) + fun `when pip is supported, the state value supportPip is true`() = runTest { + val presenter = createPictureInPicturePresenter(supportPip = true) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.supportPip).isTrue() + } + presenter.onDestroy() + } + + @Test + @Config(sdk = [VERSION_CODES.S]) + fun `when entering pip is supported, the state value isInPictureInPicture is true`() = runTest { + val presenter = createPictureInPicturePresenter(supportPip = true) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isInPictureInPicture).isFalse() + initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture) + presenter.onPictureInPictureModeChanged(true) + val pipState = awaitItem() + assertThat(pipState.isInPictureInPicture).isTrue() + // User stops pip + presenter.onPictureInPictureModeChanged(false) + val finalState = awaitItem() + assertThat(finalState.isInPictureInPicture).isFalse() + } + presenter.onDestroy() + } + + @Test + @Config(sdk = [VERSION_CODES.S]) + fun `when onUserLeaveHint is called, the state value isInPictureInPicture becomes true`() = runTest { + val presenter = createPictureInPicturePresenter(supportPip = true) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isInPictureInPicture).isFalse() + presenter.onUserLeaveHint() + presenter.onPictureInPictureModeChanged(true) + val pipState = awaitItem() + assertThat(pipState.isInPictureInPicture).isTrue() + } + presenter.onDestroy() + } + + private fun createPictureInPicturePresenter( + supportPip: Boolean = true, + ): PictureInPicturePresenter { + val activity = Robolectric.buildActivity(ElementCallActivity::class.java) + return PictureInPicturePresenter( + pipSupportProvider = FakePipSupportProvider(supportPip), + ).apply { + onCreate(activity.get()) + } + } +} From 664f78741d53d33066ee5862f9f4a02a3ccf1fca Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 12:08:42 +0200 Subject: [PATCH 020/115] Add preview for loading state. --- .../android/features/call/impl/ui/CallScreenStateProvider.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt index be6622d8ee..75cd962c9a 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt @@ -23,6 +23,7 @@ open class CallScreenStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aCallScreenState(), + aCallScreenState(urlState = AsyncData.Loading()), aCallScreenState(urlState = AsyncData.Failure(Exception("An error occurred"))), ) } From 966219b791120ee1b8845d0d930a3c8a224c6326 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 12:14:03 +0200 Subject: [PATCH 021/115] Rename method (copy paste error) --- .../impl/blockedusers/BlockedUserViewTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt index 353d505e50..e7e70623f2 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt @@ -45,7 +45,7 @@ class BlockedUserViewTest { fun `clicking on back invokes back callback`() { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLogoutView( + rule.setBlockedUsersView( aBlockedUsersState( eventSink = eventsRecorder ), @@ -59,7 +59,7 @@ class BlockedUserViewTest { fun `clicking on a user emits the expected Event`() { val eventsRecorder = EventsRecorder() val userList = aMatrixUserList() - rule.setLogoutView( + rule.setBlockedUsersView( aBlockedUsersState( blockedUsers = userList, eventSink = eventsRecorder @@ -72,7 +72,7 @@ class BlockedUserViewTest { @Test fun `clicking on cancel sends a BlockedUsersEvents`() { val eventsRecorder = EventsRecorder() - rule.setLogoutView( + rule.setBlockedUsersView( aBlockedUsersState( unblockUserAction = AsyncAction.Confirming, eventSink = eventsRecorder @@ -85,7 +85,7 @@ class BlockedUserViewTest { @Test fun `clicking on confirm sends a BlockedUsersEvents`() { val eventsRecorder = EventsRecorder() - rule.setLogoutView( + rule.setBlockedUsersView( aBlockedUsersState( unblockUserAction = AsyncAction.Confirming, eventSink = eventsRecorder @@ -96,7 +96,7 @@ class BlockedUserViewTest { } } -private fun AndroidComposeTestRule.setLogoutView( +private fun AndroidComposeTestRule.setBlockedUsersView( state: BlockedUsersState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { From 0093e611cd507c8954037b21b9666cf63465cc24 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 12:30:06 +0200 Subject: [PATCH 022/115] Add Ui test on CallScreenView --- features/call/impl/build.gradle.kts | 2 + .../call/impl/ui/CallScreenStateProvider.kt | 2 +- .../call/impl/ui/CallScreenViewTest.kt | 88 +++++++++++++++++++ .../android/tests/testutils/EventsRecorder.kt | 8 ++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 features/call/impl/src/test/kotlin/io/element/android/features/call/impl/ui/CallScreenViewTest.kt diff --git a/features/call/impl/build.gradle.kts b/features/call/impl/build.gradle.kts index d6d46d06b0..b2d845a003 100644 --- a/features/call/impl/build.gradle.kts +++ b/features/call/impl/build.gradle.kts @@ -70,4 +70,6 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) testImplementation(projects.tests.testutils) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt index 75cd962c9a..ec30725fff 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt @@ -28,7 +28,7 @@ open class CallScreenStateProvider : PreviewParameterProvider { ) } -private fun aCallScreenState( +internal fun aCallScreenState( urlState: AsyncData = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"), userAgent: String = "", isInWidgetMode: Boolean = false, diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/ui/CallScreenViewTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/ui/CallScreenViewTest.kt new file mode 100644 index 0000000000..6d15e5001c --- /dev/null +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/ui/CallScreenViewTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.call.impl.ui + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.call.impl.pip.PictureInPictureEvents +import io.element.android.features.call.impl.pip.PictureInPictureState +import io.element.android.features.call.impl.pip.aPictureInPictureState +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.pressBack +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CallScreenViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on back when pip is not supported hangs up`() { + val eventsRecorder = EventsRecorder() + val pipEventsRecorder = EventsRecorder(expectEvents = false) + rule.setCallScreenView( + aCallScreenState( + eventSink = eventsRecorder + ), + aPictureInPictureState( + supportPip = false, + eventSink = pipEventsRecorder, + ), + ) + rule.pressBack() + eventsRecorder.assertSize(2) + eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels } + eventsRecorder.assertTrue(1) { it == CallScreenEvents.Hangup } + } + + @Test + fun `clicking on back when pip is supported enables PiP`() { + val eventsRecorder = EventsRecorder() + val pipEventsRecorder = EventsRecorder() + rule.setCallScreenView( + aCallScreenState( + eventSink = eventsRecorder + ), + aPictureInPictureState( + supportPip = true, + eventSink = pipEventsRecorder, + ), + ) + rule.pressBack() + eventsRecorder.assertSize(1) + eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels } + pipEventsRecorder.assertSingle(PictureInPictureEvents.EnterPictureInPicture) + } +} + +private fun AndroidComposeTestRule.setCallScreenView( + state: CallScreenState, + pipState: PictureInPictureState, + requestPermissions: (Array, RequestPermissionCallback) -> Unit = { _, _ -> }, +) { + setContent { + CallScreenView( + state = state, + pipState = pipState, + requestPermissions = requestPermissions, + ) + } +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt index 3a1c4babff..b973b5a8d5 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt @@ -42,4 +42,12 @@ class EventsRecorder( fun assertList(expectedEvents: List) { assertThat(events).isEqualTo(expectedEvents) } + + fun assertSize(size: Int) { + assertThat(events.size).isEqualTo(size) + } + + fun assertTrue(index: Int, predicate: (T) -> Boolean) { + assertThat((predicate(events[index]))).isTrue() + } } From b409f2ea19a7c26da6dbe24062303ce02f958a0f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 13:19:35 +0200 Subject: [PATCH 023/115] Remove extra parenthesis --- .../kotlin/io/element/android/tests/testutils/EventsRecorder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt index b973b5a8d5..7818de4118 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt @@ -48,6 +48,6 @@ class EventsRecorder( } fun assertTrue(index: Int, predicate: (T) -> Boolean) { - assertThat((predicate(events[index]))).isTrue() + assertThat(predicate(events[index])).isTrue() } } From 0a1725f16dcbbf2d14d4ac8bbfe305445c283ee9 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 8 Jul 2024 11:29:02 +0000 Subject: [PATCH 024/115] Update screenshots --- .../images/features.call.impl.ui_CallScreenView_Day_1_en.png | 4 ++-- .../images/features.call.impl.ui_CallScreenView_Day_2_en.png | 3 +++ .../features.call.impl.ui_CallScreenView_Night_1_en.png | 4 ++-- .../features.call.impl.ui_CallScreenView_Night_2_en.png | 3 +++ 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_2_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_1_en.png index abd445c244..9d11ad5395 100644 --- a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68d9ca60586aac84157c60f126b17b70ca9d52087da80f253b60f47de87d7ff6 -size 13750 +oid sha256:c976f3c1d4809c28cb865b0dfe7ce1eed5fe2c9959a80da8efab5d3594e38e41 +size 14427 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_2_en.png new file mode 100644 index 0000000000..abd445c244 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68d9ca60586aac84157c60f126b17b70ca9d52087da80f253b60f47de87d7ff6 +size 13750 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_1_en.png index 1c4cfe0583..3fee0596fb 100644 --- a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b676c158a1f820d50c9ffd50d048d5c236ee8356279663f257a4882f06c5a1a9 -size 12214 +oid sha256:d6acbdb4ea1e66fa4638fc9b454566968081c511e0dcfde3f1e57fd9725a1edb +size 13263 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_2_en.png new file mode 100644 index 0000000000..1c4cfe0583 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b676c158a1f820d50c9ffd50d048d5c236ee8356279663f257a4882f06c5a1a9 +size 12214 From d9a498c86a466d7e7ffd558fbc5c5c7530375c0e Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 8 Jul 2024 13:42:38 +0200 Subject: [PATCH 025/115] Reduce delay when selecting room list filters (#3160) Previously, this had to wait for a recomposition until it was launched, which took ~100ms in a debug build. With these changes, the side effect is emitted in 10-20ms instead. --- .../impl/filters/RoomListFiltersPresenter.kt | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt index 4292492af5..e67f7b2faf 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt @@ -17,13 +17,13 @@ package io.element.android.features.roomlist.impl.filters import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionStrategy import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.roomlist.RoomListService import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.map import javax.inject.Inject import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter @@ -31,10 +31,10 @@ class RoomListFiltersPresenter @Inject constructor( private val roomListService: RoomListService, private val filterSelectionStrategy: FilterSelectionStrategy, ) : Presenter { + private val initialFilters = filterSelectionStrategy.filterSelectionStates.value.toPersistentList() + @Composable override fun present(): RoomListFiltersState { - val filters by filterSelectionStrategy.filterSelectionStates.collectAsState() - fun handleEvents(event: RoomListFiltersEvents) { when (event) { RoomListFiltersEvents.ClearSelectedFilters -> { @@ -46,12 +46,15 @@ class RoomListFiltersPresenter @Inject constructor( } } - LaunchedEffect(filters) { - val allRoomsFilter = MatrixRoomListFilter.All( - filters - .filter { it.isSelected } - .map { roomListFilter -> - when (roomListFilter.filter) { + val filters by produceState(initialValue = initialFilters) { + filterSelectionStrategy.filterSelectionStates + .map { filters -> + value = filters.toPersistentList() + filters.mapNotNull { filterState -> + if (!filterState.isSelected) { + return@mapNotNull null + } + when (filterState.filter) { RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group RoomListFilter.People -> MatrixRoomListFilter.Category.People RoomListFilter.Unread -> MatrixRoomListFilter.Unread @@ -59,12 +62,15 @@ class RoomListFiltersPresenter @Inject constructor( RoomListFilter.Invites -> MatrixRoomListFilter.Invite } } - ) - roomListService.allRooms.updateFilter(allRoomsFilter) + } + .collect { filters -> + val result = MatrixRoomListFilter.All(filters) + roomListService.allRooms.updateFilter(result) + } } return RoomListFiltersState( - filterSelectionStates = filters.toPersistentList(), + filterSelectionStates = filters, eventSink = ::handleEvents ) } From c5504365006bd0c43871a2d9ff9e5f6cf62b8c91 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 8 Jul 2024 13:43:24 +0200 Subject: [PATCH 026/115] Fix `MainActionButton` layout for long texts (#3158) * Fix `MainActionButton` layout for long texts * Update screenshots --------- Co-authored-by: ElementBot --- .../components/button/MainActionButton.kt | 22 ++++++++++++++----- ....roomdetails.impl_RoomDetailsDark_0_en.png | 4 ++-- ...roomdetails.impl_RoomDetailsDark_10_en.png | 4 ++-- ...roomdetails.impl_RoomDetailsDark_11_en.png | 4 ++-- ...roomdetails.impl_RoomDetailsDark_12_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_1_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_2_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_3_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_4_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_5_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_6_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_7_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_8_en.png | 4 ++-- ....roomdetails.impl_RoomDetailsDark_9_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_0_en.png | 4 ++-- ...res.roomdetails.impl_RoomDetails_10_en.png | 4 ++-- ...res.roomdetails.impl_RoomDetails_11_en.png | 4 ++-- ...res.roomdetails.impl_RoomDetails_12_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_1_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_2_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_3_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_4_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_5_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_6_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_7_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_8_en.png | 4 ++-- ...ures.roomdetails.impl_RoomDetails_9_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_0_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_1_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_2_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_5_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_6_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_7_en.png | 4 ++-- ...rofile.shared_UserProfileView_Day_8_en.png | 4 ++-- ...file.shared_UserProfileView_Night_0_en.png | 2 +- ...file.shared_UserProfileView_Night_1_en.png | 4 ++-- ...file.shared_UserProfileView_Night_2_en.png | 4 ++-- ...file.shared_UserProfileView_Night_5_en.png | 4 ++-- ...file.shared_UserProfileView_Night_6_en.png | 4 ++-- ...file.shared_UserProfileView_Night_7_en.png | 4 ++-- ...file.shared_UserProfileView_Night_8_en.png | 2 +- ...nts.button_MainActionButton_Buttons_en.png | 4 ++-- 42 files changed, 97 insertions(+), 85 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt index a1172b47fa..4c6d8ecc8c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt @@ -18,12 +18,12 @@ package io.element.android.libraries.designsystem.components.button import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +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.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.LocalContentColor @@ -33,6 +33,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.Hyphens +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -61,7 +64,7 @@ fun MainActionButton( onClick = onClick, indication = ripple ) - .widthIn(min = 76.dp), + .widthIn(min = 76.dp, max = 96.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { val tintColor = if (enabled) LocalContentColor.current else MaterialTheme.colorScheme.secondary @@ -73,8 +76,10 @@ fun MainActionButton( Spacer(modifier = Modifier.height(14.dp)) Text( title, - style = ElementTheme.typography.fontBodyMdMedium, + style = ElementTheme.typography.fontBodyMdMedium.copy(hyphens = Hyphens.Auto), color = tintColor, + overflow = TextOverflow.Visible, + textAlign = TextAlign.Center, ) } } @@ -89,13 +94,20 @@ internal fun MainActionButtonPreview() { @Composable private fun ContentsToPreview() { - Row(Modifier.padding(10.dp)) { + Row( + modifier = Modifier.padding(10.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { MainActionButton( title = "Share", imageVector = CompoundIcons.ShareAndroid(), onClick = { }, ) - Spacer(modifier = Modifier.width(20.dp)) + MainActionButton( + title = "Share with a long text", + imageVector = CompoundIcons.ShareAndroid(), + onClick = { }, + ) MainActionButton( title = "Share", imageVector = CompoundIcons.ShareAndroid(), diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png index 8a8a532ba2..f3becc3f46 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b66bfca625a2c5abbb99eb63840c99d941331f74da6741203f382ee49fec29fa -size 45772 +oid sha256:d2e96d71fd2e0e67415ab5d69b2712661f4e453980e202e60717cc73e1e3fa81 +size 45821 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png index 99307b9b97..9ad15d62b1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24d1b09fbf025b954711e4bd8392d32675568154bbd06ce8c69f4a8deb91a958 -size 43695 +oid sha256:1884020edf5182e4be0ef7408ae09d3ebdc79e4f482c0ed22c47864fb4ca3b6d +size 43682 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png index f11e3460cb..17e9510513 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05d47c7317111c9e7ac34f66705a8344114501c35d05d630f2146e128300077d -size 42587 +oid sha256:50940f08a015ca4ecbd4f88810b183d1632daa34e8f79c38017f20ad5a1ce90a +size 42637 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png index 2218fcc35e..9ca537357d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5491a9525166428c8b1ce94aaf3ef56aa5415cab19f8bcd99be128c265925e9 -size 45669 +oid sha256:259aced4d75ec473abf8f4fb41c3c734e69618a552b2cf0b9ceeb98d59deec25 +size 45717 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png index 773c3fe950..e42f829f62 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e1320dbc665a15434e9ea46bc8f085cb0a1dfe1a328d41139401ebd8ef23f84 -size 34975 +oid sha256:4885f85102e8e8a509586fb48e7a26d5ceb1dd7aa6897b955cb781eed87b9f30 +size 35023 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png index 028a806260..b262ff6214 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42590cba46dc5f9e0d4e9504d1ea854c5f5f3fdf3acaddca2604988164153beb -size 37065 +oid sha256:f6d8d1c7aa92f526398221aeff77cc44ebdad36bc4a1259ebb3c9635483af1f5 +size 37117 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png index 5e5446c198..e44dff5529 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30e9d873c187481341222a986741061eb84162127d7e0d086d3c957d9ef2d2d8 -size 35945 +oid sha256:3679bf87660a8bd799ae9c4397266227a0f46559bdcae6ddecec0930d5c78e95 +size 35993 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png index d9e920eb75..38cccf98e2 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:189e30f39a33924a6da727d4caa4ce117df66cc9dd1857c4909649cf8db51fdb -size 43191 +oid sha256:0ae27c470cb9be1fa75b85b607bcc3666839801376e00b9c0ec8b86d229d907b +size 43236 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png index 5053bfa3af..ce9eec3248 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd1fee6b0af1818d9b0fb6809620fa8118b970f439ae75c504ffd4e172aa60d3 -size 41670 +oid sha256:ea6ba2ec613561a26331c72ca89a6bd44b96c160bee75bc9a9b9a06339426275 +size 41660 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png index 5053bfa3af..ce9eec3248 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd1fee6b0af1818d9b0fb6809620fa8118b970f439ae75c504ffd4e172aa60d3 -size 41670 +oid sha256:ea6ba2ec613561a26331c72ca89a6bd44b96c160bee75bc9a9b9a06339426275 +size 41660 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png index e70f6778f8..308b3bc8f1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0f12af0b98d2d145d5443554a0657cd0699fe27f86447873630a189cbce615a -size 45008 +oid sha256:bf9ea4e85c5dc2d9d5c609b29e6232a5932c9b3bee81c459e9214ac16f7a3764 +size 44986 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png index f4023197f4..c2416c95b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db2babbe88dd81723ecf8f1c83fa73042efb590d04f1b12aaabf102f7f576dd9 -size 43883 +oid sha256:569a0dfdc4c5c77f44999b0e9e7a1b2458cac6b01dfd56800d3e7c3b5f8c0347 +size 43930 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png index 866944d635..12c112d879 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53056291448931a702d761557712626259f04a3969f128553a7bc7a172d74d5b -size 43840 +oid sha256:45e0e9952c3ca0fb805b5e53b990c3ec54732a971ecc658e25ef73254b3f1ca4 +size 43871 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png index 0c89045531..fea3ce3463 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d69018133b9a3ebf36e7819031df2c8b8bc19f1e7632cfeabc02ca6bcf76ffce -size 46744 +oid sha256:dcf1f93aa20a0acd4c32f761fdd39abf46016c84072f0a22c9b9cf0dd7f2c6f2 +size 46788 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png index 0d9c8a73b7..42fce9b5d6 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04a0838ac192efeacb821d23ed8b3ce8305b8e8084beb0e5a0fbc56cfa1ce964 -size 44435 +oid sha256:dc8fb59e77e8e09e79f5d89e039f302f28e2451faa025e9f1ce3b0ac48f75f8d +size 44404 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png index 343de082ef..661f82e060 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d760e5015257bd8d608749c1926c6177e4d0ef70d5d5f32764e970f4c80cb43 -size 43331 +oid sha256:e3166a62b24bfaf55d3dfa5d6e9fcd6dca2ee2789928efd66d8147e4e11d0ce0 +size 43377 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png index 3ba823f05b..a71a604612 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63d2319dc4617d805f0b652983e4bb7b6e4aa7c2e9c6ec37398fce2e2413a79d -size 46441 +oid sha256:f799d30b5c3d847cc8011ce4aefbb8710b34df8f11116842af035b05d233cfa1 +size 46485 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png index 0e629da29e..3221ed0029 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:374166110a04cee8f63d1d330601976970e5abcd92947cfb284e61613cf47f51 -size 35727 +oid sha256:09ce1743e0e3c5629fab6d0c2d06cc15be7229e446673b05f49f0e031a1c2823 +size 35776 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png index e8e3831962..54067184f1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c3dec8246e5605c61a9132cea048c5afaad35751246ffd81160c33782c26c93 -size 37861 +oid sha256:84ef09f2163f10a583853a52008f5a53f26c90350136a970fd55e0199f5fd47f +size 37907 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png index 3f3136ce6c..a5bd17734c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8491d21c160851bbe3a0362edae05e46cf9cb072ca4bf41ad569a3a6c57097c -size 36458 +oid sha256:9941f55b35128067bd7560ed24d44bc0bbe8e13a6702d6ec2d35304462f755a2 +size 36503 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png index 00cc501d4b..103d53664c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7f08cac8eecb3ae6067f756411d0ec5607d3207832cfe69858ef1189f18ee45 -size 43996 +oid sha256:b300a88051346537beac5096895ef87b007e56fdddc33ad63db092f4c813a454 +size 44041 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png index 3395cc0a08..9030577759 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5649302bb7468c7ec2214b509259df584574e52662eaca1782ad732c6c9ac659 -size 42296 +oid sha256:0fe90dffd26521d58e0956b9ea967c641f75a7cdeb39052abbfcdf85754c5dd3 +size 42281 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png index 3395cc0a08..9030577759 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5649302bb7468c7ec2214b509259df584574e52662eaca1782ad732c6c9ac659 -size 42296 +oid sha256:0fe90dffd26521d58e0956b9ea967c641f75a7cdeb39052abbfcdf85754c5dd3 +size 42281 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png index 7a9d976302..88bdc93f10 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea0cfd0f222862a8becdd4b4a7f2e47a4a7d83ecb1ff761889c46631d7d43e97 -size 45861 +oid sha256:41b3d79ae55b26478c2e6d1910b37a98eb22ac37e9bfd3a33de4dc805e82028b +size 45850 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png index 9a0cf77937..371a43df04 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd7aa71f1d24d4cacafb4aaea8802d1493e317604d5ea35f0d9fac1a304d7f19 -size 44718 +oid sha256:02a966757cc01f8707738d3cd29d05df0bf3842069fec812c7223e2e29da5dbf +size 44761 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png index a2ec270d02..816e803108 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:234e65b2caa1f23f9e4d0e01c142d22df0d407851e554a7db3e0b09c57acf47a -size 44626 +oid sha256:5a2148c28660bd9fd3a45b1f06b331966f79c77166b40bb1b7afb5b926407817 +size 44647 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png index 734efeb576..228b9abacf 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac5b4e74ec63e7e8e6e61d3f7ffd731e0376eac278c2adce3582d1da02dafe1f -size 21262 +oid sha256:c59e2c55e8527759765b7ca693e2f9f064de55590dd21831ad1f3d7c8c5ed929 +size 21261 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png index 9a980dc6a0..800a8d6963 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a99ca956353e88e7358c372b0ddda72f3b1b9e558b55b4455122af109b6be5a -size 18957 +oid sha256:fb865c555bf11c378b1d83969001c8bb32b56600203592ce94050de5db2db51d +size 18990 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png index c69f97ed09..7adaed41a1 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:071f13f5bbcf43b4e8a76c98749657daf4586a87d495fdcf605bf526496478eb -size 21608 +oid sha256:effcb61375eca558758f9671e0fa77740f6d3d03993fd744ce8c9fe751c8959b +size 21601 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png index d4943a306b..6193547f41 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3a8dce72fa62a42d6c8e1fbcf875ff279c5e3dd17399d4e8b7e4a8e5298905b -size 22173 +oid sha256:6b356948c8b3ad279d7e6c43dda56dcd6697c22843657be016b61a88a5693c71 +size 22167 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png index c7e92b6e12..99a35650b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff97b32f07dd179fb4c2685c793734fc0888978273d2330ab571347786d969bc -size 20522 +oid sha256:6112f3060064ffbb1ee3099779ba1046d2665292c0ad2d9dfc2a615c4eb15066 +size 20497 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png index 22d5ad0d6f..46e13f1a1f 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0c300400dba60e0389f454465d14c22926ae224bbd7e93355dd74c7f73f4a6a -size 22174 +oid sha256:6319c392d7f69702dcb1b148e31b3d13ffa9a2f728b01728727d3becbb4f51df +size 22236 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png index 734efeb576..228b9abacf 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac5b4e74ec63e7e8e6e61d3f7ffd731e0376eac278c2adce3582d1da02dafe1f -size 21262 +oid sha256:c59e2c55e8527759765b7ca693e2f9f064de55590dd21831ad1f3d7c8c5ed929 +size 21261 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png index 4908c22bdb..f6383e2752 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8bb7296cd22a4c882ec3e93b66b464754d5a58a2f855fe5253ba7b7a2660bfe +oid sha256:d917407c9e62ad942301e74428c1b0691cc3088b903713d5fe6664c23bb8cb7e size 21124 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png index f6adf02bfe..a363eb7d1e 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5179ae27b40a0611e8ac715e501b01e715ceb8f227d7b432652186f974f3f512 -size 18970 +oid sha256:f85b7926edabf510eef0d7f378e4066e99373593830ca902a47f8c12db771825 +size 18950 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png index 3abc686c9a..4e665c74ce 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4e55ae6975324403f2067b3633ddc38c5abd98f092c5d48a9cede88d597193a -size 21469 +oid sha256:c3ff4f065b6dd0550c1db5950923bb69421ecb6a7326d1a5621f2dec91d00ba7 +size 21462 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png index ff5db422ce..d2c86ce5de 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54c198bbebdad2dc4110ca04d3a83b70d75557fb0872157c1724f339054d1234 -size 22020 +oid sha256:0b438fece9def5f6af510b65be50e209671f4d16a05c4e97c5397f73b25ab30f +size 22018 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png index 660dd9d65d..0a7eca2747 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65e816869277c12044991fb26427aac17d65072f1e0d7218b53583363a81024f -size 19249 +oid sha256:a9bee8776293351621a63f10593eb5d718a3f60b577aaf5c1496c46219a61109 +size 19223 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png index 141e3f9965..b04fc20fea 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:611ac8c529d73b14a73a73aeb7cec1eb1a50085d536bd700567462256df30974 -size 22026 +oid sha256:74566a2b227be7d781392517bb98c6e80040fa8e4c8d4c8ee781e8384d1d2c98 +size 21996 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png index 4908c22bdb..f6383e2752 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8bb7296cd22a4c882ec3e93b66b464754d5a58a2f855fe5253ba7b7a2660bfe +oid sha256:d917407c9e62ad942301e74428c1b0691cc3088b903713d5fe6664c23bb8cb7e size 21124 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.button_MainActionButton_Buttons_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.button_MainActionButton_Buttons_en.png index 4977999091..31b2ae723a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.button_MainActionButton_Buttons_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.button_MainActionButton_Buttons_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8939d2b25d57827c2441a03bbdd324548f93238ca5224d13f788e780297acbf3 -size 13190 +oid sha256:48b55e3d045eff0f2129394ea64bb5bc4962278a8feeb13323f5eed09d95596f +size 22300 From 962320a0d8dd9c89e2e4a1e365402d5303f6892d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 14:38:54 +0200 Subject: [PATCH 027/115] Remove test on API 24 (working locally but not on CI :/) --- .../features/call/impl/pip/PictureInPicturePresenterTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt index b1d9df68dd..25890b78d9 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt @@ -32,7 +32,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) class PictureInPicturePresenterTest { @Test - @Config(sdk = [VERSION_CODES.N, VERSION_CODES.O, VERSION_CODES.S]) + @Config(sdk = [/*VERSION_CODES.N,*/ VERSION_CODES.O, VERSION_CODES.S]) fun `when pip is not supported, the state value supportPip is false`() = runTest { val presenter = createPictureInPicturePresenter(supportPip = false) moleculeFlow(RecompositionMode.Immediate) { From 5abeb62fe486c615868d9c8a3aae4af348ea0129 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:17:01 +0000 Subject: [PATCH 028/115] Update wysiwyg to v2.37.5 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccd0edd85a..ce22b4d993 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ serialization_json = "1.6.3" showkase = "1.0.3" appyx = "1.4.0" sqldelight = "2.0.2" -wysiwyg = "2.37.4" +wysiwyg = "2.37.5" telephoto = "0.11.2" # DI From fbe70ddf5db6fa1d90fb166f76861ad4462edc64 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 16:47:17 +0200 Subject: [PATCH 029/115] Use session coroutine scope instead of application coroutine scope. --- .../android/features/ftue/impl/state/DefaultFtueService.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index 049bba4dbb..8ff01100d7 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -24,6 +24,7 @@ import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.permissions.api.PermissionStateProvider @@ -47,7 +48,7 @@ import kotlin.time.Duration.Companion.seconds @ContributesBinding(SessionScope::class) class DefaultFtueService @Inject constructor( private val sdkVersionProvider: BuildVersionSdkIntProvider, - coroutineScope: CoroutineScope, + @SessionCoroutineScope sessionCoroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, private val permissionStateProvider: PermissionStateProvider, private val lockScreenService: LockScreenService, @@ -66,11 +67,11 @@ class DefaultFtueService @Inject constructor( init { sessionVerificationService.sessionVerifiedStatus .onEach { updateState() } - .launchIn(coroutineScope) + .launchIn(sessionCoroutineScope) analyticsService.didAskUserConsent() .onEach { updateState() } - .launchIn(coroutineScope) + .launchIn(sessionCoroutineScope) } suspend fun getNextStep(currentStep: FtueStep? = null): FtueStep? = From e48b958d5286c8529b8a55bf3c8c6c443fc6b9b9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 16:48:40 +0200 Subject: [PATCH 030/115] When clearing cache, ensure that SessionPreferencesStore is removed from the cache. Fixes blank screen after clear cache. Also cleanup the codebase. --- appnav/build.gradle.kts | 1 + .../android/appnav/root/RootNavStateFlowFactory.kt | 4 ++++ .../element/android/features/ftue/impl/FtueFlowNode.kt | 10 ++++------ .../features/ftue/impl/state/DefaultFtueService.kt | 8 +++----- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index e99f3e01f0..53680fd44e 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.deeplink) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.preferences.api) implementation(projects.libraries.push.api) implementation(projects.libraries.pushproviders.api) implementation(projects.libraries.designsystem) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt index b801415f88..9bdd203cd5 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt @@ -23,6 +23,7 @@ import io.element.android.features.login.api.LoginUserStory import io.element.android.features.preferences.api.CacheService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder +import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.sessionstorage.api.LoggedInState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -42,6 +43,7 @@ class RootNavStateFlowFactory @Inject constructor( private val matrixClientsHolder: MatrixClientsHolder, private val imageLoaderHolder: ImageLoaderHolder, private val loginUserStory: LoginUserStory, + private val sessionPreferencesStoreFactory: SessionPreferencesStoreFactory, ) { private var currentCacheIndex = 0 @@ -73,6 +75,8 @@ class RootNavStateFlowFactory @Inject constructor( matrixClientsHolder.remove(sessionId) // Ensure image loader will be recreated with the new MatrixClient imageLoaderHolder.remove(sessionId) + // Also remove cached value for SessionPreferencesStore + sessionPreferencesStoreFactory.remove(sessionId) } .toIndexFlow(initialCacheIndex) .onEach { cacheIndex -> diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 6720fa0274..78ee3c7e69 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -45,7 +45,7 @@ import io.element.android.libraries.di.SessionScope import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -93,11 +93,9 @@ class FtueFlowNode @AssistedInject constructor( }) analyticsService.didAskUserConsent() - .drop(1) // We only care about consent passing from not asked to asked state - .onEach { didAskUserConsent -> - if (didAskUserConsent) { - lifecycleScope.launch { moveToNextStep() } - } + .distinctUntilChanged() + .onEach { + lifecycleScope.launch { moveToNextStep() } } .launchIn(lifecycleScope) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index 8ff01100d7..24c9c7df82 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn @@ -70,6 +71,7 @@ class DefaultFtueService @Inject constructor( .launchIn(sessionCoroutineScope) analyticsService.didAskUserConsent() + .distinctUntilChanged() .onEach { updateState() } .launchIn(sessionCoroutineScope) } @@ -120,10 +122,7 @@ class DefaultFtueService @Inject constructor( emit(SessionVerifiedStatus.NotVerified) } .first() - // For some obscure reason we need to call this *before* we check the `readyVerifiedSessionStatus`, otherwise there's a deadlock - // It seems like a DataStore bug - val skipVerification = canSkipVerification() - return readyVerifiedSessionStatus == SessionVerifiedStatus.NotVerified && !skipVerification + return readyVerifiedSessionStatus == SessionVerifiedStatus.NotVerified && !canSkipVerification() } private suspend fun canSkipVerification(): Boolean { @@ -131,7 +130,6 @@ class DefaultFtueService @Inject constructor( } private suspend fun needsAnalyticsOptIn(): Boolean { - // We need this function to not be suspend, so we need to load the value through runBlocking return analyticsService.didAskUserConsent().first().not() } From 5ccf95018dc0167609b914063d9f9c15e6c5d552 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 17:49:39 +0200 Subject: [PATCH 031/115] Fix test compilation --- .../android/features/ftue/impl/DefaultFtueServiceTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt index ac2d0724f1..ae2c4dec58 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt @@ -251,7 +251,7 @@ class DefaultFtueServiceTest { // First version where notification permission is required sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU, ) = DefaultFtueService( - coroutineScope = coroutineScope, + sessionCoroutineScope = coroutineScope, sessionVerificationService = sessionVerificationService, sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion), analyticsService = analyticsService, From 9856ece42a2144f1850affe5a534d3ac89318110 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 18:17:44 +0200 Subject: [PATCH 032/115] Ensure `PinUnlockActivity` and `IncomingCallActivity` use the internal theme set by the user. --- .../call/impl/ui/IncomingCallActivity.kt | 29 +++- .../call/impl/ui/IncomingCallScreen.kt | 149 +++++++++--------- features/lockscreen/impl/build.gradle.kts | 1 + .../impl/unlock/activity/PinUnlockActivity.kt | 16 +- 4 files changed, 113 insertions(+), 82 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index 02cd612bcf..cbbeaef47c 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -20,8 +20,15 @@ import android.os.Bundle import android.view.WindowManager import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.core.content.IntentCompat import androidx.lifecycle.lifecycleScope +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme +import io.element.android.compound.theme.isDark +import io.element.android.compound.theme.mapToTheme import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings @@ -29,6 +36,7 @@ import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.utils.ActiveCallManager import io.element.android.features.call.impl.utils.CallState import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -51,6 +59,9 @@ class IncomingCallActivity : AppCompatActivity() { @Inject lateinit var activeCallManager: ActiveCallManager + @Inject + lateinit var appPreferencesStore: AppPreferencesStore + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,11 +79,19 @@ class IncomingCallActivity : AppCompatActivity() { val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) } if (notificationData != null) { setContent { - IncomingCallScreen( - notificationData = notificationData, - onAnswer = ::onAnswer, - onCancel = ::onCancel, - ) + val theme by remember { + appPreferencesStore.getThemeFlow().mapToTheme() + } + .collectAsState(initial = Theme.System) + ElementTheme( + darkTheme = theme.isDark() + ) { + IncomingCallScreen( + notificationData = notificationData, + onAnswer = ::onAnswer, + onCancel = ::onCancel, + ) + } } } else { // No data, finish the activity diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt index 80dc2353ca..d663f15807 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt @@ -64,67 +64,65 @@ internal fun IncomingCallScreen( onAnswer: (CallNotificationData) -> Unit, onCancel: () -> Unit, ) { - ElementTheme { - OnboardingBackground() + OnboardingBackground() + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Bottom + ) { Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Bottom + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp, top = 124.dp) + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 20.dp, end = 20.dp, top = 124.dp) - .weight(1f), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Avatar( - avatarData = AvatarData( - id = notificationData.senderId.value, - name = notificationData.senderName, - url = notificationData.avatarUrl, - size = AvatarSize.IncomingCall, - ) - ) - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = notificationData.senderName ?: notificationData.senderId.value, - style = ElementTheme.typography.fontHeadingMdBold, - textAlign = TextAlign.Center, - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.screen_incoming_call_subtitle_android), - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, end = 24.dp, bottom = 64.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - ActionButton( - size = 64.dp, - onClick = { onAnswer(notificationData) }, - icon = CompoundIcons.VoiceCall(), - title = stringResource(CommonStrings.action_accept), - backgroundColor = ElementTheme.colors.iconSuccessPrimary, - borderColor = ElementTheme.colors.borderSuccessSubtle + Avatar( + avatarData = AvatarData( + id = notificationData.senderId.value, + name = notificationData.senderName, + url = notificationData.avatarUrl, + size = AvatarSize.IncomingCall, ) + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = notificationData.senderName ?: notificationData.senderId.value, + style = ElementTheme.typography.fontHeadingMdBold, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.screen_incoming_call_subtitle_android), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textSecondary, + textAlign = TextAlign.Center, + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, bottom = 64.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + ActionButton( + size = 64.dp, + onClick = { onAnswer(notificationData) }, + icon = CompoundIcons.VoiceCall(), + title = stringResource(CommonStrings.action_accept), + backgroundColor = ElementTheme.colors.iconSuccessPrimary, + borderColor = ElementTheme.colors.borderSuccessSubtle + ) - ActionButton( - size = 64.dp, - onClick = onCancel, - icon = CompoundIcons.EndCall(), - title = stringResource(CommonStrings.action_reject), - backgroundColor = ElementTheme.colors.iconCriticalPrimary, - borderColor = ElementTheme.colors.borderCriticalSubtle - ) - } + ActionButton( + size = 64.dp, + onClick = onCancel, + icon = CompoundIcons.EndCall(), + title = stringResource(CommonStrings.action_reject), + backgroundColor = ElementTheme.colors.iconCriticalPrimary, + borderColor = ElementTheme.colors.borderCriticalSubtle + ) } } } @@ -145,7 +143,8 @@ private fun ActionButton( horizontalAlignment = Alignment.CenterHorizontally ) { FilledIconButton( - modifier = Modifier.size(size + borderSize) + modifier = Modifier + .size(size + borderSize) .border(borderSize, borderColor, CircleShape), onClick = onClick, colors = IconButtonDefaults.filledIconButtonColors( @@ -171,22 +170,20 @@ private fun ActionButton( @PreviewsDayNight @Composable -internal fun IncomingCallScreenPreview() { - ElementPreview { - IncomingCallScreen( - notificationData = CallNotificationData( - sessionId = SessionId("@alice:matrix.org"), - roomId = RoomId("!1234:matrix.org"), - eventId = EventId("\$asdadadsad:matrix.org"), - senderId = UserId("@bob:matrix.org"), - roomName = "A room", - senderName = "Bob", - avatarUrl = null, - notificationChannelId = "incoming_call", - timestamp = 0L, - ), - onAnswer = {}, - onCancel = {}, - ) - } +internal fun IncomingCallScreenPreview() = ElementPreview { + IncomingCallScreen( + notificationData = CallNotificationData( + sessionId = SessionId("@alice:matrix.org"), + roomId = RoomId("!1234:matrix.org"), + eventId = EventId("\$asdadadsad:matrix.org"), + senderId = UserId("@bob:matrix.org"), + roomName = "A room", + senderName = "Bob", + avatarUrl = null, + notificationChannelId = "incoming_call", + timestamp = 0L, + ), + onAnswer = {}, + onCancel = {}, + ) } diff --git a/features/lockscreen/impl/build.gradle.kts b/features/lockscreen/impl/build.gradle.kts index 1cbe9692cf..21f3e05421 100644 --- a/features/lockscreen/impl/build.gradle.kts +++ b/features/lockscreen/impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.featureflag.api) implementation(projects.libraries.cryptography.api) + implementation(projects.libraries.preferences.api) implementation(projects.libraries.uiStrings) implementation(projects.libraries.sessionStorage.api) implementation(projects.services.appnavstate.api) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt index 9c228b0736..ce87613c24 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt @@ -23,14 +23,21 @@ import androidx.activity.OnBackPressedCallback import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.lifecycle.lifecycleScope import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme +import io.element.android.compound.theme.isDark +import io.element.android.compound.theme.mapToTheme import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter import io.element.android.features.lockscreen.impl.unlock.PinUnlockView import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.launch import javax.inject.Inject @@ -43,13 +50,20 @@ class PinUnlockActivity : AppCompatActivity() { @Inject lateinit var presenter: PinUnlockPresenter @Inject lateinit var lockScreenService: LockScreenService + @Inject lateinit var appPreferencesStore: AppPreferencesStore override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) bindings().inject(this) setContent { - ElementTheme { + val theme by remember { + appPreferencesStore.getThemeFlow().mapToTheme() + } + .collectAsState(initial = Theme.System) + ElementTheme( + darkTheme = theme.isDark() + ) { val state = presenter.present() PinUnlockView(state = state, isInAppUnlock = false) } From a8dcd52db0bf033923b067a813b558cef9215765 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 14:46:05 +0200 Subject: [PATCH 033/115] Add `--alignment-preserved true` when signing APK for F-Droid. Closes #3151 --- tools/release/release.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/release/release.sh b/tools/release/release.sh index 1a39150588..60ee3b06e4 100755 --- a/tools/release/release.sh +++ b/tools/release/release.sh @@ -227,6 +227,7 @@ cp "${fdroidTargetPath}"/app-fdroid-arm64-v8a-release.apk \ "${fdroidTargetPath}"/app-fdroid-arm64-v8a-release-signed.apk "${buildToolsPath}"/apksigner sign \ -v \ + --alignment-preserved true \ --ks "${keyStorePath}" \ --ks-pass pass:"${keyStorePassword}" \ --ks-key-alias elementx \ @@ -238,6 +239,7 @@ cp "${fdroidTargetPath}"/app-fdroid-armeabi-v7a-release.apk \ "${fdroidTargetPath}"/app-fdroid-armeabi-v7a-release-signed.apk "${buildToolsPath}"/apksigner sign \ -v \ + --alignment-preserved true \ --ks "${keyStorePath}" \ --ks-pass pass:"${keyStorePassword}" \ --ks-key-alias elementx \ @@ -249,6 +251,7 @@ cp "${fdroidTargetPath}"/app-fdroid-x86-release.apk \ "${fdroidTargetPath}"/app-fdroid-x86-release-signed.apk "${buildToolsPath}"/apksigner sign \ -v \ + --alignment-preserved true \ --ks "${keyStorePath}" \ --ks-pass pass:"${keyStorePassword}" \ --ks-key-alias elementx \ @@ -260,6 +263,7 @@ cp "${fdroidTargetPath}"/app-fdroid-x86_64-release.apk \ "${fdroidTargetPath}"/app-fdroid-x86_64-release-signed.apk "${buildToolsPath}"/apksigner sign \ -v \ + --alignment-preserved true \ --ks "${keyStorePath}" \ --ks-pass pass:"${keyStorePassword}" \ --ks-key-alias elementx \ From 03ce6cf975bc5a6b24549f41f7d0ab63f6ffa908 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 14:48:11 +0200 Subject: [PATCH 034/115] Remove `Type of change` from the PR template, we are now using PR- labels. --- .github/pull_request_template.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2307ea6e3e..ea44e3a5d0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,5 @@ -## Type of change - -- [ ] Feature -- [ ] Bugfix -- [ ] Technical -- [ ] Other : - ## Content From c73d81422011c29d275dafe31e47969481d57d29 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 18:31:41 +0200 Subject: [PATCH 035/115] CallScreenPresenter is reading the current theme, it needs to be in the ElementTheme block. Closes #3153 --- .../android/features/call/impl/ui/ElementCallActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index 1f4313864d..d0eab337f2 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -94,10 +94,10 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { appPreferencesStore.getThemeFlow().mapToTheme() } .collectAsState(initial = Theme.System) - val state = presenter.present() ElementTheme( darkTheme = theme.isDark() ) { + val state = presenter.present() CallScreenView( state = state, requestPermissions = { permissions, callback -> From 214c9d2d2ba06a305f85bc265943e333c5bb43f7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jul 2024 18:48:51 +0200 Subject: [PATCH 036/115] Fix KtLint issue --- .../features/call/impl/pip/PictureInPicturePresenterTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt index 25890b78d9..895505c278 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt @@ -32,7 +32,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) class PictureInPicturePresenterTest { @Test - @Config(sdk = [/*VERSION_CODES.N,*/ VERSION_CODES.O, VERSION_CODES.S]) + @Config(sdk = [VERSION_CODES.O, VERSION_CODES.S]) fun `when pip is not supported, the state value supportPip is false`() = runTest { val presenter = createPictureInPicturePresenter(supportPip = false) moleculeFlow(RecompositionMode.Immediate) { From 7b057faf1d647ffbeb8b473b9b694abaa833ae1b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jul 2024 10:01:04 +0200 Subject: [PATCH 037/115] Add a local copy of `inplace-fix.py` and `fix-pg-map-id.py` from latest release https://github.com/obfusk/reproducible-apk-tools/blob/v0.2.7 --- tools/release/fix-pg-map-id.py | 227 +++++++++++++++++++++++++++++++++ tools/release/inplace-fix.py | 199 +++++++++++++++++++++++++++++ tools/release/release.sh | 5 +- 3 files changed, 427 insertions(+), 4 deletions(-) create mode 100644 tools/release/fix-pg-map-id.py create mode 100644 tools/release/inplace-fix.py diff --git a/tools/release/fix-pg-map-id.py b/tools/release/fix-pg-map-id.py new file mode 100644 index 0000000000..d074316034 --- /dev/null +++ b/tools/release/fix-pg-map-id.py @@ -0,0 +1,227 @@ +#!/usr/bin/python3 +# encoding: utf-8 +# SPDX-FileCopyrightText: 2024 FC (Fay) Stegerman +# SPDX-License-Identifier: GPL-3.0-or-later + +import hashlib +import os +import re +import struct +import zipfile +import zlib + +from binascii import hexlify +from typing import Any, Dict, Match, Tuple + +DEX_MAGIC = b"dex\n" +DEX_MAGIC_RE = re.compile(rb"dex\n(\d{3})\x00") + +PROF_MAGIC = b"pro\x00" +PROF_010_P = b"010\x00" + +CLASSES_DEX_RE = re.compile(r"classes\d*\.dex") +ASSET_PROF = "assets/dexopt/baseline.prof" + +PG_MAP_ID_RE = re.compile(rb'(~~R8{"backend":"dex".*?"pg-map-id":")([0-9a-f]{7})(")') + +ATTRS = ("compress_type", "create_system", "create_version", "date_time", + "external_attr", "extract_version", "flag_bits") +LEVELS = (9, 6, 4, 1) + + +class Error(RuntimeError): + pass + + +# FIXME: is there a better alternative? +class ReproducibleZipInfo(zipfile.ZipInfo): + """Reproducible ZipInfo hack.""" + + if "_compresslevel" not in zipfile.ZipInfo.__slots__: # type: ignore[attr-defined] + if "compress_level" not in zipfile.ZipInfo.__slots__: # type: ignore[attr-defined] + raise Error("zipfile.ZipInfo has no ._compresslevel") + + _compresslevel: int + _override: Dict[str, Any] = {} + + def __init__(self, zinfo: zipfile.ZipInfo, **override: Any) -> None: + # pylint: disable=W0231 + if override: + self._override = {**self._override, **override} + for k in self.__slots__: + if hasattr(zinfo, k): + setattr(self, k, getattr(zinfo, k)) + + def __getattribute__(self, name: str) -> Any: + if name != "_override": + try: + return self._override[name] + except KeyError: + pass + return object.__getattribute__(self, name) + + +def fix_pg_map_id(input_dir: str, output_dir: str, map_id: str) -> None: + file_data = {} + for filename in [ASSET_PROF] + sorted(os.listdir(input_dir)): + if re.fullmatch(CLASSES_DEX_RE, filename) or filename == ASSET_PROF: + print(f"reading {filename!r}...") + with open(os.path.join(input_dir, *filename.split("/")), "rb") as fh: + file_data[filename] = fh.read() + _fix_pg_map_id(file_data, map_id) + for filename, data in file_data.items(): + print(f"writing {filename!r}...") + if "/" in filename: + os.makedirs(os.path.join(output_dir, *filename.split("/")[:-1]), exist_ok=True) + with open(os.path.join(output_dir, *filename.split("/")), "wb") as fh: + fh.write(data) + + +def fix_pg_map_id_apk(input_apk: str, output_apk: str, map_id: str) -> None: + with open(input_apk, "rb") as fh_raw: + with zipfile.ZipFile(input_apk) as zf_in: + with zipfile.ZipFile(output_apk, "w") as zf_out: + file_data = {} + for info in zf_in.infolist(): + if re.fullmatch(CLASSES_DEX_RE, info.filename) or info.filename == ASSET_PROF: + print(f"reading {info.filename!r}...") + file_data[info.filename] = zf_in.read(info) + _fix_pg_map_id(file_data, map_id) + for info in zf_in.infolist(): + attrs = {attr: getattr(info, attr) for attr in ATTRS} + zinfo = ReproducibleZipInfo(info, **attrs) + if info.compress_type == 8: + fh_raw.seek(info.header_offset) + n, m = struct.unpack(" 0: + ccrc = zlib.crc32(fh_raw.read(min(size, 4096)), ccrc) + size -= 4096 + with zf_in.open(info) as fh_in: + comps = {lvl: zlib.compressobj(lvl, 8, -15) for lvl in LEVELS} + ccrcs = {lvl: 0 for lvl in LEVELS} + while True: + data = fh_in.read(4096) + if not data: + break + for lvl in LEVELS: + ccrcs[lvl] = zlib.crc32(comps[lvl].compress(data), ccrcs[lvl]) + for lvl in LEVELS: + if ccrc == zlib.crc32(comps[lvl].flush(), ccrcs[lvl]): + zinfo._compresslevel = lvl + break + else: + raise Error(f"Unable to determine compresslevel for {info.filename!r}") + elif info.compress_type != 0: + raise Error(f"Unsupported compress_type {info.compress_type}") + if re.fullmatch(CLASSES_DEX_RE, info.filename) or info.filename == ASSET_PROF: + print(f"writing {info.filename!r}...") + zf_out.writestr(zinfo, file_data[info.filename]) + else: + with zf_in.open(info) as fh_in: + with zf_out.open(zinfo, "w") as fh_out: + while True: + data = fh_in.read(4096) + if not data: + break + fh_out.write(data) + + +def _fix_pg_map_id(file_data: Dict[str, bytes], map_id: str) -> None: + crcs = {} + for filename in file_data: + if re.fullmatch(CLASSES_DEX_RE, filename): + print(f"fixing {filename!r}...") + data = _fix_dex_id_checksum(file_data[filename], map_id.encode()) + file_data[filename] = data + crcs[filename] = zlib.crc32(data) + if ASSET_PROF in file_data: + print(f"fixing {ASSET_PROF!r}...") + file_data[ASSET_PROF] = _fix_prof_checksum(file_data[ASSET_PROF], crcs) + + +def _fix_dex_id_checksum(data: bytes, map_id: bytes) -> bytes: + def repl(m: Match[bytes]) -> bytes: + print(f"fixing pg-map-id: {m.group(2)!r} -> {map_id!r}") + return m.group(1) + map_id + m.group(3) + + magic = data[:8] + if magic[:4] != DEX_MAGIC or not DEX_MAGIC_RE.fullmatch(magic): + raise Error(f"Unsupported magic {magic!r}") + print(f"dex version={int(magic[4:7]):03d}") + checksum, signature = struct.unpack(" {hexlify(fixed_sig).decode()}") + fixed_data = fixed_sig + fixed_data + fixed_checksum = zlib.adler32(fixed_data) + print(f"fixing checksum: 0x{checksum:x} -> 0x{fixed_checksum:x}") + return magic + int.to_bytes(fixed_checksum, 4, "little") + fixed_data + + +def _fix_prof_checksum(data: bytes, crcs: Dict[str, int]) -> bytes: + magic, data = _split(data, 4) + version, data = _split(data, 4) + if magic == PROF_MAGIC: + if version == PROF_010_P: + print("prof version=010 P") + return PROF_MAGIC + PROF_010_P + _fix_prof_010_p_checksum(data, crcs) + else: + raise Error(f"Unsupported prof version {version!r}") + else: + raise Error(f"Unsupported magic {magic!r}") + + +def _fix_prof_010_p_checksum(data: bytes, crcs: Dict[str, int]) -> bytes: + num_dex_files, uncompressed_data_size, compressed_data_size, data = _unpack(" 0x{fixed_checksum:x}") + dex_data_headers.append(struct.pack( + " Any: + assert all(c in " Tuple[bytes, bytes]: + return data[:size], data[size:] + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(prog="fix-pg-map-id.py") + parser.add_argument("input_dir_or_apk", metavar="INPUT_DIR_OR_APK") + parser.add_argument("output_dir_or_apk", metavar="OUTPUT_DIR_OR_APK") + parser.add_argument("pg_map_id", metavar="PG_MAP_ID") + args = parser.parse_args() + if os.path.isdir(args.input_dir_or_apk): + fix_pg_map_id(args.input_dir_or_apk, args.output_dir_or_apk, args.pg_map_id) + else: + fix_pg_map_id_apk(args.input_dir_or_apk, args.output_dir_or_apk, args.pg_map_id) + +# vim: set tw=80 sw=4 sts=4 et fdm=marker : diff --git a/tools/release/inplace-fix.py b/tools/release/inplace-fix.py new file mode 100644 index 0000000000..5a57f97d10 --- /dev/null +++ b/tools/release/inplace-fix.py @@ -0,0 +1,199 @@ +#!/usr/bin/python3 +# encoding: utf-8 +# SPDX-FileCopyrightText: 2024 FC (Fay) Stegerman +# SPDX-License-Identifier: GPL-3.0-or-later + +import argparse +import os +import shutil +import subprocess +import sys +import tempfile + +from typing import Optional, Tuple + +COMMANDS = ( + "fix-compresslevel", + "fix-files", + "fix-newlines", + "fix-pg-map-id", + "rm-files", + "sort-apk", + "sort-baseline", +) + +BUILD_TOOLS_WITH_BROKEN_ZIPALIGN = ("31.0.0", "32.0.0") +BUILD_TOOLS_WITH_PAGE_SIZE_FROM = "35.0.0-rc1" +SDK_ENV = ("ANDROID_HOME", "ANDROID_SDK", "ANDROID_SDK_ROOT") + + +def _zipalign_cmd(page_align: bool, page_size: Optional[int]) -> Tuple[str, ...]: + if page_align: + if page_size is not None: + return ("zipalign", "-P", str(page_size), "4") + return ("zipalign", "-p", "4") + return ("zipalign", "4") + + +ZIPALIGN = _zipalign_cmd(page_align=False, page_size=None) +ZIPALIGN_P = _zipalign_cmd(page_align=True, page_size=None) + + +class Error(RuntimeError): + pass + + +def inplace_fix(command: str, input_file: str, *args: str, + zipalign: bool = False, page_align: bool = False, + page_size: Optional[int] = None, internal: bool = False) -> None: + if command not in COMMANDS: + raise Error(f"Unknown command {command}") + exe, script = _script_cmd(command) + ext = os.path.splitext(input_file)[1] + with tempfile.TemporaryDirectory() as tdir: + fixed = os.path.join(tdir, "fixed" + ext) + run_command(exe, script, input_file, fixed, *args, trim=2) + if zipalign: + aligned = os.path.join(tdir, "aligned" + ext) + zac = zipalign_cmd(page_align=page_align, page_size=page_size, internal=internal) + run_command(*zac, fixed, aligned, trim=2) + print(f"[MOVE] {aligned} to {input_file}") + shutil.move(aligned, input_file) + else: + print(f"[MOVE] {fixed} to {input_file}") + shutil.move(fixed, input_file) + + +def zipalign_cmd(page_align: bool = False, page_size: Optional[int] = None, + internal: bool = False) -> Tuple[str, ...]: + """ + Find zipalign command using $PATH or $ANDROID_HOME etc. + + >>> zipalign_cmd() + ('zipalign', '4') + >>> zipalign_cmd(page_align=True) + ('zipalign', '-p', '4') + >>> zipalign_cmd(page_align=True, page_size=16) + ('zipalign', '-P', '16', '4') + >>> cmd = zipalign_cmd(page_align=True, page_size=16, internal=True) + >>> [x.split("/")[-1] for x in cmd] + ['python3', 'zipalign.py', '-P', '16', '4'] + >>> os.environ["PATH"] = "" + >>> for k in SDK_ENV: + ... os.environ[k] = "" + >>> cmd = zipalign_cmd() + >>> [x.split("/")[-1] for x in cmd] + ['python3', 'zipalign.py', '4'] + >>> os.environ["ANDROID_HOME"] = "test/fake-sdk" + >>> zipalign_cmd() + [SKIP BROKEN] 31.0.0 + [FOUND] test/fake-sdk/build-tools/30.0.3/zipalign + ('test/fake-sdk/build-tools/30.0.3/zipalign', '4') + >>> cmd = zipalign_cmd(page_align=True, page_size=16) + [SKIP TOO OLD] 31.0.0 + [SKIP TOO OLD] 30.0.3 + [SKIP TOO OLD] 26.0.2 + >>> [x.split("/")[-1] for x in cmd] + ['python3', 'zipalign.py', '-P', '16', '4'] + >>> os.environ["ANDROID_HOME"] = "test/fake-sdk-2" + >>> zipalign_cmd(page_align=True, page_size=16) + [FOUND] test/fake-sdk-2/build-tools/35.0.0-rc1/zipalign + ('test/fake-sdk-2/build-tools/35.0.0-rc1/zipalign', '-P', '16', '4') + + """ + cmd, *args = _zipalign_cmd(page_align, page_size) + if not internal: + if shutil.which(cmd): + return (cmd, *args) + for k in SDK_ENV: + if home := os.environ.get(k): + tools = os.path.join(home, "build-tools") + if os.path.exists(tools): + for vsn in sorted(os.listdir(tools), key=_vsn, reverse=True): + if page_size and _vsn(vsn) < _vsn(BUILD_TOOLS_WITH_PAGE_SIZE_FROM): + print(f"[SKIP TOO OLD] {vsn}") + continue + for s in BUILD_TOOLS_WITH_BROKEN_ZIPALIGN: + if vsn.startswith(s): + print(f"[SKIP BROKEN] {vsn}") + break + else: + c = os.path.join(tools, vsn, cmd) + if shutil.which(c): + print(f"[FOUND] {c}") + return (c, *args) + return (*_script_cmd(cmd), *args) + + +def _vsn(v: str) -> Tuple[int, ...]: + """ + >>> vs = "31.0.0 32.1.0-rc1 34.0.0-rc3 34.0.0 35.0.0-rc1".split() + >>> for v in sorted(vs, key=_vsn, reverse=True): + ... (_vsn(v), v) + ((35, 0, 0, 0, 1), '35.0.0-rc1') + ((34, 0, 0, 1, 0), '34.0.0') + ((34, 0, 0, 0, 3), '34.0.0-rc3') + ((32, 1, 0, 0, 1), '32.1.0-rc1') + ((31, 0, 0, 1, 0), '31.0.0') + """ + if "-rc" in v: + v = v.replace("-rc", ".0.", 1) + else: + v = v + ".1.0" + return tuple(int(x) if x.isdigit() else -1 for x in v.split(".")) + + +def _script_cmd(command: str) -> Tuple[str, str]: + script_dir = os.path.dirname(__file__) + for cmd in (command, command.replace("-", "_")): + script = os.path.join(script_dir, cmd + ".py") + if os.path.exists(script): + break + else: + raise Error(f"Script for {command} not found") + exe = sys.executable or "python3" + return exe, script + + +def run_command(*args: str, trim: int = 1) -> None: + targs = tuple(os.path.basename(a) for a in args[:trim]) + args[trim:] + print(f"[RUN] {' '.join(targs)}") + try: + subprocess.run(args, check=True) + except subprocess.CalledProcessError as e: + raise Error(f"{args[0]} command failed") from e + except FileNotFoundError as e: + raise Error(f"{args[0]} command not found") from e + + +def main() -> None: + prog = os.path.basename(sys.argv[0]) + usage = (f"{prog} [-h] [--zipalign] [--page-align] [--page-size N] [--internal]\n" + f"{len('usage: ' + prog) * ' '} COMMAND INPUT_FILE [...]") + epilog = f"Commands: {', '.join(COMMANDS)}." + parser = argparse.ArgumentParser(usage=usage, epilog=epilog) + parser.add_argument("--zipalign", action="store_true", + help="run zipalign after COMMAND") + parser.add_argument("--page-align", action="store_true", + help="run zipalign w/ -p option (implies --zipalign)") + parser.add_argument("--page-size", metavar="N", type=int, + help="run zipalign w/ -P N option (implies --page-align)") + parser.add_argument("--internal", action="store_true", + help="use zipalign.py instead of searching $PATH/$ANDROID_HOME/etc.") + parser.add_argument("command", metavar="COMMAND") + parser.add_argument("input_file", metavar="INPUT_FILE") + args, rest = parser.parse_known_args() + try: + inplace_fix(args.command, args.input_file, *rest, + zipalign=bool(args.zipalign or args.page_align or args.page_size), + page_align=bool(args.page_align or args.page_size), + page_size=args.page_size, internal=args.internal) + except Error as e: + print(f"Error: {e}.", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() + +# vim: set tw=80 sw=4 sts=4 et fdm=marker : diff --git a/tools/release/release.sh b/tools/release/release.sh index 60ee3b06e4..e0af115971 100755 --- a/tools/release/release.sh +++ b/tools/release/release.sh @@ -211,10 +211,7 @@ unzip "${targetPath}"/elementx-app-fdroid-apks-unsigned.zip -d "${fdroidTargetPa printf "\n================================================================================\n" printf "Patching the FDroid APKs using inplace-fix.py...\n" -inplaceFixScript="./tmp/inplace-fix.py" -curl -s https://raw.githubusercontent.com/obfusk/reproducible-apk-tools/master/inplace-fix.py --output "${inplaceFixScript}" -curl -s https://raw.githubusercontent.com/obfusk/reproducible-apk-tools/master/fix-pg-map-id.py --output "./tmp/fix-pg-map-id.py" - +inplaceFixScript="./tools/release/inplace-fix.py" python3 "${inplaceFixScript}" --page-size 16 fix-pg-map-id "${fdroidTargetPath}"/app-fdroid-arm64-v8a-release.apk '0000000' python3 "${inplaceFixScript}" --page-size 16 fix-pg-map-id "${fdroidTargetPath}"/app-fdroid-armeabi-v7a-release.apk '0000000' python3 "${inplaceFixScript}" --page-size 16 fix-pg-map-id "${fdroidTargetPath}"/app-fdroid-x86-release.apk '0000000' From bb5ae89b2f3e9a84209e671e648956aad907342e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jul 2024 11:08:16 +0200 Subject: [PATCH 038/115] Introduce ElementThemeApp to avoid code duplication. --- .../io/element/android/x/MainActivity.kt | 16 +------ .../call/impl/ui/ElementCallActivity.kt | 16 +------ .../call/impl/ui/IncomingCallActivity.kt | 16 +------ .../impl/unlock/activity/PinUnlockActivity.kt | 16 +------ libraries/designsystem/build.gradle.kts | 1 + .../designsystem/theme/ElementThemeApp.kt | 46 +++++++++++++++++++ 6 files changed, 55 insertions(+), 56 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index eb3f9450f5..b36f93f1dc 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -26,9 +26,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -38,16 +35,13 @@ import androidx.lifecycle.repeatOnLifecycle import com.bumble.appyx.core.integration.NodeHost import com.bumble.appyx.core.integrationpoint.NodeActivity import com.bumble.appyx.core.plugin.NodeReadyObserver -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.theme.Theme -import io.element.android.compound.theme.isDark -import io.element.android.compound.theme.mapToTheme import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.api.handleSecureFlag import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher import io.element.android.x.di.AppBindings import io.element.android.x.intent.SafeUriHandler @@ -74,14 +68,8 @@ class MainActivity : NodeActivity() { @Composable private fun MainContent(appBindings: AppBindings) { - val theme by remember { - appBindings.preferencesStore().getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) val migrationState = appBindings.migrationEntryPoint().present() - ElementTheme( - darkTheme = theme.isDark() - ) { + ElementThemeApp(appBindings.preferencesStore()) { CompositionLocalProvider( LocalSnackbarDispatcher provides appBindings.snackbarDispatcher(), LocalUriHandler provides SafeUriHandler(this), diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index b8f10f15ed..b0d41efe9e 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -30,15 +30,8 @@ import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.core.content.IntentCompat -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.theme.Theme -import io.element.android.compound.theme.isDark -import io.element.android.compound.theme.mapToTheme import io.element.android.features.call.api.CallType import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings @@ -46,6 +39,7 @@ import io.element.android.features.call.impl.pip.PictureInPicturePresenter import io.element.android.features.call.impl.services.CallForegroundService import io.element.android.features.call.impl.utils.CallIntentDataParser import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.preferences.api.store.AppPreferencesStore import javax.inject.Inject @@ -94,14 +88,8 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { requestAudioFocus() setContent { - val theme by remember { - appPreferencesStore.getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) val pipState = pictureInPicturePresenter.present() - ElementTheme( - darkTheme = theme.isDark() - ) { + ElementThemeApp(appPreferencesStore) { val state = presenter.present() CallScreenView( state = state, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index cbbeaef47c..05d36434cc 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -20,15 +20,8 @@ import android.os.Bundle import android.view.WindowManager import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.core.content.IntentCompat import androidx.lifecycle.lifecycleScope -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.theme.Theme -import io.element.android.compound.theme.isDark -import io.element.android.compound.theme.mapToTheme import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings @@ -36,6 +29,7 @@ import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.utils.ActiveCallManager import io.element.android.features.call.impl.utils.CallState import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn @@ -79,13 +73,7 @@ class IncomingCallActivity : AppCompatActivity() { val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) } if (notificationData != null) { setContent { - val theme by remember { - appPreferencesStore.getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) - ElementTheme( - darkTheme = theme.isDark() - ) { + ElementThemeApp(appPreferencesStore) { IncomingCallScreen( notificationData = notificationData, onAnswer = ::onAnswer, diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt index ce87613c24..7b7b16790f 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt @@ -23,20 +23,14 @@ import androidx.activity.OnBackPressedCallback import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.lifecycle.lifecycleScope -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.theme.Theme -import io.element.android.compound.theme.isDark -import io.element.android.compound.theme.mapToTheme import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter import io.element.android.features.lockscreen.impl.unlock.PinUnlockView import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.launch import javax.inject.Inject @@ -57,13 +51,7 @@ class PinUnlockActivity : AppCompatActivity() { super.onCreate(savedInstanceState) bindings().inject(this) setContent { - val theme by remember { - appPreferencesStore.getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) - ElementTheme( - darkTheme = theme.isDark() - ) { + ElementThemeApp(appPreferencesStore) { val state = presenter.present() PinUnlockView(state = state, isInAppUnlock = false) } diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts index 9934da3e13..e737220961 100644 --- a/libraries/designsystem/build.gradle.kts +++ b/libraries/designsystem/build.gradle.kts @@ -42,6 +42,7 @@ android { implementation(libs.coil.compose) implementation(libs.vanniktech.blurhash) implementation(projects.libraries.architecture) + implementation(projects.libraries.preferences.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt new file mode 100644 index 0000000000..4c20c02e9b --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme +import io.element.android.compound.theme.isDark +import io.element.android.compound.theme.mapToTheme +import io.element.android.libraries.preferences.api.store.AppPreferencesStore + +/** + * Theme to use for all the regular screens of the application. + * Will manage the light / dark theme based on the user preference. + */ +@Composable +fun ElementThemeApp( + appPreferencesStore: AppPreferencesStore, + content: @Composable () -> Unit, +) { + val theme by remember { + appPreferencesStore.getThemeFlow().mapToTheme() + } + .collectAsState(initial = Theme.System) + ElementTheme( + darkTheme = theme.isDark(), + content = content, + ) +} From 573cd5e2981a00a902b90e9c0707ee48db929068 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 09:20:26 +0000 Subject: [PATCH 039/115] Update dependency org.jsoup:jsoup to v1.18.1 (#3171) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 182d3f310e..cfe2b85dc9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -158,7 +158,7 @@ serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-jso kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7" 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.17.2" +jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" From 6d6de1ae3d868dd539de352cbc7f1c098149a398 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jul 2024 16:24:29 +0200 Subject: [PATCH 040/115] Rework. Keep `io.element.android.appconfig.LockScreenConfig` as simple as possible. --- appconfig/build.gradle.kts | 7 --- .../android/appconfig/LockScreenConfig.kt | 58 +++++++------------ .../impl/DefaultLockScreenService.kt | 1 - .../lockscreen/impl/LockScreenConfig.kt | 49 ++++++++++++++++ .../DefaultBiometricUnlockManager.kt | 2 +- .../settings/LockScreenSettingsPresenter.kt | 2 +- .../impl/setup/pin/SetupPinPresenter.kt | 2 +- .../impl/setup/pin/validation/PinValidator.kt | 2 +- .../storage/PreferencesLockScreenStore.kt | 2 +- .../impl/fixtures/LockScreenConfig.kt | 2 +- .../LockScreenSettingsPresenterTest.kt | 2 +- .../impl/setup/pin/SetupPinPresenterTest.kt | 2 +- 12 files changed, 78 insertions(+), 53 deletions(-) create mode 100644 features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt diff --git a/appconfig/build.gradle.kts b/appconfig/build.gradle.kts index a6e66f1462..bb3f16e3b4 100644 --- a/appconfig/build.gradle.kts +++ b/appconfig/build.gradle.kts @@ -15,20 +15,13 @@ */ plugins { id("io.element.android-library") - alias(libs.plugins.anvil) } android { namespace = "io.element.android.appconfig" } -anvil { - generateDaggerFactories.set(true) -} - dependencies { implementation(libs.androidx.annotationjvm) - implementation(libs.dagger) - implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt index 02586bea0d..0312265366 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt @@ -16,44 +16,28 @@ package io.element.android.appconfig -import com.squareup.anvil.annotations.ContributesTo -import dagger.Module -import dagger.Provides -import io.element.android.libraries.di.AppScope import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -/** - * Configuration for the lock screen feature. - * @property isPinMandatory Whether the PIN is mandatory or not. - * @property pinBlacklist Some PINs are forbidden. - * @property pinSize The size of the PIN. - * @property maxPinCodeAttemptsBeforeLogout Number of attempts before the user is logged out. - * @property gracePeriod Time period before locking the app once backgrounded. - * @property isStrongBiometricsEnabled Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported. - * @property isWeakBiometricsEnabled Authentication with weak methods (most face/iris unlock implementations) is supported. - */ -data class LockScreenConfig( - val isPinMandatory: Boolean, - val pinBlacklist: Set, - val pinSize: Int, - val maxPinCodeAttemptsBeforeLogout: Int, - val gracePeriod: Duration, - val isStrongBiometricsEnabled: Boolean, - val isWeakBiometricsEnabled: Boolean, -) - -@ContributesTo(AppScope::class) -@Module -object LockScreenConfigModule { - @Provides - fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig( - isPinMandatory = false, - pinBlacklist = setOf("0000", "1234"), - pinSize = 4, - maxPinCodeAttemptsBeforeLogout = 3, - gracePeriod = 0.seconds, - isStrongBiometricsEnabled = true, - isWeakBiometricsEnabled = true, - ) +object LockScreenConfig { + /** Whether the PIN is mandatory or not. */ + const val IS_PIN_MANDATORY: Boolean = false + + /** Set of forbidden PIN. */ + val PIN_BLACKLIST: Set = setOf("0000", "1234") + + /** The size of the PIN */ + const val PIN_SIZE: Int = 4 + + /** Number of attempts before the user is logged out. */ + const val MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT: Int = 3 + + /** Time period before locking the app once backgrounded. */ + val GRACE_PERIOD: Duration = 0.seconds + + /** Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported. */ + const val IS_STRONG_BIOMETRICS_ENABLED: Boolean = true + + /** Authentication with weak methods (most face/iris unlock implementations) is supported. */ + const val IS_WEAK_BIOMETRICS_ENABLED: Boolean = true } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt index b0b7ab75b3..a72649a125 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt @@ -17,7 +17,6 @@ package io.element.android.features.lockscreen.impl import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.appconfig.LockScreenConfig import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt new file mode 100644 index 0000000000..15eaaaab08 --- /dev/null +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.lockscreen.impl + +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.Provides +import io.element.android.libraries.di.AppScope +import kotlin.time.Duration +import io.element.android.appconfig.LockScreenConfig as AppConfigLockScreenConfig + +data class LockScreenConfig( + val isPinMandatory: Boolean, + val pinBlacklist: Set, + val pinSize: Int, + val maxPinCodeAttemptsBeforeLogout: Int, + val gracePeriod: Duration, + val isStrongBiometricsEnabled: Boolean, + val isWeakBiometricsEnabled: Boolean, +) + +@ContributesTo(AppScope::class) +@Module +object LockScreenConfigModule { + @Provides + fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig( + isPinMandatory = AppConfigLockScreenConfig.IS_PIN_MANDATORY, + pinBlacklist = AppConfigLockScreenConfig.PIN_BLACKLIST, + pinSize = AppConfigLockScreenConfig.PIN_SIZE, + maxPinCodeAttemptsBeforeLogout = AppConfigLockScreenConfig.MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT, + gracePeriod = AppConfigLockScreenConfig.GRACE_PERIOD, + isStrongBiometricsEnabled = AppConfigLockScreenConfig.IS_STRONG_BIOMETRICS_ENABLED, + isWeakBiometricsEnabled = AppConfigLockScreenConfig.IS_WEAK_BIOMETRICS_ENABLED, + ) +} diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt index 95951866f3..68020ba20a 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.res.stringResource import androidx.core.content.getSystemService import androidx.fragment.app.FragmentActivity import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.appconfig.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.R import io.element.android.features.lockscreen.impl.storage.LockScreenStore import io.element.android.libraries.cryptography.api.EncryptionDecryptionService diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt index fbbe0442c1..b0cb97350a 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import io.element.android.appconfig.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.storage.LockScreenStore diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt index ff2bdc56db..ac87d8b2f7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt @@ -22,7 +22,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import io.element.android.appconfig.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.features.lockscreen.impl.setup.pin.validation.PinValidator diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt index 22e6275079..602ff4ccf0 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt @@ -16,7 +16,7 @@ package io.element.android.features.lockscreen.impl.setup.pin.validation -import io.element.android.appconfig.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.pin.model.PinEntry import javax.inject.Inject diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt index b32ef8fd4c..dd0e4c67f6 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt @@ -25,7 +25,7 @@ import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.appconfig.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt index aa575eabd4..cf95830539 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt @@ -16,7 +16,7 @@ package io.element.android.features.lockscreen.impl.fixtures -import io.element.android.appconfig.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt index c3358c471c..46baf620f4 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt @@ -20,7 +20,7 @@ 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.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.biometric.FakeBiometricUnlockManager import io.element.android.features.lockscreen.impl.fixtures.aLockScreenConfig import io.element.android.features.lockscreen.impl.fixtures.aPinCodeManager diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt index 36774d9ef4..10f1a6abc5 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt @@ -20,7 +20,7 @@ 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.LockScreenConfig +import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.fixtures.aLockScreenConfig import io.element.android.features.lockscreen.impl.fixtures.aPinCodeManager import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback From d1a4fd76de070b516ebfa4edd8bdc26e8d98f39c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jul 2024 16:09:09 +0200 Subject: [PATCH 041/115] Some renaming. --- .../io/element/android/appconfig/LockScreenConfig.kt | 4 ++-- .../features/lockscreen/impl/LockScreenConfig.kt | 4 ++-- .../lockscreen/impl/setup/pin/SetupPinPresenter.kt | 2 +- .../lockscreen/impl/setup/pin/SetupPinStateProvider.kt | 2 +- .../features/lockscreen/impl/setup/pin/SetupPinView.kt | 4 ++-- .../impl/setup/pin/validation/PinValidator.kt | 6 +++--- .../impl/setup/pin/validation/SetupPinFailure.kt | 2 +- .../impl/src/main/res/values-be/translations.xml | 4 ++-- .../impl/src/main/res/values-bg/translations.xml | 2 +- .../impl/src/main/res/values-cs/translations.xml | 4 ++-- .../impl/src/main/res/values-de/translations.xml | 4 ++-- .../impl/src/main/res/values-el/translations.xml | 4 ++-- .../impl/src/main/res/values-es/translations.xml | 4 ++-- .../impl/src/main/res/values-et/translations.xml | 4 ++-- .../impl/src/main/res/values-fr/translations.xml | 4 ++-- .../impl/src/main/res/values-hu/translations.xml | 4 ++-- .../impl/src/main/res/values-in/translations.xml | 4 ++-- .../impl/src/main/res/values-it/translations.xml | 4 ++-- .../impl/src/main/res/values-pt/translations.xml | 4 ++-- .../impl/src/main/res/values-ro/translations.xml | 4 ++-- .../impl/src/main/res/values-ru/translations.xml | 4 ++-- .../impl/src/main/res/values-sk/translations.xml | 4 ++-- .../impl/src/main/res/values-sv/translations.xml | 4 ++-- .../impl/src/main/res/values-uk/translations.xml | 4 ++-- .../impl/src/main/res/values-zh-rTW/translations.xml | 4 ++-- .../impl/src/main/res/values-zh/translations.xml | 4 ++-- .../lockscreen/impl/src/main/res/values/localazy.xml | 4 ++-- .../lockscreen/impl/fixtures/LockScreenConfig.kt | 4 ++-- .../lockscreen/impl/setup/pin/SetupPinPresenterTest.kt | 10 +++++----- 29 files changed, 58 insertions(+), 58 deletions(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt index 0312265366..78ed65b070 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt @@ -23,8 +23,8 @@ object LockScreenConfig { /** Whether the PIN is mandatory or not. */ const val IS_PIN_MANDATORY: Boolean = false - /** Set of forbidden PIN. */ - val PIN_BLACKLIST: Set = setOf("0000", "1234") + /** Set of forbidden PIN codes. */ + val FORBIDDEN_PIN_CODES: Set = setOf("0000", "1234") /** The size of the PIN */ const val PIN_SIZE: Int = 4 diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt index 15eaaaab08..b6f901de59 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt @@ -25,7 +25,7 @@ import io.element.android.appconfig.LockScreenConfig as AppConfigLockScreenConfi data class LockScreenConfig( val isPinMandatory: Boolean, - val pinBlacklist: Set, + val forbiddenPinCodes: Set, val pinSize: Int, val maxPinCodeAttemptsBeforeLogout: Int, val gracePeriod: Duration, @@ -39,7 +39,7 @@ object LockScreenConfigModule { @Provides fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig( isPinMandatory = AppConfigLockScreenConfig.IS_PIN_MANDATORY, - pinBlacklist = AppConfigLockScreenConfig.PIN_BLACKLIST, + forbiddenPinCodes = AppConfigLockScreenConfig.FORBIDDEN_PIN_CODES, pinSize = AppConfigLockScreenConfig.PIN_SIZE, maxPinCodeAttemptsBeforeLogout = AppConfigLockScreenConfig.MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT, gracePeriod = AppConfigLockScreenConfig.GRACE_PERIOD, diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt index ac87d8b2f7..2f1fdadc22 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt @@ -97,7 +97,7 @@ class SetupPinPresenter @Inject constructor( choosePinEntry = choosePinEntry.clear() confirmPinEntry = confirmPinEntry.clear() } - is SetupPinFailure.PinBlacklisted -> { + is SetupPinFailure.ForbiddenPin -> { choosePinEntry = choosePinEntry.clear() } null -> Unit diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt index d1820ea75d..1e8274b1b8 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt @@ -39,7 +39,7 @@ open class SetupPinStateProvider : PreviewParameterProvider { ), aSetupPinState( choosePinEntry = PinEntry.createEmpty(4).fillWith("1111"), - creationFailure = SetupPinFailure.PinBlacklisted + creationFailure = SetupPinFailure.ForbiddenPin ), ) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt index 2a0e890470..61d89bf3fd 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt @@ -135,7 +135,7 @@ private fun SetupPinContent( @Composable private fun SetupPinFailure.content(): String { return when (this) { - SetupPinFailure.PinBlacklisted -> stringResource(id = R.string.screen_app_lock_setup_pin_blacklisted_dialog_content) + SetupPinFailure.ForbiddenPin -> stringResource(id = R.string.screen_app_lock_setup_pin_forbidden_dialog_content) SetupPinFailure.PinsDontMatch -> stringResource(id = R.string.screen_app_lock_setup_pin_mismatch_dialog_content) } } @@ -143,7 +143,7 @@ private fun SetupPinFailure.content(): String { @Composable private fun SetupPinFailure.title(): String { return when (this) { - SetupPinFailure.PinBlacklisted -> stringResource(id = R.string.screen_app_lock_setup_pin_blacklisted_dialog_title) + SetupPinFailure.ForbiddenPin -> stringResource(id = R.string.screen_app_lock_setup_pin_forbidden_dialog_title) SetupPinFailure.PinsDontMatch -> stringResource(id = R.string.screen_app_lock_setup_pin_mismatch_dialog_title) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt index 602ff4ccf0..b7f96ecec8 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt @@ -28,9 +28,9 @@ class PinValidator @Inject constructor(private val lockScreenConfig: LockScreenC fun isPinValid(pinEntry: PinEntry): Result { val pinAsText = pinEntry.toText() - val isBlacklisted = lockScreenConfig.pinBlacklist.any { it == pinAsText } - return if (isBlacklisted) { - Result.Invalid(SetupPinFailure.PinBlacklisted) + val isForbidden = lockScreenConfig.forbiddenPinCodes.any { it == pinAsText } + return if (isForbidden) { + Result.Invalid(SetupPinFailure.ForbiddenPin) } else { Result.Valid } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt index 271dcc2f2c..873220eef7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt @@ -17,6 +17,6 @@ package io.element.android.features.lockscreen.impl.setup.pin.validation sealed interface SetupPinFailure { - data object PinBlacklisted : SetupPinFailure + data object ForbiddenPin : SetupPinFailure data object PinsDontMatch : SetupPinFailure } diff --git a/features/lockscreen/impl/src/main/res/values-be/translations.xml b/features/lockscreen/impl/src/main/res/values-be/translations.xml index e38af70014..7667034623 100644 --- a/features/lockscreen/impl/src/main/res/values-be/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-be/translations.xml @@ -14,11 +14,11 @@ "Эканомце час і выкарыстоўвайце %1$s для разблакіроўкі праграмы" "Выберыце PIN-код" "Пацвярджэнне PIN-кода" - "Вы не можаце выбраць гэты PIN-код з меркаванняў бяспекі" - "Выберыце іншы PIN-код" "Заблакіруйце %1$s, каб павялічыць бяспеку вашых чатаў. Абярыце што-небудзь незабыўнае. Калі вы забудзецеся гэты PIN-код, вы выйдзеце з праграмы." + "Вы не можаце выбраць гэты PIN-код з меркаванняў бяспекі" + "Выберыце іншы PIN-код" "Увядзіце адзін і той жа PIN двойчы" "PIN-коды не супадаюць" "Каб працягнуць, вам неабходна паўторна ўвайсці ў сістэму і стварыць новы PIN-код" diff --git a/features/lockscreen/impl/src/main/res/values-bg/translations.xml b/features/lockscreen/impl/src/main/res/values-bg/translations.xml index a1b8ecda84..af8e8a9853 100644 --- a/features/lockscreen/impl/src/main/res/values-bg/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-bg/translations.xml @@ -9,7 +9,7 @@ "Предпочитам да използвам PIN" "Избор на PIN" "Потвърждаване на PIN" - "Избор на различен PIN" + "Избор на различен PIN" "Моля, въведете един и същ PIN два пъти" "PINs не съвпадат" diff --git a/features/lockscreen/impl/src/main/res/values-cs/translations.xml b/features/lockscreen/impl/src/main/res/values-cs/translations.xml index 264b9f08e5..b9984b1dc3 100644 --- a/features/lockscreen/impl/src/main/res/values-cs/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-cs/translations.xml @@ -14,11 +14,11 @@ "Ušetřete si čas a použijte pokaždé %1$s pro odemknutí aplikace" "Zvolte PIN" "Potvrďte PIN" - "Z bezpečnostních důvodů si toto nemůžete zvolit jako svůj PIN kód" - "Zvolte jiný PIN" "Zamkněte %1$s pro zvýšení bezpečnosti vašich konverzací. Vyberte si něco zapamatovatelného. Pokud tento kód PIN zapomenete, budete z aplikace odhlášeni." + "Z bezpečnostních důvodů si toto nemůžete zvolit jako svůj PIN kód" + "Zvolte jiný PIN" "Zadejte stejný PIN dvakrát" "PIN kódy se neshodují." "Abyste mohli pokračovat, budete se muset znovu přihlásit a vytvořit nový PIN" diff --git a/features/lockscreen/impl/src/main/res/values-de/translations.xml b/features/lockscreen/impl/src/main/res/values-de/translations.xml index 53df8aeca0..bf89b3f5e4 100644 --- a/features/lockscreen/impl/src/main/res/values-de/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-de/translations.xml @@ -14,11 +14,11 @@ "Spare dir etwas Zeit und benutze %1$s, um die App zu entsperren" "PIN wählen" "PIN bestätigen" - "Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden." - "Bitte eine andere PIN verwenden." "Sperre %1$s mit einem PIN Code, um den Zugriff auf deine Chats zu beschränken. Wähle etwas Einprägsames. Bei falscher Eingabe wirst du aus der App ausgeloggt." + "Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden." + "Bitte eine andere PIN verwenden." "Bitte gib die gleiche PIN wie zuvor ein." "Die PINs stimmen nicht überein" "Um fortzufahren, musst du dich erneut anmelden und eine neue PIN erstellen" diff --git a/features/lockscreen/impl/src/main/res/values-el/translations.xml b/features/lockscreen/impl/src/main/res/values-el/translations.xml index a7a8e954c1..ebc22971c2 100644 --- a/features/lockscreen/impl/src/main/res/values-el/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-el/translations.xml @@ -14,11 +14,11 @@ "Εξοικονόμησε χρόνο και χρησιμοποίησε %1$s για να ξεκλειδώσεις την εφαρμογή κάθε φορά" "Επέλεξε PIN" "Επιβεβαίωση PIN" - "Δεν μπορείς να το επιλέξεις ως κωδικό PIN για λόγους ασφαλείας" - "Επέλεξε διαφορετικό PIN" "Κλείδωμα του %1$s για να προσθέσεις επιπλέον ασφάλεια στις συνομιλίες σου. Επέλεξε κάτι αξιομνημόνευτο. Εάν ξεχάσεις αυτό το PIN, θα αποσυνδεθείς από την εφαρμογή." + "Δεν μπορείς να το επιλέξεις ως κωδικό PIN για λόγους ασφαλείας" + "Επέλεξε διαφορετικό PIN" "Παρακαλώ εισήγαγε το ίδιο PIN δύο φορές" "Τα PIN δεν ταιριάζουν" "Θα χρειαστεί να συνδεθείς ξανά και να δημιουργήσεις ένα νέο PIN για να προχωρήσεις" diff --git a/features/lockscreen/impl/src/main/res/values-es/translations.xml b/features/lockscreen/impl/src/main/res/values-es/translations.xml index c034d14fb0..1ab64c8f3b 100644 --- a/features/lockscreen/impl/src/main/res/values-es/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-es/translations.xml @@ -14,11 +14,11 @@ "Ahorra algo de tiempo y usa %1$s para desbloquear la aplicación cada vez" "Elegir PIN" "Confirmar PIN" - "No puedes usar este código PIN por motivos de seguridad" - "Elige un PIN diferente" "Añade un bloqueo a %1$s para añadir seguridad adicional a tus chats. Elige algo que puedas recordar. Si olvidas este PIN, se cerrará la sesión de la aplicación." + "No puedes usar este código PIN por motivos de seguridad" + "Elige un PIN diferente" "Por favor ingresa el mismo PIN dos veces" "Los PINs no coinciden" "Tendrás que volver a iniciar sesión y crear un nuevo PIN para continuar" diff --git a/features/lockscreen/impl/src/main/res/values-et/translations.xml b/features/lockscreen/impl/src/main/res/values-et/translations.xml index fd2632b884..25c2300510 100644 --- a/features/lockscreen/impl/src/main/res/values-et/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-et/translations.xml @@ -14,11 +14,11 @@ "Säästa aega ja kasuta alati %1$s rakenduse lukustuse eemaldamiseks" "Vali PIN-kood" "Korda PIN-koodi" - "Turvakaalutlustel sa ei saa sellist PIN-koodi kasutada" - "Kasuta mõnda teist PIN-koodi" "Lisamaks oma %1$s vestlustele turvalisust ja privaatsust, lukusta oma nutiseade. Vali midagi, mis hästi meelde jääb. Kui unustad selle PIN-koodi, siis turvakaalutlustel logitakse sind rakendusest välja." + "Turvakaalutlustel sa ei saa sellist PIN-koodi kasutada" + "Kasuta mõnda teist PIN-koodi" "Palun sisesta sama PIN-kood kaks korda" "PIN-koodid ei klapi omavahel" "Jätkamaks pead uuesti sisse logima ja looma uue PIN-koodi" diff --git a/features/lockscreen/impl/src/main/res/values-fr/translations.xml b/features/lockscreen/impl/src/main/res/values-fr/translations.xml index e086a88e61..a8b3757022 100644 --- a/features/lockscreen/impl/src/main/res/values-fr/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-fr/translations.xml @@ -14,11 +14,11 @@ "Gagnez du temps en utilisant %1$s pour déverrouiller l’application à chaque fois." "Choisissez un code PIN" "Confirmer le code PIN" - "Vous ne pouvez pas choisir ce code PIN pour des raisons de sécurité" - "Choisissez un code PIN différent" "Verrouillez %1$s pour ajouter une sécurité supplémentaire à vos discussions. Choisissez un code facile à retenir. Si vous oubliez le code PIN, vous serez déconnecté." + "Vous ne pouvez pas choisir ce code PIN pour des raisons de sécurité" + "Choisissez un code PIN différent" "Veuillez saisir le même code PIN deux fois" "Les codes PIN ne correspondent pas" "Pour continuer, vous devrez vous connecter à nouveau et créer un nouveau code PIN." diff --git a/features/lockscreen/impl/src/main/res/values-hu/translations.xml b/features/lockscreen/impl/src/main/res/values-hu/translations.xml index fba0444c15..2df43b3fae 100644 --- a/features/lockscreen/impl/src/main/res/values-hu/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-hu/translations.xml @@ -14,11 +14,11 @@ "Spóroljon meg némi időt, és használja a %1$st az alkalmazás feloldásához" "PIN-kód kiválasztása" "PIN-kód megerősítése" - "Ezt biztonsági okokból nem választhatja PIN-kódként" - "Válasszon egy másik PIN-kódot" "Az %1$s zárolása a csevegései nagyobb biztonsága érdekében. Válasszon valami megjegyezhetőt. Ha elfelejti a PIN-kódot, akkor ki lesz jelentkeztetve az alkalmazásból." + "Ezt biztonsági okokból nem választhatja PIN-kódként" + "Válasszon egy másik PIN-kódot" "Adja meg a PIN-kódját kétszer" "A PIN-kódok nem egyeznek" "A folytatáshoz újra be kell jelentkeznie, és létre kell hoznia egy új PIN-kódot" diff --git a/features/lockscreen/impl/src/main/res/values-in/translations.xml b/features/lockscreen/impl/src/main/res/values-in/translations.xml index 9a591be4c6..d6c7c3bc94 100644 --- a/features/lockscreen/impl/src/main/res/values-in/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-in/translations.xml @@ -14,11 +14,11 @@ "Hemat waktu Anda dan gunakan %1$s untuk membuka kunci aplikasi setiap kalinya" "Pilih PIN" "Konfirmasi PIN" - "Anda tidak dapat memilih PIN ini demi keamanan" - "Pilih PIN yang lain" "Kunci %1$s untuk menambahkan keamanan tambahan pada percakapan Anda. Pilih sesuatu yang mudah untuk diingat. Jika Anda lupa PIN ini, Anda akan dikeluarkan dari aplikasi." + "Anda tidak dapat memilih PIN ini demi keamanan" + "Pilih PIN yang lain" "Silakan masukkan PIN yang sama dua kali" "PIN tidak cocok" "Anda harus masuk ulang dan membuat PIN baru untuk melanjutkan" diff --git a/features/lockscreen/impl/src/main/res/values-it/translations.xml b/features/lockscreen/impl/src/main/res/values-it/translations.xml index 3c9cfbe45d..cdb48f8f63 100644 --- a/features/lockscreen/impl/src/main/res/values-it/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-it/translations.xml @@ -14,11 +14,11 @@ "Risparmia un po\' di tempo e usa %1$s per sbloccare l\'app ogni volta" "Scegli il PIN" "Conferma il PIN" - "Non puoi scegliere questo codice PIN per motivi di sicurezza" - "Scegli un PIN diverso" "Blocca %1$s per aggiungere ulteriore sicurezza alle tue conversazioni. Scegli qualcosa facile da ricordare. Se dimentichi questo PIN, verrai disconnesso dall\'app." + "Non puoi scegliere questo codice PIN per motivi di sicurezza" + "Scegli un PIN diverso" "Inserisci lo stesso PIN due volte" "I PIN non corrispondono" "Dovrai effettuare nuovamente l\'accesso e creare un nuovo PIN per procedere" diff --git a/features/lockscreen/impl/src/main/res/values-pt/translations.xml b/features/lockscreen/impl/src/main/res/values-pt/translations.xml index ab87d61955..b767f0266a 100644 --- a/features/lockscreen/impl/src/main/res/values-pt/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-pt/translations.xml @@ -14,11 +14,11 @@ "Poupa tempo e utiliza %1$s para desbloquear a aplicação" "Escolher PIN" "Confirmar PIN" - "Não podes escolher este código PIN por razões de segurança" - "Escolhe um PIN diferente" "Bloqueia a %1$s para dar mais segurança às tuas conversas. Escolhe algo memorável. Se te esqueceres deste PIN, a tua sessão será terminada." + "Não podes escolher este código PIN por razões de segurança" + "Escolhe um PIN diferente" "Insere o mesmo PIN duas vezes" "Os PINs não coincidem" "Terás de voltar a iniciar sessão e criar um novo PIN para continuar" diff --git a/features/lockscreen/impl/src/main/res/values-ro/translations.xml b/features/lockscreen/impl/src/main/res/values-ro/translations.xml index 06298d6ad3..69ecc93689 100644 --- a/features/lockscreen/impl/src/main/res/values-ro/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-ro/translations.xml @@ -14,11 +14,11 @@ "Economisiți timp și utilizați %1$s pentru a debloca aplicația de fiecare dată." "Alegeți codul PIN" "Confirmare PIN" - "Nu puteți alege acest cod PIN din motive de securitate" - "Alegeți un alt cod PIN" "Blocați %1$s pentru a adăuga un plus de securitate la conversațiile dvs. Alegeți ceva memorabil. Dacă uitați acest PIN, veți fi deconectat din aplicație." + "Nu puteți alege acest cod PIN din motive de securitate" + "Alegeți un alt cod PIN" "Vă rugăm să introduceți același cod PIN de două ori" "Codurile PIN nu corespund" "Va trebui să vă reconectați și să creați un cod PIN nou pentru a continua" diff --git a/features/lockscreen/impl/src/main/res/values-ru/translations.xml b/features/lockscreen/impl/src/main/res/values-ru/translations.xml index 07d4e4d3a6..dd9deb0ee1 100644 --- a/features/lockscreen/impl/src/main/res/values-ru/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-ru/translations.xml @@ -14,11 +14,11 @@ "Сэкономьте время и используйте %1$s для разблокировки приложения" "Выберите PIN-код" "Подтвердите PIN-код" - "Из соображений безопасности вы не можешь выбрать это в качестве PIN-кода" - "Выберите другой PIN-код" "Заблокируйте %1$s, чтобы повысить безопасность ваших чатов. Введите что-нибудь незабываемое. Если вы забудете этот PIN-код, вы выйдете из приложения." + "Из соображений безопасности вы не можешь выбрать это в качестве PIN-кода" + "Выберите другой PIN-код" "Повторите PIN-код" "PIN-коды не совпадают" "Чтобы продолжить, вам необходимо повторно войти в систему и создать новый PIN-код" diff --git a/features/lockscreen/impl/src/main/res/values-sk/translations.xml b/features/lockscreen/impl/src/main/res/values-sk/translations.xml index 7d78fa9fe0..7333d2a53f 100644 --- a/features/lockscreen/impl/src/main/res/values-sk/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sk/translations.xml @@ -14,11 +14,11 @@ "Ušetrite si čas a použite zakaždým %1$s na odomknutie aplikácie" "Vyberte PIN" "Potvrdiť PIN" - "Z bezpečnostných dôvodov si nemôžete toto zvoliť ako svoj PIN kód." - "Vyberte iný PIN" "Uzamknite %1$s, aby ste zvýšili bezpečnosť svojich konverzácií. Vyberte si niečo zapamätateľné. Ak tento kód PIN zabudnete, budete z aplikácie odhlásení." + "Z bezpečnostných dôvodov si nemôžete toto zvoliť ako svoj PIN kód." + "Vyberte iný PIN" "Zadajte prosím ten istý PIN dvakrát" "PIN kódy sa nezhodujú" "Ak chcete pokračovať, musíte sa znovu prihlásiť a vytvoriť nový PIN kód." diff --git a/features/lockscreen/impl/src/main/res/values-sv/translations.xml b/features/lockscreen/impl/src/main/res/values-sv/translations.xml index 14a2faffd9..d022e6b953 100644 --- a/features/lockscreen/impl/src/main/res/values-sv/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sv/translations.xml @@ -14,11 +14,11 @@ "Bespara dig själv lite tid och använd %1$s för att låsa upp appen varje gång" "Välj PIN-kod" "Bekräfta PIN-kod" - "Du kan inte välja detta som din PIN-kod av säkerhetsskäl" - "Välj en annan PIN-kod" "Lås %1$s för att lägga till extra säkerhet i dina chattar. Välj något minnesvärt. Om du glömmer den här PIN-koden loggas du ut från appen." + "Du kan inte välja detta som din PIN-kod av säkerhetsskäl" + "Välj en annan PIN-kod" "Ange samma PIN-kod två gånger" "PIN-koder matchar inte" "Du måste logga in igen och skapa en ny PIN-kod för att fortsätta" diff --git a/features/lockscreen/impl/src/main/res/values-uk/translations.xml b/features/lockscreen/impl/src/main/res/values-uk/translations.xml index c56d4e3d70..e97b0211ae 100644 --- a/features/lockscreen/impl/src/main/res/values-uk/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-uk/translations.xml @@ -14,11 +14,11 @@ "Заощаджуйте час і використовуйте %1$s для розблокування застосунку щоразу" "Виберіть PIN-код" "Підтвердити PIN-код" - "Ви не можете вибрати його як свій PIN-код з міркувань безпеки" - "Виберіть інший PIN-код" "Заблокуйте %1$s, щоб додати додаткову безпеку вашим чатам. Виберіть щось, що запам\'ятовується. Але якщо ви забудете PIN-код, ви вийдете з застосунку." + "Ви не можете вибрати його як свій PIN-код з міркувань безпеки" + "Виберіть інший PIN-код" "Будь ласка, введіть один і той самий PIN-код двічі" "PIN-коди не збігаються" "Щоб продовжити, вам потрібно буде повторно увійти та створити новий PIN-код" diff --git a/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml b/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml index 757604c4f4..48fc86718c 100644 --- a/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml @@ -13,11 +13,11 @@ "我想使用 PIN 碼" "選擇 PIN 碼" "確認 PIN 碼" - "基於安全性的考量,您選的 PIN 碼無法使用" - "選擇不一樣的 PIN 碼" "將 %1$s 上鎖,為你的聊天室添加一層防護。 請選擇好記憶的數字。如果忘記 PIN 碼,您會被登出。" + "基於安全性的考量,您選的 PIN 碼無法使用" + "選擇不一樣的 PIN 碼" "請輸入相同的 PIN 碼兩次" "PIN 碼不一樣" "您需要重新登入並建立新的 PIN 碼才能繼續" diff --git a/features/lockscreen/impl/src/main/res/values-zh/translations.xml b/features/lockscreen/impl/src/main/res/values-zh/translations.xml index 9d19208c6a..6fc69042a9 100644 --- a/features/lockscreen/impl/src/main/res/values-zh/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-zh/translations.xml @@ -14,11 +14,11 @@ "节省时间,用 %1$s 来解锁应用程序" "选择 PIN 码" "确认 PIN 码" - "出于安全原因,您不能选择这个 PIN 码" - "选择不同的 PIN 码" "锁定 %1$s 以为聊天增加安全性。 选择好记的 PIN 码。如果忘掉了这个 PIN 码,就不得不登出应用。" + "出于安全原因,您不能选择这个 PIN 码" + "选择不同的 PIN 码" "请输入两次相同的 PIN 码" "PIN 码不匹配" "您需要重新登录并创建新的 PIN 才能继续" diff --git a/features/lockscreen/impl/src/main/res/values/localazy.xml b/features/lockscreen/impl/src/main/res/values/localazy.xml index 8f0a3def88..0836efa99b 100644 --- a/features/lockscreen/impl/src/main/res/values/localazy.xml +++ b/features/lockscreen/impl/src/main/res/values/localazy.xml @@ -14,11 +14,11 @@ "Save yourself some time and use %1$s to unlock the app each time" "Choose PIN" "Confirm PIN" - "You cannot choose this as your PIN code for security reasons" - "Choose a different PIN" "Lock %1$s to add extra security to your chats. Choose something memorable. If you forget this PIN, you will be logged out of the app." + "You cannot choose this as your PIN code for security reasons" + "Choose a different PIN" "Please enter the same PIN twice" "PINs don\'t match" "You’ll need to re-login and create a new PIN to proceed" diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt index cf95830539..692e565b8b 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt @@ -22,7 +22,7 @@ import kotlin.time.Duration.Companion.seconds internal fun aLockScreenConfig( isPinMandatory: Boolean = false, - pinBlacklist: Set = emptySet(), + forbiddenPinCodes: Set = emptySet(), pinSize: Int = 4, maxPinCodeAttemptsBeforeLogout: Int = 3, gracePeriod: Duration = 3.seconds, @@ -31,7 +31,7 @@ internal fun aLockScreenConfig( ): LockScreenConfig { return LockScreenConfig( isPinMandatory = isPinMandatory, - pinBlacklist = pinBlacklist, + forbiddenPinCodes = forbiddenPinCodes, pinSize = pinSize, maxPinCodeAttemptsBeforeLogout = maxPinCodeAttemptsBeforeLogout, gracePeriod = gracePeriod, diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt index 10f1a6abc5..ecbeffc35c 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class SetupPinPresenterTest { - private val blacklistedPin = "1234" + private val forbiddenPin = "1234" private val halfCompletePin = "12" private val completePin = "1235" private val mismatchedPin = "1236" @@ -66,11 +66,11 @@ class SetupPinPresenterTest { state.confirmPinEntry.assertEmpty() assertThat(state.setupPinFailure).isNull() assertThat(state.isConfirmationStep).isFalse() - state.onPinEntryChanged(blacklistedPin) + state.onPinEntryChanged(forbiddenPin) } awaitLastSequentialItem().also { state -> - state.choosePinEntry.assertText(blacklistedPin) - assertThat(state.setupPinFailure).isEqualTo(SetupPinFailure.PinBlacklisted) + state.choosePinEntry.assertText(forbiddenPin) + assertThat(state.setupPinFailure).isEqualTo(SetupPinFailure.ForbiddenPin) state.eventSink(SetupPinEvents.ClearFailure) } awaitLastSequentialItem().also { state -> @@ -122,7 +122,7 @@ class SetupPinPresenterTest { private fun createSetupPinPresenter( callback: PinCodeManager.Callback, lockScreenConfig: LockScreenConfig = aLockScreenConfig( - pinBlacklist = setOf(blacklistedPin) + forbiddenPinCodes = setOf(forbiddenPin) ), ): SetupPinPresenter { val pinCodeManager = aPinCodeManager() From 2efa7fea9629ca434b8a9733c86e8f73772033fe Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jul 2024 16:22:07 +0200 Subject: [PATCH 042/115] Change PIN grace period to 2 minutes. --- .../kotlin/io/element/android/appconfig/LockScreenConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt index 78ed65b070..8e9b7b653c 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt @@ -17,7 +17,7 @@ package io.element.android.appconfig import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.minutes object LockScreenConfig { /** Whether the PIN is mandatory or not. */ @@ -33,7 +33,7 @@ object LockScreenConfig { const val MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT: Int = 3 /** Time period before locking the app once backgrounded. */ - val GRACE_PERIOD: Duration = 0.seconds + val GRACE_PERIOD: Duration = 2.minutes /** Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported. */ const val IS_STRONG_BIOMETRICS_ENABLED: Boolean = true From 85428fbcfb1e6777ff49da39f46755ae689beb98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jul 2024 16:26:09 +0200 Subject: [PATCH 043/115] No need to be a member of the class. --- .../features/lockscreen/impl/DefaultLockScreenService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt index a72649a125..55b53f054c 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt @@ -54,7 +54,7 @@ class DefaultLockScreenService @Inject constructor( private val coroutineScope: CoroutineScope, private val sessionObserver: SessionObserver, private val appForegroundStateService: AppForegroundStateService, - private val biometricUnlockManager: BiometricUnlockManager, + biometricUnlockManager: BiometricUnlockManager, ) : LockScreenService { private val _lockState = MutableStateFlow(LockScreenLockState.Unlocked) override val lockState: StateFlow = _lockState From eb8d7e21660482f7d78615c81512c95a406a5fc8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jul 2024 16:27:29 +0200 Subject: [PATCH 044/115] Some renaming. --- .../features/lockscreen/impl/setup/pin/SetupPinPresenter.kt | 4 ++-- .../lockscreen/impl/setup/pin/SetupPinStateProvider.kt | 2 +- .../features/lockscreen/impl/setup/pin/SetupPinView.kt | 4 ++-- .../lockscreen/impl/setup/pin/validation/SetupPinFailure.kt | 2 +- .../lockscreen/impl/setup/pin/SetupPinPresenterTest.kt | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt index 2f1fdadc22..eec7e6a19b 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt @@ -76,7 +76,7 @@ class SetupPinPresenter @Inject constructor( if (confirmPinEntry == choosePinEntry) { pinCodeManager.createPinCode(confirmPinEntry.toText()) } else { - setupPinFailure = SetupPinFailure.PinsDontMatch + setupPinFailure = SetupPinFailure.PinsDoNotMatch } } } @@ -93,7 +93,7 @@ class SetupPinPresenter @Inject constructor( } SetupPinEvents.ClearFailure -> { when (setupPinFailure) { - is SetupPinFailure.PinsDontMatch -> { + is SetupPinFailure.PinsDoNotMatch -> { choosePinEntry = choosePinEntry.clear() confirmPinEntry = confirmPinEntry.clear() } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt index 1e8274b1b8..f8d35ec630 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt @@ -35,7 +35,7 @@ open class SetupPinStateProvider : PreviewParameterProvider { choosePinEntry = PinEntry.createEmpty(4).fillWith("1789"), confirmPinEntry = PinEntry.createEmpty(4).fillWith("1788"), isConfirmationStep = true, - creationFailure = SetupPinFailure.PinsDontMatch + creationFailure = SetupPinFailure.PinsDoNotMatch ), aSetupPinState( choosePinEntry = PinEntry.createEmpty(4).fillWith("1111"), diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt index 61d89bf3fd..ad0b375812 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt @@ -136,7 +136,7 @@ private fun SetupPinContent( private fun SetupPinFailure.content(): String { return when (this) { SetupPinFailure.ForbiddenPin -> stringResource(id = R.string.screen_app_lock_setup_pin_forbidden_dialog_content) - SetupPinFailure.PinsDontMatch -> stringResource(id = R.string.screen_app_lock_setup_pin_mismatch_dialog_content) + SetupPinFailure.PinsDoNotMatch -> stringResource(id = R.string.screen_app_lock_setup_pin_mismatch_dialog_content) } } @@ -144,7 +144,7 @@ private fun SetupPinFailure.content(): String { private fun SetupPinFailure.title(): String { return when (this) { SetupPinFailure.ForbiddenPin -> stringResource(id = R.string.screen_app_lock_setup_pin_forbidden_dialog_title) - SetupPinFailure.PinsDontMatch -> stringResource(id = R.string.screen_app_lock_setup_pin_mismatch_dialog_title) + SetupPinFailure.PinsDoNotMatch -> stringResource(id = R.string.screen_app_lock_setup_pin_mismatch_dialog_title) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt index 873220eef7..66f3cd0731 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt @@ -18,5 +18,5 @@ package io.element.android.features.lockscreen.impl.setup.pin.validation sealed interface SetupPinFailure { data object ForbiddenPin : SetupPinFailure - data object PinsDontMatch : SetupPinFailure + data object PinsDoNotMatch : SetupPinFailure } diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt index ecbeffc35c..3261aa9e71 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt @@ -89,7 +89,7 @@ class SetupPinPresenterTest { awaitLastSequentialItem().also { state -> state.choosePinEntry.assertText(completePin) state.confirmPinEntry.assertText(mismatchedPin) - assertThat(state.setupPinFailure).isEqualTo(SetupPinFailure.PinsDontMatch) + assertThat(state.setupPinFailure).isEqualTo(SetupPinFailure.PinsDoNotMatch) state.eventSink(SetupPinEvents.ClearFailure) } awaitLastSequentialItem().also { state -> From f73b48032899abd639b7f869f370abc04e076faf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jul 2024 17:01:04 +0200 Subject: [PATCH 045/115] Doc: add missing period --- .../kotlin/io/element/android/appconfig/LockScreenConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt index 8e9b7b653c..0cfe2bd7a5 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt @@ -26,7 +26,7 @@ object LockScreenConfig { /** Set of forbidden PIN codes. */ val FORBIDDEN_PIN_CODES: Set = setOf("0000", "1234") - /** The size of the PIN */ + /** The size of the PIN. */ const val PIN_SIZE: Int = 4 /** Number of attempts before the user is logged out. */ From 0be7058416af8fda694acdcb67242e5bb23c9ed4 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 10 Jul 2024 18:28:46 +0200 Subject: [PATCH 046/115] Unify the way we decide whether a room is a DM or a group room (#3100) * Add centralised 'room is DM' check Also add extension functions for `MatrixRoom` and `MatrixRoomInfo`. * Use the centralised method and extension functions through the app, including: - Room list. - Room details screen. - Invites. - Notifications. Replace most `isDirect` usages with `isDm`. * Update screenshots --------- Co-authored-by: ElementBot --- .../AcceptDeclineInviteStateProvider.kt | 4 +- .../invite/api/response/InviteData.kt | 2 +- .../impl/response/AcceptDeclineInviteView.kt | 4 +- .../AcceptDeclineInvitePresenterTest.kt | 4 +- .../joinroom/impl/JoinRoomPresenter.kt | 9 +-- .../features/joinroom/impl/JoinRoomState.kt | 2 +- .../joinroom/impl/JoinRoomStateProvider.kt | 11 +++- .../joinroom/impl/JoinRoomPresenterTest.kt | 6 +- .../impl/DefaultLeaveRoomPresenter.kt | 1 + .../impl/DefaultLeaveRoomPresenterTest.kt | 2 +- .../messages/impl/MessagesPresenter.kt | 3 +- .../MessageComposerPresenter.kt | 1 + .../impl/timeline/TimelinePresenter.kt | 1 + .../MessageComposerPresenterTest.kt | 4 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 1 + .../DefaultRoomMembersModerationPresenter.kt | 4 +- ...faultRoomMembersModerationPresenterTest.kt | 6 +- .../roomlist/impl/RoomListPresenter.kt | 2 +- .../impl/components/RoomSummaryRow.kt | 8 +-- .../datasource/RoomListRoomSummaryFactory.kt | 2 +- .../impl/model/RoomListRoomSummaryProvider.kt | 2 +- .../api/notification/NotificationData.kt | 1 + .../libraries/matrix/api/room/MatrixRoom.kt | 3 - .../matrix/api/room/RoomIsDmCheck.kt | 38 ++++++++++++ .../api/room/recent/RecentDirectRoom.kt | 1 + .../matrix/api/room/RoomIsDmCheckTest.kt | 62 +++++++++++++++++++ .../impl/notification/NotificationMapper.kt | 8 ++- .../matrix/impl/roomlist/RoomListFilter.kt | 4 +- .../roomlist/RoomSummaryDetailsFactory.kt | 3 +- .../matrix/impl/timeline/RustTimeline.kt | 1 + .../impl/roomlist/RoomListFilterTest.kt | 10 +-- .../test/notification/NotificationData.kt | 1 + .../matrix/test/room/FakeMatrixRoom.kt | 1 - .../CallNotificationEventResolver.kt | 2 +- .../DefaultNotifiableEventResolver.kt | 10 +-- .../NotificationBroadcastReceiverHandler.kt | 3 +- .../notifications/NotificationDataFactory.kt | 12 ++-- .../impl/notifications/RoomEventGroupInfo.kt | 2 +- .../notifications/RoomGroupMessageCreator.kt | 5 +- .../factories/NotificationCreator.kt | 2 +- .../model/NotifiableMessageEvent.kt | 2 +- .../DefaultNotifiableEventResolverTest.kt | 5 +- .../DefaultRoomGroupMessageCreatorTest.kt | 4 +- .../DefaultNotificationCreatorTest.kt | 2 - .../fixtures/NotifiableEventFixture.kt | 1 - ...s.joinroom.impl_JoinRoomView_Day_10_en.png | 3 + ...joinroom.impl_JoinRoomView_Night_10_en.png | 3 + 47 files changed, 195 insertions(+), 73 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt create mode 100644 libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt create mode 100644 tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Day_10_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Night_10_en.png diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInviteStateProvider.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInviteStateProvider.kt index af2c37e68f..ec262a7506 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInviteStateProvider.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInviteStateProvider.kt @@ -27,13 +27,13 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider Unit, modifier: Modifier = Modifier ) { - val contentResource = if (invite.isDirect) { + val contentResource = if (invite.isDm) { R.string.screen_invites_decline_direct_chat_message } else { R.string.screen_invites_decline_chat_message } - val titleResource = if (invite.isDirect) { + val titleResource = if (invite.isDm) { R.string.screen_invites_decline_direct_chat_title } else { R.string.screen_invites_decline_chat_title diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt index 672c5b58ee..faa1388fea 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt @@ -260,12 +260,12 @@ class AcceptDeclineInvitePresenterTest { private fun anInviteData( roomId: RoomId = A_ROOM_ID, name: String = A_ROOM_NAME, - isDirect: Boolean = false + isDm: Boolean = false ): InviteData { return InviteData( roomId = roomId, roomName = name, - isDirect = isDirect + isDm = isDm ) } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 5f1efe827e..bba2a82a34 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.room.preview.RoomPreview import io.element.android.libraries.matrix.ui.model.toInviteSender @@ -173,7 +174,7 @@ private fun RoomPreview.toContentState(): ContentState { topic = topic, alias = canonicalAlias, numberOfMembers = numberOfJoinedMembers, - isDirect = false, + isDm = false, roomType = roomType, roomAvatarUrl = avatarUrl, joinAuthorisationStatus = when { @@ -194,7 +195,7 @@ internal fun RoomDescription.toContentState(): ContentState { topic = topic, alias = alias, numberOfMembers = numberOfMembers, - isDirect = false, + isDm = false, roomType = RoomType.Room, roomAvatarUrl = avatarUrl, joinAuthorisationStatus = when (joinRule) { @@ -213,7 +214,7 @@ internal fun MatrixRoomInfo.toContentState(): ContentState { topic = topic, alias = canonicalAlias, numberOfMembers = activeMembersCount, - isDirect = isDirect, + isDm = isDm, roomType = if (isSpace) RoomType.Space else RoomType.Room, roomAvatarUrl = avatarUrl, joinAuthorisationStatus = when { @@ -233,7 +234,7 @@ internal fun ContentState.toInviteData(): InviteData? { roomId = roomId, // Note: name should not be null at this point, but use Id just in case... roomName = name ?: roomId.value, - isDirect = isDirect + isDm = isDm ) else -> null } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index 905d5dd2d1..ab66d0d80c 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -53,7 +53,7 @@ sealed interface ContentState { val topic: String?, val alias: RoomAlias?, val numberOfMembers: Long?, - val isDirect: Boolean, + val isDm: Boolean, val roomType: RoomType, val roomAvatarUrl: String?, val joinAuthorisationStatus: JoinAuthorisationStatus, diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt index f897026600..8da0573b51 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.room.RoomType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.ui.model.InviteSender open class JoinRoomStateProvider : PreviewParameterProvider { @@ -84,6 +85,12 @@ open class JoinRoomStateProvider : PreviewParameterProvider { roomType = RoomType.Space, ) ), + aJoinRoomState( + contentState = aLoadedContentState( + name = "A DM", + isDm = true, + ) + ), ) } @@ -106,7 +113,7 @@ fun aLoadedContentState( alias: RoomAlias? = RoomAlias("#exa:matrix.org"), topic: String? = "Element X is a secure, private and decentralized messenger.", numberOfMembers: Long? = null, - isDirect: Boolean = false, + isDm: Boolean = false, roomType: RoomType = RoomType.Room, roomAvatarUrl: String? = null, joinAuthorisationStatus: JoinAuthorisationStatus = JoinAuthorisationStatus.Unknown @@ -116,7 +123,7 @@ fun aLoadedContentState( alias = alias, topic = topic, numberOfMembers = numberOfMembers, - isDirect = isDirect, + isDm = isDm, roomType = roomType, roomAvatarUrl = roomAvatarUrl, joinAuthorisationStatus = joinAuthorisationStatus diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 2a054b820f..7a6e08244c 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -93,7 +93,7 @@ class JoinRoomPresenterTest { assertThat(contentState.topic).isEqualTo(roomInfo.topic) assertThat(contentState.alias).isEqualTo(roomInfo.canonicalAlias) assertThat(contentState.numberOfMembers).isEqualTo(roomInfo.activeMembersCount) - assertThat(contentState.isDirect).isEqualTo(roomInfo.isDirect) + assertThat(contentState.isDm).isEqualTo(roomInfo.isDirect) assertThat(contentState.roomAvatarUrl).isEqualTo(roomInfo.avatarUrl) } } @@ -283,7 +283,7 @@ class JoinRoomPresenterTest { assertThat(contentState.topic).isEqualTo(roomDescription.topic) assertThat(contentState.alias).isEqualTo(roomDescription.alias) assertThat(contentState.numberOfMembers).isEqualTo(roomDescription.numberOfMembers) - assertThat(contentState.isDirect).isFalse() + assertThat(contentState.isDm).isFalse() assertThat(contentState.roomAvatarUrl).isEqualTo(roomDescription.avatarUrl) } } @@ -398,7 +398,7 @@ class JoinRoomPresenterTest { topic = "Room topic", alias = RoomAlias("#alias:matrix.org"), numberOfMembers = 2, - isDirect = false, + isDm = false, roomType = RoomType.Room, roomAvatarUrl = "avatarUrl", joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenter.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenter.kt index c949a5f446..4b121073a0 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenter.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenter.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.room.isDm import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt index 9962a3bd32..49b44851de 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt @@ -119,7 +119,7 @@ class DefaultLeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true, isOneToOne = true), + result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true), ) } ) 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 fc8a8c3975..b13a95d4f2 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 @@ -72,6 +72,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.room.canCall @@ -162,7 +163,7 @@ class MessagesPresenter @AssistedInject constructor( var showReinvitePrompt by remember { mutableStateOf(false) } LaunchedEffect(hasDismissedInviteDialog, composerState.textEditorState.hasFocus(), syncUpdateFlow.value) { withContext(dispatchers.io) { - showReinvitePrompt = !hasDismissedInviteDialog && composerState.textEditorState.hasFocus() && room.isDirect && room.activeMemberCount == 1L + showReinvitePrompt = !hasDismissedInviteDialog && composerState.textEditorState.hasFocus() && room.isDm && room.activeMemberCount == 1L } } val networkConnectionStatus by networkMonitor.connectivity.collectAsState() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 9a80985bd0..85eacc38ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -57,6 +57,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.mediapickers.api.PickerProvider diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 81ef19e587..f5903f036b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index c2d5966fd0..667d43b56e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -839,7 +839,6 @@ class MessageComposerPresenterTest { val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) val room = FakeMatrixRoom( isDirect = false, - isOneToOne = false, ).apply { givenRoomMembersState( MatrixRoomMembersState.Ready( @@ -904,7 +903,8 @@ class MessageComposerPresenterTest { val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) val room = FakeMatrixRoom( isDirect = true, - isOneToOne = true, + activeMemberCount = 2, + isEncrypted = true, ).apply { givenRoomMembersState( MatrixRoomMembersState.Ready( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 50fad866d9..163e466e1f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -41,6 +41,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.api.room.roomNotificationSettings diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt index f35aa1f211..326255effd 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canBan import io.element.android.libraries.matrix.api.room.powerlevels.canKick import io.element.android.services.analytics.api.AnalyticsService @@ -58,8 +59,7 @@ class DefaultRoomMembersModerationPresenter @Inject constructor( private suspend fun canKick() = room.canKick().getOrDefault(false) override suspend fun canDisplayModerationActions(): Boolean { - val isDm = room.isDm && room.isEncrypted - return !isDm && (canBan() || canKick()) + return !room.isDm && (canBan() || canKick()) } @Composable diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt index 8909870669..ae789f8958 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt @@ -45,7 +45,7 @@ import org.junit.Test class DefaultRoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when room is DM is false`() = runTest { - val room = FakeMatrixRoom(isDirect = true, isPublic = true, isOneToOne = true).apply { + val room = FakeMatrixRoom(isDirect = true, isPublic = true, activeMemberCount = 2).apply { givenRoomInfo(aRoomInfo(isDirect = true, isPublic = false, activeMembersCount = 2)) } val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) @@ -54,7 +54,7 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = FakeMatrixRoom(isDirect = false, isOneToOne = false).apply { + val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply { givenCanKickResult(Result.success(true)) } val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) @@ -63,7 +63,7 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = FakeMatrixRoom(isDirect = false, isOneToOne = false).apply { + val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply { givenCanBanResult(Result.success(true)) } val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) 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 8d838441e6..fc874c3a84 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 @@ -290,5 +290,5 @@ internal fun RoomListRoomSummary.toInviteData() = InviteData( roomId = roomId, // Note: `name` should not be null at this point, but just in case, fallback to the roomId roomName = name ?: roomId.value, - isDirect = isDirect, + isDm = isDm, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index f7602faa16..f673998872 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -95,8 +95,8 @@ internal fun RoomSummaryRow( modifier = modifier ) { InviteNameAndIndicatorRow(name = room.name) - InviteSubtitle(isDirect = room.isDirect, inviteSender = room.inviteSender, canonicalAlias = room.canonicalAlias) - if (!room.isDirect && room.inviteSender != null) { + InviteSubtitle(isDm = room.isDm, inviteSender = room.inviteSender, canonicalAlias = room.canonicalAlias) + if (!room.isDm && room.inviteSender != null) { Spacer(modifier = Modifier.height(4.dp)) InviteSenderView( modifier = Modifier.fillMaxWidth(), @@ -206,12 +206,12 @@ private fun NameAndTimestampRow( @Composable private fun InviteSubtitle( - isDirect: Boolean, + isDm: Boolean, inviteSender: InviteSender?, canonicalAlias: RoomAlias?, modifier: Modifier = Modifier ) { - val subtitle = if (isDirect) { + val subtitle = if (isDm) { inviteSender?.userId?.value } else { canonicalAlias?.value 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 3cde0908b4..153c7cf41d 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 @@ -45,7 +45,7 @@ class RoomListRoomSummaryFactory @Inject constructor( isMarkedUnread = details.isMarkedUnread, timestamp = lastMessageTimestampFormatter.format(details.lastMessageTimestamp), lastMessage = details.lastMessage?.let { message -> - roomLastMessageFormatter.format(message.event, details.isDirect) + roomLastMessageFormatter.format(message.event, details.isDm) }.orEmpty(), avatarData = avatarData, userDefinedNotificationMode = details.userDefinedNotificationMode, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt index 473c60049a..cf44be6090 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt @@ -101,7 +101,7 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider val roomTypingMembersFlow: Flow> diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt new file mode 100644 index 0000000000..1cc665ede9 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room + +/** + * Returns whether the room with the provided info is a DM. + * A DM is a room with at most 2 active members (one of them may have left). + * + * @param isDirect true if the room is direct + * @param activeMembersCount the number of active members in the room (joined or invited) + */ +fun isDm(isDirect: Boolean, activeMembersCount: Int): Boolean { + return isDirect && activeMembersCount <= 2 +} + +/** + * Returns whether the [MatrixRoom] is a DM. + */ +val MatrixRoom.isDm get() = isDm(isDirect, activeMemberCount.toInt()) + +/** + * Returns whether the [MatrixRoomInfo] is from a DM. + */ +val MatrixRoomInfo.isDm get() = isDm(isDirect, activeMembersCount.toInt()) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt index c2fb147aa0..e97c903e07 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.coroutines.flow.first diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt new file mode 100644 index 0000000000..95b13e95af --- /dev/null +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class RoomIsDmCheckTest { + @Test + fun `a room is a DM only if it has at most 2 members and is direct`() { + val isDirect = true + val activeMembersCount = 2 + + val isDm = isDm(isDirect, activeMembersCount) + + assertThat(isDm).isTrue() + } + + @Test + fun `a room can be a DM if it has also a single active user`() { + val isDirect = true + val activeMembersCount = 1 + + val isDm = isDm(isDirect, activeMembersCount) + + assertThat(isDm).isTrue() + } + + @Test + fun `a room is not a DM if it's not direct`() { + val isDirect = false + val activeMembersCount = 2 + + val isDm = isDm(isDirect, activeMembersCount) + + assertThat(isDm).isFalse() + } + + @Test + fun `a room is not a DM if it has more than 2 active users`() { + val isDirect = true + val activeMembersCount = 3 + + val isDm = isDm(isDirect, activeMembersCount) + + assertThat(isDm).isFalse() + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt index 4b7c08f9b4..48f79b5e93 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.services.toolbox.api.systemclock.SystemClock import org.matrix.rustcomponents.sdk.NotificationEvent import org.matrix.rustcomponents.sdk.NotificationItem @@ -40,15 +41,20 @@ class NotificationMapper( notificationItem: NotificationItem ): NotificationData { return notificationItem.use { item -> + val isDm = isDm( + isDirect = item.roomInfo.isDirect, + activeMembersCount = item.roomInfo.joinedMembersCount.toInt(), + ) NotificationData( eventId = eventId, roomId = roomId, senderAvatarUrl = item.senderInfo.avatarUrl, senderDisplayName = item.senderInfo.displayName, senderIsNameAmbiguous = item.senderInfo.isNameAmbiguous, - roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { item.roomInfo.isDirect }, + roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { isDm }, roomDisplayName = item.roomInfo.displayName, isDirect = item.roomInfo.isDirect, + isDm = isDm, isEncrypted = item.roomInfo.isEncrypted.orFalse(), isNoisy = item.isNoisy.orFalse(), timestamp = item.timestamp() ?: clock.epochMillis(), 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 bc66bb6b78..ac933443b1 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 @@ -26,10 +26,10 @@ val RoomListFilter.predicate is RoomListFilter.Any -> { _: RoomSummary -> true } RoomListFilter.None -> { _: RoomSummary -> false } RoomListFilter.Category.Group -> { roomSummary: RoomSummary -> - !roomSummary.isDirect && !roomSummary.isInvited() + !roomSummary.isDm && !roomSummary.isInvited() } RoomListFilter.Category.People -> { roomSummary: RoomSummary -> - roomSummary.isDirect && !roomSummary.isInvited() + roomSummary.isDm && !roomSummary.isInvited() } RoomListFilter.Favorite -> { roomSummary: RoomSummary -> roomSummary.isFavorite && !roomSummary.isInvited() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index 1361d74a3d..2528599343 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper import io.element.android.libraries.matrix.impl.room.elementHeroes @@ -47,7 +48,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto inviter = roomInfo.inviter?.let(RoomMemberMapper::map), userDefinedNotificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode), hasRoomCall = roomInfo.hasRoomCall, - isDm = roomInfo.isDirect && roomInfo.activeMembersCount.toLong() == 2L, + isDm = isDm(isDirect = roomInfo.isDirect, activeMembersCount = roomInfo.activeMembersCount.toInt()), isFavorite = roomInfo.isFavourite, currentUserMembership = roomInfo.membership.map(), heroes = roomInfo.elementHeroes(), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 443191a538..9cdb8d1366 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.Mention +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.ReceiptType 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 5687cd0c14..beef6f4b2b 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 @@ -25,10 +25,10 @@ import org.junit.Test class RoomListFilterTest { private val regularRoom = aRoomSummary( - isDirect = false + isDm = false ) - private val directRoom = aRoomSummary( - isDirect = true + private val dmRoom = aRoomSummary( + isDm = true ) private val favoriteRoom = aRoomSummary( isFavorite = true @@ -48,7 +48,7 @@ class RoomListFilterTest { private val roomSummaries = listOf( regularRoom, - directRoom, + dmRoom, favoriteRoom, markedAsUnreadRoom, unreadNotificationRoom, @@ -71,7 +71,7 @@ class RoomListFilterTest { @Test fun `Room list filter people`() = runTest { val filter = RoomListFilter.Category.People - assertThat(roomSummaries.filter(filter)).containsExactly(directRoom) + assertThat(roomSummaries.filter(filter)).containsExactly(dmRoom) } @Test diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt index 330db62274..b6827d13e5 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt @@ -34,6 +34,7 @@ fun aNotificationData( roomAvatarUrl = null, roomDisplayName = null, isDirect = false, + isDm = false, isEncrypted = false, isNoisy = false, timestamp = 0L, 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 7b08b2ea63..849b8aa0a6 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 @@ -80,7 +80,6 @@ class FakeMatrixRoom( override val isPublic: Boolean = true, override val isSpace: Boolean = false, override val isDirect: Boolean = false, - override val isOneToOne: Boolean = false, override val joinedMemberCount: Long = 123L, override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt index b14fcdeab3..1e50a25857 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt @@ -79,7 +79,7 @@ class DefaultCallNotificationEventResolver @Inject constructor( senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId), body = "☎️ ${stringProvider.getString(R.string.notification_incoming_call)}", roomName = roomDisplayName, - roomIsDirect = isDirect, + roomIsDm = isDm, roomAvatarPath = roomAvatarUrl, senderAvatarPath = senderAvatarUrl, type = EventType.CALL_NOTIFY, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index 30ede8f3d1..1e186b38a8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -120,7 +120,7 @@ class DefaultNotifiableEventResolver @Inject constructor( body = notificationBody, imageUriString = fetchImageIfPresent(client)?.toString(), roomName = roomDisplayName, - roomIsDirect = isDirect, + roomIsDm = isDm, roomAvatarPath = roomAvatarUrl, senderAvatarPath = senderAvatarUrl, ) @@ -168,7 +168,7 @@ class DefaultNotifiableEventResolver @Inject constructor( body = stringProvider.getString(CommonStrings.common_call_invite), imageUriString = fetchImageIfPresent(client)?.toString(), roomName = roomDisplayName, - roomIsDirect = isDirect, + roomIsDm = isDm, roomAvatarPath = roomAvatarUrl, senderAvatarPath = senderAvatarUrl, ) @@ -197,7 +197,7 @@ class DefaultNotifiableEventResolver @Inject constructor( body = stringProvider.getString(CommonStrings.common_poll_summary, content.question), imageUriString = null, roomName = roomDisplayName, - roomIsDirect = isDirect, + roomIsDm = isDm, roomAvatarPath = roomAvatarUrl, senderAvatarPath = senderAvatarUrl, ) @@ -330,7 +330,7 @@ internal fun buildNotifiableMessageEvent( imageUriString: String? = null, threadId: ThreadId? = null, roomName: String? = null, - roomIsDirect: Boolean = false, + roomIsDm: Boolean = false, roomAvatarPath: String? = null, senderAvatarPath: String? = null, soundName: String? = null, @@ -354,7 +354,7 @@ internal fun buildNotifiableMessageEvent( imageUriString = imageUriString, threadId = threadId, roomName = roomName, - roomIsDirect = roomIsDirect, + roomIsDm = roomIsDm, roomAvatarPath = roomAvatarPath, senderAvatarPath = senderAvatarPath, soundName = soundName, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index 5f32bd86ac..b1d26ce7ab 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.push.api.notifications.NotificationDrawerManager @@ -160,7 +161,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( imageUriString = null, threadId = threadId, roomName = room.displayName, - roomIsDirect = room.isDirect, + roomIsDm = room.isDm, outGoingMessage = true, ) onNotifiableEventReceived.onNotifiableEventReceived(notifiableMessageEvent) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt index a0b7e30218..2f0f90fc40 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt @@ -79,7 +79,7 @@ class DefaultNotificationDataFactory @Inject constructor( .groupBy { it.roomId } return messagesToDisplay.map { (roomId, events) -> val roomName = events.lastOrNull()?.roomName ?: roomId.value - val isDirect = events.lastOrNull()?.roomIsDirect ?: false + val isDm = events.lastOrNull()?.roomIsDm ?: false val notification = roomGroupMessageCreator.createRoomMessage( currentUser = currentUser, events = events, @@ -90,7 +90,7 @@ class DefaultNotificationDataFactory @Inject constructor( RoomNotification( notification = notification, roomId = roomId, - summaryLine = createRoomMessagesGroupSummaryLine(events, roomName, isDirect), + summaryLine = createRoomMessagesGroupSummaryLine(events, roomName, isDm), messageCount = events.size, latestTimestamp = events.maxOf { it.timestamp }, shouldBing = events.any { it.noisy } @@ -167,9 +167,9 @@ class DefaultNotificationDataFactory @Inject constructor( } } - private fun createRoomMessagesGroupSummaryLine(events: List, roomName: String, roomIsDirect: Boolean): CharSequence { + private fun createRoomMessagesGroupSummaryLine(events: List, roomName: String, roomIsDm: Boolean): CharSequence { return when (events.size) { - 1 -> createFirstMessageSummaryLine(events.first(), roomName, roomIsDirect) + 1 -> createFirstMessageSummaryLine(events.first(), roomName, roomIsDm) else -> { stringProvider.getQuantityString( R.plurals.notification_compat_summary_line_for_room, @@ -181,8 +181,8 @@ class DefaultNotificationDataFactory @Inject constructor( } } - private fun createFirstMessageSummaryLine(event: NotifiableMessageEvent, roomName: String, roomIsDirect: Boolean): CharSequence { - return if (roomIsDirect) { + private fun createFirstMessageSummaryLine(event: NotifiableMessageEvent, roomName: String, roomIsDm: Boolean): CharSequence { + return if (roomIsDm) { buildSpannedString { event.senderDisambiguatedDisplayName?.let { inSpans(StyleSpan(Typeface.BOLD)) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt index 96a8b90f06..ffbbc7122b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt @@ -26,7 +26,7 @@ data class RoomEventGroupInfo( val sessionId: SessionId, val roomId: RoomId, val roomDisplayName: String, - val isDirect: Boolean = false, + val isDm: Boolean = false, // An event in the list has not yet been display val hasNewEvent: Boolean = false, // true if at least one on the not yet displayed event is noisy diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 9fcbf8c152..8b7e44096d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -56,7 +56,7 @@ class DefaultRoomGroupMessageCreator @Inject constructor( ): Notification { val lastKnownRoomEvent = events.last() val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderDisambiguatedDisplayName ?: "Room name (${roomId.value.take(8)}…)" - val roomIsGroup = !lastKnownRoomEvent.roomIsDirect + val roomIsGroup = !lastKnownRoomEvent.roomIsDm val tickerText = if (roomIsGroup) { stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderDisambiguatedDisplayName, events.last().description) @@ -68,12 +68,13 @@ class DefaultRoomGroupMessageCreator @Inject constructor( val lastMessageTimestamp = events.last().timestamp val smartReplyErrors = events.filter { it.isSmartReplyError() } + val roomIsDm = !roomIsGroup return notificationCreator.createMessagesListNotification( RoomEventGroupInfo( sessionId = currentUser.userId, roomId = roomId, roomDisplayName = roomName, - isDirect = !roomIsGroup, + isDm = roomIsDm, hasSmartReplyError = smartReplyErrors.isNotEmpty(), shouldBing = events.any { it.noisy }, customSound = events.last().soundName, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 67dbda63a4..c7a5eb40d6 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -157,7 +157,7 @@ class DefaultNotificationCreator @Inject constructor( val messagingStyle = existingNotification?.let { MessagingStyle.extractMessagingStyleFromNotification(it) - } ?: messagingStyleFromCurrentUser(roomInfo.sessionId, currentUser, imageLoader, roomInfo.roomDisplayName, !roomInfo.isDirect) + } ?: messagingStyleFromCurrentUser(roomInfo.sessionId, currentUser, imageLoader, roomInfo.roomDisplayName, !roomInfo.isDm) messagingStyle.addMessagesFromEvents(events, imageLoader) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt index 4f3dbf57b3..b90af3df94 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt @@ -43,7 +43,7 @@ data class NotifiableMessageEvent( val imageUriString: String?, val threadId: ThreadId?, val roomName: String?, - val roomIsDirect: Boolean = false, + val roomIsDm: Boolean = false, val roomAvatarPath: String? = null, val senderAvatarPath: String? = null, val soundName: String? = null, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt index 6caaac207b..1f6c98e914 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt @@ -473,7 +473,6 @@ class DefaultNotifiableEventResolverTest { imageUriString = null, threadId = null, roomName = null, - roomIsDirect = false, roomAvatarPath = null, senderAvatarPath = null, soundName = null, @@ -544,7 +543,6 @@ class DefaultNotifiableEventResolverTest { roomId = A_ROOM_ID, threadId = null, roomName = null, - roomIsDirect = false, canBeReplaced = false, isRedacted = false, imageUriString = null, @@ -578,7 +576,6 @@ class DefaultNotifiableEventResolverTest { roomId = A_ROOM_ID, threadId = null, roomName = null, - roomIsDirect = false, canBeReplaced = false, isRedacted = false, imageUriString = null, @@ -686,6 +683,7 @@ class DefaultNotifiableEventResolverTest { timestamp = timestamp, content = content, hasMention = hasMention, + isDm = false, ) } @@ -704,7 +702,6 @@ class DefaultNotifiableEventResolverTest { imageUriString = null, threadId = null, roomName = null, - roomIsDirect = false, roomAvatarPath = null, senderAvatarPath = null, soundName = null, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultRoomGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultRoomGroupMessageCreatorTest.kt index 2b5b941292..a6bdb298c0 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultRoomGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultRoomGroupMessageCreatorTest.kt @@ -188,14 +188,14 @@ class DefaultRoomGroupMessageCreatorTest { } @Test - fun `test createRoomMessage for direct room`() = runTest { + fun `test createRoomMessage for DM`() = runTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( currentUser = aMatrixUser(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( - roomIsDirect = true, + roomIsDm = true, ), ), roomId = A_ROOM_ID, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt index 124c71adcf..176673450c 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt @@ -227,7 +227,6 @@ class DefaultNotificationCreatorTest { sessionId = A_SESSION_ID, roomId = A_ROOM_ID, roomDisplayName = "roomDisplayName", - isDirect = false, hasSmartReplyError = false, shouldBing = false, customSound = null, @@ -254,7 +253,6 @@ class DefaultNotificationCreatorTest { sessionId = A_SESSION_ID, roomId = A_ROOM_ID, roomDisplayName = "roomDisplayName", - isDirect = false, hasSmartReplyError = false, shouldBing = true, customSound = null, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt index fabe4bd353..f1228dd0ce 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt @@ -97,7 +97,6 @@ fun aNotifiableMessageEvent( roomId = roomId, threadId = threadId, roomName = "room-name", - roomIsDirect = false, canBeReplaced = false, isRedacted = isRedacted, imageUriString = null, diff --git a/tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Day_10_en.png new file mode 100644 index 0000000000..655bba1cb2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Day_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd2b534a026cb2c96c90f20eb0154b20672d4eae294c558804898cd927e93585 +size 106258 diff --git a/tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Night_10_en.png new file mode 100644 index 0000000000..02b54d0c29 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.joinroom.impl_JoinRoomView_Night_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13ed33b9e0b040ac1052389728179014984dc765671384e2d7215377a19d8fcc +size 91575 From 5944f112fb7f92e57ede453a7fb7e016ea2870dd Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 11 Jul 2024 10:54:56 +0200 Subject: [PATCH 047/115] Subscribe to `RoomListItems` in the visible range (#3169) * Subscribe to `RoomListItems` in the visible range This ensures the room list items always have updated info. --- .../features/roomlist/impl/RoomListEvents.kt | 1 + .../roomlist/impl/RoomListPresenter.kt | 23 +++++++++ .../impl/components/RoomListContentView.kt | 19 ++++++- .../impl/datasource/RoomListDataSource.kt | 11 ++-- .../roomlist/impl/RoomListPresenterTest.kt | 32 ++++++++++++ .../roomlist/impl/RoomListViewTest.kt | 50 ++++++++++++++++++- .../matrix/api/roomlist/RoomListService.kt | 7 +++ .../libraries/matrix/impl/RustMatrixClient.kt | 5 ++ .../matrix/impl/room/RoomSyncSubscriber.kt | 35 +++++++++++-- .../matrix/impl/room/RustRoomFactory.kt | 3 +- .../roomlist/RoomSummaryDetailsFactory.kt | 4 +- .../impl/roomlist/RoomSummaryListProcessor.kt | 4 +- .../impl/roomlist/RustRoomListService.kt | 11 ++++ .../roomlist/RoomSummaryListProcessorTest.kt | 3 ++ .../test/roomlist/FakeRoomListService.kt | 9 +++- .../android/tests/testutils/EventsRecorder.kt | 4 ++ 16 files changed, 204 insertions(+), 17 deletions(-) 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 7919fd2274..aa1e8e2832 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 @@ -20,6 +20,7 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.libraries.matrix.api.core.RoomId sealed interface RoomListEvents { + data class UpdateVisibleRange(val range: IntRange) : RoomListEvents data object DismissRequestVerificationPrompt : RoomListEvents data object DismissRecoveryKeyPrompt : RoomListEvents data object ToggleSearchResults : RoomListEvents 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 fc874c3a84..493047bc8b 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 @@ -68,6 +68,8 @@ import io.element.android.services.analyticsproviders.api.trackers.captureIntera import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -78,6 +80,8 @@ import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import javax.inject.Inject +private const val EXTENDED_RANGE_SIZE = 40 + class RoomListPresenter @Inject constructor( private val client: MatrixClient, private val networkMonitor: NetworkMonitor, @@ -122,6 +126,9 @@ class RoomListPresenter @Inject constructor( fun handleEvents(event: RoomListEvents) { when (event) { + is RoomListEvents.UpdateVisibleRange -> coroutineScope.launch { + updateVisibleRange(event.range) + } RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true RoomListEvents.DismissRecoveryKeyPrompt -> securityBannerDismissed = true RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility) @@ -283,6 +290,22 @@ class RoomListPresenter @Inject constructor( } } } + + private var currentUpdateVisibleRangeJob: Job? = null + private fun CoroutineScope.updateVisibleRange(range: IntRange) { + currentUpdateVisibleRangeJob?.cancel() + currentUpdateVisibleRangeJob = launch(SupervisorJob()) { + if (range.isEmpty()) return@launch + val currentRoomList = roomListDataSource.allRooms.first() + // Use extended range to 'prefetch' the next rooms info + val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2 + val extendedRange = range.first until range.last + midExtendedRangeSize + val roomIds = extendedRange.mapNotNull { index -> + currentRoomList.getOrNull(index)?.roomId + } + roomListDataSource.subscribeToVisibleRooms(roomIds) + } + } } @VisibleForTesting diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index ad4a7b0c26..5bc85d3458 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -30,6 +30,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -165,6 +170,18 @@ private fun RoomsViewList( modifier: Modifier = Modifier, ) { val lazyListState = rememberLazyListState() + val visibleRange by remember { + derivedStateOf { + val layoutInfo = lazyListState.layoutInfo + val firstItemIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: 0 + val size = layoutInfo.visibleItemsInfo.size + firstItemIndex until firstItemIndex + size + } + } + val updatedEventSink by rememberUpdatedState(newValue = eventSink) + LaunchedEffect(visibleRange) { + updatedEventSink(RoomListEvents.UpdateVisibleRange(visibleRange)) + } LazyColumn( state = lazyListState, modifier = modifier, @@ -177,7 +194,7 @@ private fun RoomsViewList( item { ConfirmRecoveryKeyBanner( onContinueClick = onConfirmRecoveryKeyClick, - onDismissClick = { eventSink(RoomListEvents.DismissRecoveryKeyPrompt) } + onDismissClick = { updatedEventSink(RoomListEvents.DismissRecoveryKeyPrompt) } ) } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index 21d3da1eef..6e7f386b14 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -20,6 +20,7 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary @@ -57,6 +58,10 @@ class RoomListDataSource @Inject constructor( old?.roomId == new?.roomId } + val allRooms: Flow> = _allRooms + + val loadingState = roomListService.allRooms.loadingState + fun launchIn(coroutineScope: CoroutineScope) { roomListService .allRooms @@ -67,9 +72,9 @@ class RoomListDataSource @Inject constructor( .launchIn(coroutineScope) } - val allRooms: Flow> = _allRooms - - val loadingState = roomListService.allRooms.loadingState + suspend fun subscribeToVisibleRooms(roomIds: List) { + roomListService.subscribeToVisibleRooms(roomIds) + } @OptIn(FlowPreview::class) private fun observeNotificationSettings() { 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 b70db9c87b..e1e0970756 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 @@ -50,6 +50,7 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPermissionsPresenter import io.element.android.libraries.indicator.impl.DefaultIndicatorService import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.room.CurrentUserMembership @@ -584,6 +585,37 @@ class RoomListPresenterTest { } } + @Test + fun `present - UpdateVisibleRange subscribes to rooms in visible range`() = runTest { + val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List -> } + val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val matrixClient = FakeMatrixClient( + roomListService = roomListService, + ) + val roomSummary = aRoomSummary( + currentUserMembership = CurrentUserMembership.INVITED + ) + roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) + roomListService.postAllRooms(listOf(roomSummary)) + val presenter = createRoomListPresenter( + coroutineScope = scope, + client = matrixClient, + ) + presenter.test { + val state = consumeItemsUntilPredicate { + it.contentState is RoomListContentState.Rooms + }.last() + + state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 10))) + subscribeToVisibleRoomsLambda.assertions().isCalledOnce() + + // If called again, it will cancel the current one, which should not result in a test failure + state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 11))) + subscribeToVisibleRoomsLambda.assertions().isCalledExactly(2) + } + } + private fun TestScope.createRoomListPresenter( client: MatrixClient = FakeMatrixClient(), networkMonitor: NetworkMonitor = FakeNetworkMonitor(), diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt index 99045cd783..09cb6a8019 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt @@ -44,6 +44,24 @@ import org.junit.runner.RunWith class RoomListViewTest { @get:Rule val rule = createAndroidComposeRule() + @Test + fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() { + val eventsRecorder = EventsRecorder() + rule.setRoomListView( + state = aRoomListState( + contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), + eventSink = eventsRecorder, + ) + ) + + eventsRecorder.assertList( + listOf( + RoomListEvents.UpdateVisibleRange(IntRange.EMPTY), + RoomListEvents.UpdateVisibleRange(0 until 2), + ) + ) + } + @Test fun `clicking on close recovery key banner emits the expected Event`() { val eventsRecorder = EventsRecorder() @@ -53,6 +71,10 @@ class RoomListViewTest { eventSink = eventsRecorder, ) ) + + // Remove automatic initial events + eventsRecorder.clear() + val close = rule.activity.getString(CommonStrings.action_close) rule.onNodeWithContentDescription(close).performClick() eventsRecorder.assertSingle(RoomListEvents.DismissRecoveryKeyPrompt) @@ -60,7 +82,7 @@ class RoomListViewTest { @Test fun `clicking on continue recovery key banner invokes the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder() ensureCalledOnce { callback -> rule.setRoomListView( state = aRoomListState( @@ -69,7 +91,13 @@ class RoomListViewTest { ), onConfirmRecoveryKeyClick = callback, ) + + // Remove automatic initial events + eventsRecorder.clear() + rule.clickOn(CommonStrings.action_continue) + + eventsRecorder.assertEmpty() } } @@ -90,7 +118,7 @@ class RoomListViewTest { @Test fun `clicking on a room invokes the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder() val state = aRoomListState( eventSink = eventsRecorder, ) @@ -102,8 +130,14 @@ class RoomListViewTest { state = state, onRoomClick = callback, ) + + // Remove automatic initial events + eventsRecorder.clear() + rule.onNodeWithText(room0.lastMessage!!.toString()).performClick() } + + eventsRecorder.assertEmpty() } @Test @@ -118,6 +152,9 @@ class RoomListViewTest { rule.setRoomListView( state = state, ) + // Remove automatic initial events + eventsRecorder.clear() + rule.onNodeWithText(room0.lastMessage!!.toString()).performTouchInput { longClick() } eventsRecorder.assertSingle(RoomListEvents.ShowContextMenu(room0)) } @@ -135,8 +172,13 @@ class RoomListViewTest { state = state, onRoomSettingsClick = callback, ) + + // Remove automatic initial events + eventsRecorder.clear() + rule.clickOn(CommonStrings.common_settings) } + eventsRecorder.assertSingle(RoomListEvents.HideContextMenu) } @@ -150,6 +192,10 @@ class RoomListViewTest { it.displayType == RoomSummaryDisplayType.INVITE } rule.setRoomListView(state = state) + + // Remove automatic initial events + eventsRecorder.clear() + rule.clickOn(CommonStrings.action_accept) rule.clickOn(CommonStrings.action_decline) eventsRecorder.assertList( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 4f9c4c0c5a..c98b05d192 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.api.roomlist import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterIsInstance @@ -53,6 +54,12 @@ interface RoomListService { source: RoomList.Source, ): DynamicRoomList + /** + * Subscribes to sync requests for the visible rooms. + * @param roomIds the list of visible room ids to subscribe to. + */ + suspend fun subscribeToVisibleRooms(roomIds: List) + /** * Returns a [DynamicRoomList] object of all rooms we want to display. * If you want to get a filtered room list, consider using [createRoomList]. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 453bd51cba..13eb506b0a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -55,6 +55,7 @@ import io.element.android.libraries.matrix.impl.notificationsettings.RustNotific import io.element.android.libraries.matrix.impl.oidc.toRustAction import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.room.RoomContentForwarder +import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber import io.element.android.libraries.matrix.impl.room.RustRoomFactory import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewMapper import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService @@ -213,6 +214,8 @@ class RustMatrixClient( } } + private val roomSyncSubscriber: RoomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers) + override val roomListService: RoomListService = RustRoomListService( innerRoomListService = innerRoomListService, sessionCoroutineScope = sessionCoroutineScope, @@ -221,6 +224,7 @@ class RustMatrixClient( innerRoomListService = innerRoomListService, sessionCoroutineScope = sessionCoroutineScope, ), + roomSyncSubscriber = roomSyncSubscriber, ) private val verificationService = RustSessionVerificationService( @@ -238,6 +242,7 @@ class RustMatrixClient( dispatchers = dispatchers, systemClock = clock, roomContentForwarder = RoomContentForwarder(innerRoomListService), + roomSyncSubscriber = roomSyncSubscriber, isKeyBackupEnabled = { client.encryption().use { it.backupState() == BackupState.ENABLED } }, getSessionData = { sessionStore.getSession(sessionId.value)!! }, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt index a65dc17a43..6fcba5f89c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt @@ -19,18 +19,19 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.timeline.item.event.EventType +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.RequiredState -import org.matrix.rustcomponents.sdk.RoomListService +import org.matrix.rustcomponents.sdk.RoomListServiceInterface import org.matrix.rustcomponents.sdk.RoomSubscription import timber.log.Timber private const val DEFAULT_TIMELINE_LIMIT = 20u class RoomSyncSubscriber( - private val roomListService: RoomListService, + private val roomListService: RoomListServiceInterface, private val dispatchers: CoroutineDispatchers, ) { private val subscriptionCounts = HashMap() @@ -38,8 +39,10 @@ class RoomSyncSubscriber( private val settings = RoomSubscription( requiredState = listOf( - RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), + RequiredState(key = EventType.STATE_ROOM_NAME, value = ""), RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""), + RequiredState(key = EventType.STATE_ROOM_AVATAR, value = ""), + RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""), RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""), ), @@ -65,6 +68,27 @@ class RoomSyncSubscriber( } } + suspend fun batchSubscribe(roomIds: List) = mutex.withLock { + withContext(dispatchers.io) { + for (roomId in roomIds) { + try { + val currentSubscription = subscriptionCounts.getOrElse(roomId) { 0 } + if (currentSubscription == 0) { + Timber.d("Subscribing to room $roomId}") + roomListService.room(roomId.value).use { roomListItem -> + roomListItem.subscribe(settings) + } + } + subscriptionCounts[roomId] = currentSubscription + 1 + } catch (cancellationException: CancellationException) { + throw cancellationException + } catch (exception: Exception) { + Timber.e("Failed to subscribe to room $roomId") + } + } + } + } + suspend fun unsubscribe(roomId: RoomId) = mutex.withLock { withContext(dispatchers.io) { try { @@ -84,4 +108,9 @@ class RoomSyncSubscriber( } } } + + fun isSubscribedTo(roomId: RoomId): Boolean { + val subscriptionCount = subscriptionCounts[roomId] ?: return false + return subscriptionCount > 0 + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index 19d9401a27..8d3d056aab 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -50,6 +50,7 @@ class RustRoomFactory( private val roomContentForwarder: RoomContentForwarder, private val roomListService: RoomListService, private val innerRoomListService: InnerRoomListService, + private val roomSyncSubscriber: RoomSyncSubscriber, private val isKeyBackupEnabled: suspend () -> Boolean, private val getSessionData: suspend () -> SessionData, ) { @@ -59,8 +60,6 @@ class RustRoomFactory( private val matrixRoomInfoMapper = MatrixRoomInfoMapper() - private val roomSyncSubscriber: RoomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers) - private val eventFilters = TimelineConfig.excludedEvents .takeIf { it.isNotEmpty() } ?.let { listStateEventType -> diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index 2528599343..a833734a5c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -31,8 +31,8 @@ import org.matrix.rustcomponents.sdk.use class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { suspend fun create(roomListItem: RoomListItem): RoomSummary { val roomInfo = roomListItem.roomInfo() - val latestRoomMessage = roomListItem.latestEvent()?.use { - roomMessageFactory.create(it) + val latestRoomMessage = roomListItem.latestEvent().use { event -> + roomMessageFactory.create(event) } return RoomSummary( roomId = RoomId(roomInfo.id), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index 8e4899d942..8b9cfc6151 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -113,9 +113,7 @@ class RoomSummaryListProcessor( val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem -> buildAndCacheRoomSummaryForRoomListItem(roomListItem) } - if (builtRoomSummary != null) { - roomSummariesByIdentifier[identifier] = builtRoomSummary - } else { + if (builtRoomSummary == null) { roomSummariesByIdentifier.remove(identifier) } return builtRoomSummary diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index d2b80a84a9..7297913097 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -16,11 +16,13 @@ package io.element.android.libraries.matrix.impl.roomlist +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally +import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -41,6 +43,7 @@ internal class RustRoomListService( private val sessionCoroutineScope: CoroutineScope, private val sessionDispatcher: CoroutineDispatcher, private val roomListFactory: RoomListFactory, + private val roomSyncSubscriber: RoomSyncSubscriber, ) : RoomListService { override fun createRoomList( pageSize: Int, @@ -58,6 +61,14 @@ internal class RustRoomListService( } } + override suspend fun subscribeToVisibleRooms(roomIds: List) { + val toSubscribe = roomIds.filterNot { roomSyncSubscriber.isSubscribedTo(it) } + if (toSubscribe.isNotEmpty()) { + Timber.d("Subscribe to ${toSubscribe.size} rooms: $toSubscribe") + roomSyncSubscriber.batchSubscribe(toSubscribe) + } + } + override val allRooms: DynamicRoomList = roomListFactory.createRoomList( pageSize = DEFAULT_PAGE_SIZE, coroutineContext = sessionDispatcher, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt index 394cc5d379..f2e2f07ad6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt @@ -43,6 +43,7 @@ import org.matrix.rustcomponents.sdk.RoomListServiceStateListener import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomNotificationMode +import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.TaskHandle // NOTE: this class is using a fake implementation of a Rust SDK interface which returns actual Rust objects with pointers. @@ -267,4 +268,6 @@ class FakeRoomListItem( override suspend fun latestEvent(): EventTimelineItem? { return latestEvent } + + override fun subscribe(settings: RoomSubscription?) = Unit } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index f8980bb75d..9fc257c65a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.test.roomlist +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter @@ -24,7 +25,9 @@ import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class FakeRoomListService : RoomListService { +class FakeRoomListService( + var subscribeToVisibleRoomsLambda: (List) -> Unit = {}, +) : RoomListService { private val allRoomSummariesFlow = MutableStateFlow>(emptyList()) private val allRoomsLoadingStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) private val roomListStateFlow = MutableStateFlow(RoomListService.State.Idle) @@ -56,6 +59,10 @@ class FakeRoomListService : RoomListService { } } + override suspend fun subscribeToVisibleRooms(roomIds: List) { + subscribeToVisibleRoomsLambda(roomIds) + } + override val allRooms = SimplePagedRoomList( allRoomSummariesFlow, allRoomsLoadingStateFlow, diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt index 7818de4118..add5632acb 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt @@ -50,4 +50,8 @@ class EventsRecorder( fun assertTrue(index: Int, predicate: (T) -> Boolean) { assertThat(predicate(events[index])).isTrue() } + + fun clear() { + events.clear() + } } From 1e9497b83a6f9dfd1abe636e61d2f9c824beba9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:50:56 +0000 Subject: [PATCH 048/115] Update dependency gradle to v8.9 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 515ab9d5f1..4094e92b2d 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=f8b4f4772d302c8ff580bc40d0f56e715de69b163546944f787c87abf209c961 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 61c021668619fa400784c3ec5c8098baf1089c90 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 08:03:52 +0000 Subject: [PATCH 049/115] Update dependency com.google.firebase:firebase-bom to v33.1.2 (#3178) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cfe2b85dc9..ff6aec12ec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -67,7 +67,7 @@ kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", v kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } gms_google_services = "com.google.gms:google-services:4.4.2" # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:33.1.1" +google_firebase_bom = "com.google.firebase:firebase-bom:33.1.2" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } From c508c8bdd07c152bef8857873a61156f9c0567b7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 12 Jul 2024 18:11:36 +0200 Subject: [PATCH 050/115] Performance : subscribe to timeline items only when necessary --- .../matrix/impl/timeline/RustTimeline.kt | 127 +++++++----------- .../impl/timeline/TimelineItemsSubscriber.kt | 108 +++++++++++++++ 2 files changed, 156 insertions(+), 79 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 9cdb8d1366..9f48e59995 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -56,8 +56,6 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -67,32 +65,28 @@ import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageFormat import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle -import org.matrix.rustcomponents.sdk.TimelineChange -import org.matrix.rustcomponents.sdk.TimelineDiff -import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.messageEventContentFromHtml import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.use import timber.log.Timber -import uniffi.matrix_sdk_ui.EventItemOrigin import uniffi.matrix_sdk_ui.LiveBackPaginationStatus import java.io.File import java.util.Date import java.util.concurrent.atomic.AtomicBoolean import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline -private const val INITIAL_MAX_SIZE = 50 private const val PAGINATION_SIZE = 50 class RustTimeline( private val inner: InnerTimeline, - isLive: Boolean, + private val isLive: Boolean, systemClock: SystemClock, roomCoroutineScope: CoroutineScope, isKeyBackupEnabled: Boolean, @@ -100,7 +94,7 @@ class RustTimeline( private val dispatcher: CoroutineDispatcher, lastLoginTimestamp: Date?, private val roomContentForwarder: RoomContentForwarder, - private val onNewSyncedEvent: () -> Unit, + onNewSyncedEvent: () -> Unit, ) : Timeline { private val initLatch = CompletableDeferred() private val isInit = AtomicBoolean(false) @@ -108,20 +102,9 @@ class RustTimeline( private val _timelineItems: MutableStateFlow> = MutableStateFlow(emptyList()) - private val encryptedHistoryPostProcessor = TimelineEncryptedHistoryPostProcessor( - lastLoginTimestamp = lastLoginTimestamp, - isRoomEncrypted = matrixRoom.isEncrypted, - isKeyBackupEnabled = isKeyBackupEnabled, - dispatcher = dispatcher, - ) - - private val roomBeginningPostProcessor = RoomBeginningPostProcessor() - private val loadingIndicatorsPostProcessor = LoadingIndicatorsPostProcessor(systemClock) - private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(isLive) - private val timelineEventContentMapper = TimelineEventContentMapper() private val inReplyToMapper = InReplyToMapper(timelineEventContentMapper) - private val timelineItemFactory = MatrixTimelineItemMapper( + private val timelineItemMapper = MatrixTimelineItemMapper( fetchDetailsForEvent = this::fetchDetailsForEvent, roomCoroutineScope = roomCoroutineScope, virtualTimelineItemMapper = VirtualTimelineItemMapper(), @@ -129,11 +112,29 @@ class RustTimeline( contentMapper = timelineEventContentMapper ) ) - private val timelineDiffProcessor = MatrixTimelineDiffProcessor( timelineItems = _timelineItems, - timelineItemFactory = timelineItemFactory, + timelineItemFactory = timelineItemMapper, ) + private val encryptedHistoryPostProcessor = TimelineEncryptedHistoryPostProcessor( + lastLoginTimestamp = lastLoginTimestamp, + isRoomEncrypted = matrixRoom.isEncrypted, + isKeyBackupEnabled = isKeyBackupEnabled, + dispatcher = dispatcher, + ) + private val timelineItemsSubscriber = TimelineItemsSubscriber( + timeline = inner, + roomCoroutineScope = roomCoroutineScope, + timelineDiffProcessor = timelineDiffProcessor, + initLatch = initLatch, + isInit = isInit, + dispatcher = dispatcher, + onNewSyncedEvent = onNewSyncedEvent, + ) + + private val roomBeginningPostProcessor = RoomBeginningPostProcessor() + private val loadingIndicatorsPostProcessor = LoadingIndicatorsPostProcessor(systemClock) + private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(isLive) private val backPaginationStatus = MutableStateFlow( Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = true) @@ -145,36 +146,28 @@ class RustTimeline( init { roomCoroutineScope.launch(dispatcher) { - inner.timelineDiffFlow() - .onEach { diffs -> - if (diffs.any { diff -> diff.eventOrigin() == EventItemOrigin.SYNC }) { - onNewSyncedEvent() - } - postDiffs(diffs) - } - .launchIn(this) - - launch { - fetchMembers() - } - + fetchMembers() if (isLive) { // When timeline is live, we need to listen to the back pagination status as // sdk can automatically paginate backwards. - inner.liveBackPaginationStatus() - .onEach { backPaginationStatus -> - updatePaginationStatus(Timeline.PaginationDirection.BACKWARDS) { - when (backPaginationStatus) { - is LiveBackPaginationStatus.Idle -> it.copy(isPaginating = false, hasMoreToLoad = !backPaginationStatus.hitStartOfTimeline) - is LiveBackPaginationStatus.Paginating -> it.copy(isPaginating = true, hasMoreToLoad = true) - } - } - } - .launchIn(this) + registerBackPaginationStatusListener() } } } + private fun CoroutineScope.registerBackPaginationStatusListener() { + inner.liveBackPaginationStatus() + .onEach { backPaginationStatus -> + updatePaginationStatus(Timeline.PaginationDirection.BACKWARDS) { + when (backPaginationStatus) { + is LiveBackPaginationStatus.Idle -> it.copy(isPaginating = false, hasMoreToLoad = !backPaginationStatus.hitStartOfTimeline) + is LiveBackPaginationStatus.Paginating -> it.copy(isPaginating = true, hasMoreToLoad = true) + } + } + } + .launchIn(this) + } + override val membershipChangeEventReceived: Flow = timelineDiffProcessor.membershipChangeEventReceived override suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result { @@ -248,13 +241,15 @@ class RustTimeline( // Keep lastForwardIndicatorsPostProcessor last .let { items -> lastForwardIndicatorsPostProcessor.process(items) } } + }.onStart { + timelineItemsSubscriber.subscribeIfNeeded() } override fun close() { inner.close() } - private suspend fun fetchMembers() = withContext(dispatcher) { + private fun CoroutineScope.fetchMembers() = launch(dispatcher) { initLatch.await() try { inner.fetchMembers() @@ -263,32 +258,6 @@ class RustTimeline( } } - private suspend fun postItems(items: List) = coroutineScope { - // Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap. - items.chunked(INITIAL_MAX_SIZE).reversed().forEach { - ensureActive() - timelineDiffProcessor.postItems(it) - } - isInit.set(true) - initLatch.complete(Unit) - } - - private suspend fun postDiffs(diffs: List) { - val diffsToProcess = diffs.toMutableList() - if (!isInit.get()) { - val resetDiff = diffsToProcess.firstOrNull { it.change() == TimelineChange.RESET } - if (resetDiff != null) { - // Keep using the postItems logic so we can post the timelineItems asap. - postItems(resetDiff.reset() ?: emptyList()) - diffsToProcess.remove(resetDiff) - } - } - initLatch.await() - if (diffsToProcess.isNotEmpty()) { - timelineDiffProcessor.postDiffs(diffsToProcess) - } - } - override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result = withContext(dispatcher) { messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content -> runCatching { @@ -550,12 +519,6 @@ class RustTimeline( } } - private suspend fun fetchDetailsForEvent(eventId: EventId): Result { - return runCatching { - inner.fetchDetailsForEvent(eventId.value) - } - } - override suspend fun loadReplyDetails(eventId: EventId): InReplyTo = withContext(dispatcher) { val timelineItem = _timelineItems.value.firstOrNull { timelineItem -> timelineItem is MatrixTimelineItem.Event && timelineItem.eventId == eventId @@ -572,4 +535,10 @@ class RustTimeline( inner.loadReplyDetails(eventId.value).use(inReplyToMapper::map) } } + + private suspend fun fetchDetailsForEvent(eventId: EventId): Result { + return runCatching { + inner.fetchDetailsForEvent(eventId.value) + } + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt new file mode 100644 index 0000000000..0205ac20e5 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.timeline + +import io.element.android.libraries.core.coroutine.childScope +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.matrix.rustcomponents.sdk.Timeline +import org.matrix.rustcomponents.sdk.TimelineChange +import org.matrix.rustcomponents.sdk.TimelineDiff +import org.matrix.rustcomponents.sdk.TimelineItem +import uniffi.matrix_sdk_ui.EventItemOrigin +import java.util.concurrent.atomic.AtomicBoolean + +private const val INITIAL_MAX_SIZE = 50 + +/** + * This class is responsible for subscribing to a timeline and post the items/diffs to the timelineDiffProcessor. + * It will also trigger a callback when a new synced event is received. + * It will also handle the initial items and make sure they are posted before any diff. + * When closing the room subscription, it will also unsubscribe automatically. + */ +internal class TimelineItemsSubscriber( + roomCoroutineScope: CoroutineScope, + dispatcher: CoroutineDispatcher, + private val timeline: Timeline, + private val timelineDiffProcessor: MatrixTimelineDiffProcessor, + private val initLatch: CompletableDeferred, + private val isInit: AtomicBoolean, + private val onNewSyncedEvent: () -> Unit, +) { + private var subscriptionCount = 0 + private val mutex = Mutex() + + private val coroutineScope = roomCoroutineScope.childScope(dispatcher, "TimelineItemsSubscriber") + + suspend fun subscribeIfNeeded() = mutex.withLock { + if (subscriptionCount == 0) { + timeline.timelineDiffFlow() + .onEach { diffs -> + if (diffs.any { diff -> diff.eventOrigin() == EventItemOrigin.SYNC }) { + onNewSyncedEvent() + } + postDiffs(diffs) + } + .launchIn(coroutineScope) + } + subscriptionCount++ + } + + suspend fun unsubscribeIfNeeded() = mutex.withLock { + when (subscriptionCount) { + 0 -> return@withLock + 1 -> { + coroutineScope.coroutineContext.cancelChildren() + } + } + subscriptionCount-- + } + + private suspend fun postItems(items: List) = coroutineScope { + // Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap. + items.chunked(INITIAL_MAX_SIZE).reversed().forEach { + ensureActive() + timelineDiffProcessor.postItems(it) + } + isInit.set(true) + initLatch.complete(Unit) + } + + private suspend fun postDiffs(diffs: List) { + val diffsToProcess = diffs.toMutableList() + if (!isInit.get()) { + val resetDiff = diffsToProcess.firstOrNull { it.change() == TimelineChange.RESET } + if (resetDiff != null) { + // Keep using the postItems logic so we can post the timelineItems asap. + postItems(resetDiff.reset() ?: emptyList()) + diffsToProcess.remove(resetDiff) + } + } + initLatch.await() + if (diffsToProcess.isNotEmpty()) { + timelineDiffProcessor.postDiffs(diffsToProcess) + } + } +} From 9476e44c2ebfafceb5a9ec4e95d161103c85bba9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jul 2024 03:29:53 +0000 Subject: [PATCH 051/115] Update dependency com.google.truth:truth to v1.4.4 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff6aec12ec..d2900e2d31 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -141,7 +141,7 @@ test_runner = "androidx.test:runner:1.6.1" test_mockk = "io.mockk:mockk:1.13.11" test_konsist = "com.lemonappdev:konsist:0.15.1" test_turbine = "app.cash.turbine:turbine:1.1.0" -test_truth = "com.google.truth:truth:1.4.3" +test_truth = "com.google.truth:truth:1.4.4" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.16" test_robolectric = "org.robolectric:robolectric:4.13" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } From 14511633db72d9cda57fe35cab67faa854d2011c Mon Sep 17 00:00:00 2001 From: bmarty <3940906+bmarty@users.noreply.github.com> Date: Mon, 15 Jul 2024 00:23:30 +0000 Subject: [PATCH 052/115] Sync Strings from Localazy --- .../src/main/res/values-cs/translations.xml | 2 +- .../src/main/res/values-et/translations.xml | 4 +- .../src/main/res/values-be/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 1 + .../src/main/res/values-el/translations.xml | 1 + .../src/main/res/values-et/translations.xml | 9 +- .../src/main/res/values-sk/translations.xml | 1 + .../src/main/res/values/localazy.xml | 1 + ...s.call.impl.ui_CallScreenView_Day_1_de.png | 4 +- ...s.call.impl.ui_CallScreenView_Day_2_de.png | 3 + ....roomdetails.impl_RoomDetailsDark_0_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_10_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_11_de.png | 4 +- ...roomdetails.impl_RoomDetailsDark_12_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_1_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_2_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_3_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_4_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_5_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_6_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_7_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_8_de.png | 4 +- ....roomdetails.impl_RoomDetailsDark_9_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_0_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_10_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_11_de.png | 4 +- ...res.roomdetails.impl_RoomDetails_12_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_1_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_2_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_3_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_4_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_5_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_6_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_7_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_8_de.png | 4 +- ...ures.roomdetails.impl_RoomDetails_9_de.png | 4 +- ...rofile.shared_UserProfileView_Day_0_de.png | 4 +- ...rofile.shared_UserProfileView_Day_1_de.png | 4 +- ...rofile.shared_UserProfileView_Day_2_de.png | 4 +- ...rofile.shared_UserProfileView_Day_5_de.png | 4 +- ...rofile.shared_UserProfileView_Day_7_de.png | 4 +- ...rofile.shared_UserProfileView_Day_8_de.png | 4 +- screenshots/html/data.js | 1118 +++++++++-------- 43 files changed, 642 insertions(+), 631 deletions(-) create mode 100644 screenshots/de/features.call.impl.ui_CallScreenView_Day_2_de.png diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml index 88e7a23abe..b49f9cfc95 100644 --- a/features/preferences/impl/src/main/res/values-cs/translations.xml +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -13,7 +13,7 @@ "Potvrzení o přečtení" "Pokud je vypnuto, potvrzení o přečtení se nikomu neodesílají. Stále budete dostávat potvrzení o přečtení od ostatních uživatelů." "Sdílejte přítomnost" - "Pokud je tato funkce vypnutá, nebudete moci odesílat ani přijímat potvrzení o přečtení ani upozornění na psaní" + "Pokud je tato funkce vypnutá, nebudete moci odesílat ani přijímat potvrzení o přečtení ani upozornění o psaní." "Povolit možnost zobrazení zdroje zprávy na časové ose." "Nemáte žádné blokované uživatele" "Odblokovat" diff --git a/features/verifysession/impl/src/main/res/values-et/translations.xml b/features/verifysession/impl/src/main/res/values-et/translations.xml index efe615fa46..219c7653e1 100644 --- a/features/verifysession/impl/src/main/res/values-et/translations.xml +++ b/features/verifysession/impl/src/main/res/values-et/translations.xml @@ -8,9 +8,9 @@ "Kasuta mõnda muud seadet" "Ootame teise seadme järgi…" "Olukord pole päris õige. Päring kas aegus või teine osapool keeldus päringule vastamast." - "Kinnita, et kõik alljärgnevas kuvatud emojid on täpselt samad, mida sa näed oma teises sessioonis." + "Kinnita, et kõik järgnevalt kuvatud emojid on täpselt samad, mida sa näed oma teises sessioonis." "Võrdle emojisid" - "Kinnita, et kõik alljärgnevas kuvatud numbrid on täpselt samad, mida sa näed oma teises sessioonis." + "Kinnita, et kõik järgnevalt kuvatud numbrid on täpselt samad, mida sa näed oma teises sessioonis." "Võrdle numbreid" "Sinu uus sessioon on nüüd verifitseeritud. Sellel sessioonil on nüüd ligipääs sinu krüptitud sõnumitele ja teised osapooled näevad teda usaldusväärsena." "Sisesta taastevõti" diff --git a/libraries/ui-strings/src/main/res/values-be/translations.xml b/libraries/ui-strings/src/main/res/values-be/translations.xml index 623ffc8363..bc59c712b9 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -171,6 +171,7 @@ "Вынікаў няма" "Няма назвы пакоя" "Па-за сеткай" + "Ліцэнзіі з адкрытым зыходным кодам" "або" "Пароль" "Людзі" 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 871e62207d..84358eda6f 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -171,6 +171,7 @@ Důvod: %1$s." "Žádné výsledky" "Žádný název místnosti" "Offline" + "Licence s otevřeným zdrojovým kódem" "nebo" "Heslo" "Lidé" 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 4b86c08ec0..50f73c4b58 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -167,6 +167,7 @@ "Κανένα αποτέλεσμα" "Χωρίς όνομα δωματίου" "Εκτός σύνδεσης" + "Άδειες ανοιχτού κώδικα" "ή" "Κωδικός πρόσβασης" "Άτομα" 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 96ff21c1e3..dd76ad3d7e 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -83,7 +83,7 @@ "Kiirvastus" "Tsiteeri" "Reageeri" - "Lükka tagasi" + "Keeldu" "Eemalda" "Vasta" "Vasta jutulõngas" @@ -151,7 +151,7 @@ Põhjus: %1$s." "Vastuseks kasutajale %1$s" "Paigalda APK-failist" "Sellist Matrix\'i kasutajatunnust ei õnnestu leida, seega sõnumit ilmselt keegi kätte ei saa." - "On lahkumas jututoast" + "Oled lahkumas jututoast" "Hele" "Link on kopeeritud lõikelauale" "Laadime…" @@ -168,6 +168,7 @@ Põhjus: %1$s." "Otsingul pole tulemusi" "Jututoal puudub nimi" "Võrgust väljas" + "Avatud lähtekoodiga litsentsid" "või" "Salasõna" "Inimesed" @@ -251,8 +252,8 @@ Põhjus: %1$s." "Sinu häälsõnumi üleslaadimine ei õnnestunud." "Sõnumit ei leidu" "Rakendusel %1$s puudub õigus sinu asukohta tuvastada. Sa saad seda lubada süsteemi seadistustest." - "Rakendusel %1$s puudub õigus sinu asukohta tuvastada. Alljärgnevas luba vastavad õigused." - "Rakendusel %1$s puudub õigus sinu nutiseadme mikrofoni kasutada. Alljärgnevas luba õigused heli salvestamiseks." + "Rakendusel %1$s puudub õigus sinu asukohta tuvastada. Järgnevalt anna vastavad õigused." + "Rakendusel %1$s puudub õigus sinu nutiseadme mikrofoni kasutada. Järgnevalt anna õigused heli salvestamiseks." "Mõned sõnumid on saatmata" "Vabandust, ilmnes viga" "🔐️ Liitu minuga rakenduses %1$s" diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index e1da839870..b0afeacc8d 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -171,6 +171,7 @@ Dôvod: %1$s." "Žiadne výsledky" "Žiadny názov miestnosti" "Offline" + "Licencie s otvoreným zdrojom" "alebo" "Heslo" "Ľudia" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 1b706457c7..c484bc6374 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -168,6 +168,7 @@ Reason: %1$s." "No results" "No room name" "Offline" + "Open source licenses" "or" "Password" "People" diff --git a/screenshots/de/features.call.impl.ui_CallScreenView_Day_1_de.png b/screenshots/de/features.call.impl.ui_CallScreenView_Day_1_de.png index 59729a12be..4264e1f48c 100644 --- a/screenshots/de/features.call.impl.ui_CallScreenView_Day_1_de.png +++ b/screenshots/de/features.call.impl.ui_CallScreenView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3eef9394f3fed83a5c62391a7e93151b9883f1be0aafb76701f1218d0661923 -size 13990 +oid sha256:e59ed451dc39c6d7dc125f5cd21864129de6c23e77163e57c615ea0cb930202e +size 14108 diff --git a/screenshots/de/features.call.impl.ui_CallScreenView_Day_2_de.png b/screenshots/de/features.call.impl.ui_CallScreenView_Day_2_de.png new file mode 100644 index 0000000000..59729a12be --- /dev/null +++ b/screenshots/de/features.call.impl.ui_CallScreenView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3eef9394f3fed83a5c62391a7e93151b9883f1be0aafb76701f1218d0661923 +size 13990 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png index cba86543a7..3cf913b9d5 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:e92d40466804770e33c961c23d749606709bb7252f6dedfd334eebf60e61b566 -size 49159 +oid sha256:256514b929049387b484ceda7997ec7631be5561a274aa26411d511f72fbc987 +size 47214 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png index f99cf139c9..49c7b1c554 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:e327207ac7abebc13c33053c41c77d8271a76cd1a2ec440759497d2b884e0835 -size 48728 +oid sha256:7f2125e0d581e5befbca1c8084d3631cfc26dfa8c4e8c6dec234654fc30bdcde +size 48458 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png index d091ca10bf..e526e752d5 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:ec02cf389448cfdea770933d0b00fc0138efe6b31a16d87a92efca363b19007d -size 47142 +oid sha256:6ecf385e4f41eb5ff79fbb9047709a4ca519f0e3a6c7be0b7117daad3f66c9ed +size 46957 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png index 6b8654fd58..39edde8520 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:ab7e53965831e084561f91457fca8a3888defaa5f8b1342843d0da076815e662 -size 50759 +oid sha256:2b2492d3e09ec386d29455a89908a6262b84dc6bc86cee0a1c9c5598f12ab647 +size 50580 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png index 275a963c05..4987b5f38d 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:e432b81e9f88ce614c5db07a93d06297619368566ac9b143f6cb95bf4606b657 -size 40216 +oid sha256:15165cbdf3439a22febfe8d551d4f89d5955f8f7853ba1b07593cc53bc2da006 +size 40297 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png index fe13ba2282..120e8a9bc5 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:61e726e8927104a3c86b87332ff4caae0116285b9aa00ad0552fe4cce6db3e27 -size 42789 +oid sha256:3560aa407a07605b60c5fb37d9c721a8acd99497651971215647d01245c6477e +size 42864 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png index 0145a8be58..8e894c4020 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:16ecb1ed76b6dc0690d0739c578150eb4d9972d301c39b3734204f4a7f9ec7ba -size 39257 +oid sha256:9baca3e0ae61b70f4ad4280e95b685dd5c76dd3e2642ac373b255d6bb5ff60f6 +size 39380 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png index 3561161c62..116aad28a8 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:d210b9f06cfeac009631949452348ee5d0ade0c15b9777945ba0022b8f051dd5 -size 47157 +oid sha256:1dac3d66d3dae4e8e29b42fa9a6a6c79ee3650fc8c5a5d3b2b5619438b8d55cb +size 47005 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png index 190b60de55..2364c410bb 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:667a579782f09bd6671a490cfa79c73a812e3ada1af25bc7ad837b84fb92644b -size 45840 +oid sha256:111e51d224660d79babcf80e9a0c08ac2cdc10397ff3931d931975a1bb271047 +size 44518 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png index 190b60de55..2364c410bb 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:667a579782f09bd6671a490cfa79c73a812e3ada1af25bc7ad837b84fb92644b -size 45840 +oid sha256:111e51d224660d79babcf80e9a0c08ac2cdc10397ff3931d931975a1bb271047 +size 44518 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png index 8e80da183f..073c96952b 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:9b79dbac297a485c296519db1e5e031e3c66020404de2083152071374d89e577 -size 45732 +oid sha256:7482cd5f41485a6f77d29b7a9385a8a4a425bdf992f5fd4ba4e59cdfc6ec597e +size 49826 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png index 7b5db59790..843356189e 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:a821a33d6d0dfb07e8674c3faea1140bd2eb75515472dcb99811e842b4458347 -size 48962 +oid sha256:da72c475bc6020bdca9213da1e3a4e4c5e4ab13743659d47da12d508fd0b13cd +size 48776 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png index a4d7d54afb..d3b62d34da 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:d649971d1dc6ba6588a22a33e27f83b6f4794507c8fe9c650af04f88f1b44a8b -size 48180 +oid sha256:c8cd1586f440dbac2453d9c6c4d8244adc4b1e6432299e4237de42b0aaf28678 +size 47835 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png index be2333b78c..3545ebca8b 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:3f37146e153a53b14fd26dff34ec2a62159faec2d0e6fe678e6125426019ae89 -size 50339 +oid sha256:5e4bee82cd03bd4cf8e20308f05769a659ea44a5b91a80fa79752badc06bd27f +size 48409 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png index f770cabd13..5d26c431f4 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:32c7c32a0a724ca1384c6348128fa802e25902b13e7d8c90c2b6f7e4d9823bc0 -size 49726 +oid sha256:2be2bc2653f3dad7919cd9938a6d5dd4da0a32fe2e3d1b2adbe39a153cbdc6ca +size 49488 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png index 2b18eda2f9..cf76507dda 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:7ee78629b97ac29eff2d7063964159f4fc0dd7c0c5d6c5d638e9dc674bc84513 -size 48224 +oid sha256:48ba72a85c9e3aaa30b2370f788931a9321c9934733e92c4d6b05ab8eeb8eef8 +size 47993 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png index 9ebdeab0b8..fe790faa60 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:a10950ab07924371690d3f3be4fcdbe95f46c0e397aabd5b860dd8e9a4f007fe -size 51731 +oid sha256:76f39a52439d7c436a60762f50c2fed5716ee17982287c96b5173112f4d5f923 +size 51512 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png index a1fc2fd766..3b44be8904 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:fcb0e7526d70968db55534795ae1bd49d510dcc03d0dc798c3dd577b451de268 -size 41330 +oid sha256:eac35951b2bed70d203ce988ec2880c154c3b4255be9eb90d21227b8612b3eef +size 41398 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png index f520901774..3256a6d41b 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:4737bc923610d0e1dea0c117b7a24581bc51c44ddfdccd22328526f5b5b34e22 -size 43994 +oid sha256:8fcac422ee971efa3fdd9f6852fda0784e74f4dea920d8a725cd900b2601422d +size 44046 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png index fa2821ed2c..ccfcd13d1b 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:974a13d25e55c5a49a967d841fb5ec9a47a5389836482cc74690cc0138631d7a -size 40059 +oid sha256:97a1e597b8486c2ccbaf069ca5fcf5bd9637938d7d78dced09c6637e1b1c73b2 +size 40165 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png index e83896b19f..e223a8729d 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:37ed17c872b3c8c9226b9c4b94eaf635a2f50481e16f9697f09ee8738a6847ee -size 48196 +oid sha256:c756a6059e4e9aef0963cab0582d81042e01002f242311f8d3c531184f5b70d8 +size 48050 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png index ef5e33b916..e8c5dd2bc0 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:d58304b8964e081ffebe09fd1e8517499290c9998d93236a101e0644917a458b -size 46695 +oid sha256:4da3163d10eeecf66ce10111df8cae57e2912d37a8895ea4ee756671531ee9fd +size 45315 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png index ef5e33b916..e8c5dd2bc0 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:d58304b8964e081ffebe09fd1e8517499290c9998d93236a101e0644917a458b -size 46695 +oid sha256:4da3163d10eeecf66ce10111df8cae57e2912d37a8895ea4ee756671531ee9fd +size 45315 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png index 20c5194060..10750e39a8 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:cf1c5453366e54885eb3f7e2e06e4e0970f875d65b3130c4a33cb5712a6e19b9 -size 46806 +oid sha256:5544bf3d6994a10391c8d3002352916d1c238bb9a39df423c7c693950fda4d0f +size 50928 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png index a40070f58a..2e920ce60d 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:bd10955671a04c607f485a0abbd608bcc5061020f51f8d8b81e1d534f30de7a1 -size 50097 +oid sha256:f306fc60621f92e205f128e576ae91db0d8f8c59aec8bbca90b5e49efbd348e2 +size 49878 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png index a96678c8d1..bd9cc39c3d 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:c93cd2fd727c335867563761832985b08d83a3c4f1d0dc4b71f70c321a18336b -size 49205 +oid sha256:9f0dab74d2541fc73d6a3c8c0fba4e776ce8a061b39137907ec2df09c90c48af +size 48820 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png index 03fe84cdc1..4922cd0dce 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0514fe5e119340252693d43cad99aaec3df872f05bf488e3e9e35e509b5d6ce0 -size 22051 +oid sha256:b47212f7091cad82b08c895973878350e6811abbef213a15cf8212f63bee6383 +size 22063 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png index 81ec190291..f8778b2b5f 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eda5714ef4ef483e6f59af3a0f705fdd3e8423413728c0d38d2fd35679311ac0 -size 19771 +oid sha256:1b7852461279a2f92f42617050f6e024bba153a231c762c09165886fbfb4ff1a +size 19789 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png index 53dc865afa..889c1c18a9 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7583b7c9a9221b3687fd38cb050f0051066583aa95fe40e57a30918fa49f6d56 -size 22428 +oid sha256:3fd20e8a91b2c1d779c5a92ea7fc7ad74ea90810473abf1cef55ae234eee27dc +size 22448 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png index 5671164c6e..b15f376b95 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3d0957ca5bd3e41616c3a559f3e0df6dc4c3d09eaf10a82a9452e453a46e6e1 -size 22941 +oid sha256:632eb318c56cf3854ff8eaf030b2b3d5660b0c485337ea6ffe4fe00c6e68ad72 +size 22957 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png index c3ab1d9ad9..f434835da5 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61f85634bfdd266220e65e9d78f0d187ad4cf70bdfc5b47e7d4aba797bf07cb2 -size 23090 +oid sha256:d24df97d7a01dbdb981a74b322de36e278f8d071f56eaa7cfbb60d857f13b3d8 +size 23100 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png index 03fe84cdc1..4922cd0dce 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0514fe5e119340252693d43cad99aaec3df872f05bf488e3e9e35e509b5d6ce0 -size 22051 +oid sha256:b47212f7091cad82b08c895973878350e6811abbef213a15cf8212f63bee6383 +size 22063 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index d07d94b26d..5dfdc7048c 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,39 +1,39 @@ // 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",19909,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",19916,], ["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",19909,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",19909,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",19909,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",19909,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",19916,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",19916,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",19916,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",19916,], ["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.login.impl.accountprovider_AccountProviderView_Day_4_en","features.login.impl.accountprovider_AccountProviderView_Night_4_en",0,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",19909,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",19909,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",19909,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",19909,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",19909,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",19909,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",19909,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",19909,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",19909,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",19909,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",19909,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",19909,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",19916,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",19916,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",19916,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",19916,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",19916,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",19916,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",19916,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",19916,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",19916,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",19916,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",19916,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",19916,], ["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",19909,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",19916,], ["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",19909,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",19916,], ["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",19909,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",19916,], ["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",19909,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",19916,], ["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,], @@ -43,11 +43,11 @@ 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","",19909,], -["features.messages.impl.attachments.preview_AttachmentsView_1_en","",19909,], -["features.messages.impl.attachments.preview_AttachmentsView_2_en","",19909,], -["features.messages.impl.attachments.preview_AttachmentsView_3_en","",19909,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",19909,], +["features.messages.impl.attachments.preview_AttachmentsView_0_en","",19916,], +["features.messages.impl.attachments.preview_AttachmentsView_1_en","",19916,], +["features.messages.impl.attachments.preview_AttachmentsView_2_en","",19916,], +["features.messages.impl.attachments.preview_AttachmentsView_3_en","",19916,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",19916,], ["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,], @@ -127,13 +127,13 @@ export const screenshots = [ ["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",19909,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",19909,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",19909,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",19909,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",19909,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",19909,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",19909,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",19916,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",19916,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",19916,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",19916,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",19916,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",19916,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",19916,], ["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,], @@ -144,81 +144,82 @@ 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",19909,], -["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",19909,], -["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",19909,], -["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",19909,], -["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",19909,], +["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",19916,], +["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",19916,], +["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",19916,], +["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",19916,], +["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",19916,], ["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.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",19909,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",19909,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",19909,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",19909,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",19916,], +["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",19919,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",19916,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",19916,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",19916,], ["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",19909,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",19909,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",19916,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",19916,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",19909,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",19916,], ["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.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_ConfigureRoomView_Day_0_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_0_en",19909,], -["features.createroom.impl.configureroom_ConfigureRoomView_Day_1_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_1_en",19909,], +["features.createroom.impl.configureroom_ConfigureRoomView_Day_0_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_0_en",19916,], +["features.createroom.impl.configureroom_ConfigureRoomView_Day_1_en","features.createroom.impl.configureroom_ConfigureRoomView_Night_1_en",19916,], ["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",19909,], -["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",19909,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",19916,], +["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",19916,], ["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",19909,], -["features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Day_0_en","features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Night_0_en",19909,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",19909,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",19909,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",19909,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",19909,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",19909,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",19909,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",19909,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",19909,], -["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",19909,], -["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",19909,], -["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",19909,], -["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",19909,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime pickers_en","",19909,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime pickers_en","",19909,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",19916,], +["features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Day_0_en","features.securebackup.impl.createkey_CreateNewRecoveryKeyView_Night_0_en",19916,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",19916,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",19916,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",19916,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",19916,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",19916,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",19916,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",19916,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",19916,], +["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",19916,], +["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",19916,], +["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",19916,], +["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",19916,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime pickers_en","",19916,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime pickers_en","",19916,], ["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",19909,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",19909,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",19909,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",19916,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",19916,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",19916,], ["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",19909,], -["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",19909,], -["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",19909,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",19909,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",19909,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",19909,], -["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",19909,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",19916,], +["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",19916,], +["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",19916,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",19916,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",19916,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",19916,], +["libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Day_0_en","libraries.designsystem.atomic.molecules_DialogLikeBannerMolecule_Night_0_en",19916,], ["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,], @@ -230,12 +231,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",19909,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",19909,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",19909,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",19909,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",19909,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",19909,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",19916,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",19916,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",19916,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",19916,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",19916,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",19916,], ["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,], @@ -245,10 +246,10 @@ 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,], -["features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Day_0_en","features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Night_0_en",19909,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",19909,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",19909,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",19909,], +["features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Day_0_en","features.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_Night_0_en",19916,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",19916,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",19916,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",19916,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], ["libraries.designsystem.theme.components_FilledButtonLarge_Buttons_en","",0,], @@ -257,15 +258,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",19909,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",19909,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",19909,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",19916,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",19916,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",19916,], ["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",19909,], -["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",19909,], +["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",19916,], +["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",19916,], ["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,], @@ -292,36 +293,37 @@ export const screenshots = [ ["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",19909,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",19916,], ["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",19909,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",19916,], ["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",19909,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",19916,], ["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",19909,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",19916,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",19909,], -["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",19909,], +["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",0,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",19916,], +["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",19916,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], ["libraries.designsystem.components_LabelledOutlinedTextField_Day_0_en","libraries.designsystem.components_LabelledOutlinedTextField_Night_0_en",0,], ["libraries.designsystem.components_LabelledTextField_Day_0_en","libraries.designsystem.components_LabelledTextField_Night_0_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",19909,], -["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",19909,], -["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",19909,], -["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",19909,], -["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",19909,], -["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",19909,], +["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",19916,], +["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",19916,], +["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",19916,], +["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",19916,], +["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",19916,], +["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",19916,], ["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,], @@ -372,28 +374,28 @@ 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",19909,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",19909,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",19909,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",19909,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",19916,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",19916,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",19916,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",19916,], ["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",19909,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",19909,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",19909,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",19909,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",19909,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",19909,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",19909,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",19909,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",19909,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",19909,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",19909,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",19909,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",19909,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",19909,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",19909,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",19916,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",19916,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",19916,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",19916,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",19916,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",19916,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",19916,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",19916,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",19916,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",19916,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",19916,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",19916,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",19916,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",19916,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",19916,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",19909,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",19916,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], ["libraries.matrix.ui.components_MatrixUserHeaderPlaceholder_Day_0_en","libraries.matrix.ui.components_MatrixUserHeaderPlaceholder_Night_0_en",0,], ["libraries.matrix.ui.components_MatrixUserHeader_Day_0_en","libraries.matrix.ui.components_MatrixUserHeader_Night_0_en",0,], @@ -403,7 +405,7 @@ export const screenshots = [ ["libraries.mediaviewer.api.viewer_MediaViewerView_0_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_10_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.api.viewer_MediaViewerView_2_en","",19909,], +["libraries.mediaviewer.api.viewer_MediaViewerView_2_en","",19916,], ["libraries.mediaviewer.api.viewer_MediaViewerView_3_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_4_en","",0,], ["libraries.mediaviewer.api.viewer_MediaViewerView_5_en","",0,], @@ -413,10 +415,10 @@ export const screenshots = [ ["libraries.mediaviewer.api.viewer_MediaViewerView_9_en","",0,], ["libraries.designsystem.theme.components_MediumTopAppBar_App Bars_en","",0,], ["libraries.textcomposer.mentions_MentionSpan_Day_0_en","libraries.textcomposer.mentions_MentionSpan_Night_0_en",0,], -["features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en","features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en",19909,], +["features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en","features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en",19916,], ["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",19909,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",19916,], ["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,], @@ -440,25 +442,25 @@ 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.typing_MessagesViewWithTyping_Day_0_en","features.messages.impl.typing_MessagesViewWithTyping_Night_0_en",19909,], -["features.messages.impl.typing_MessagesViewWithTyping_Day_1_en","features.messages.impl.typing_MessagesViewWithTyping_Night_1_en",19909,], +["features.messages.impl.typing_MessagesViewWithTyping_Day_0_en","features.messages.impl.typing_MessagesViewWithTyping_Night_0_en",19916,], +["features.messages.impl.typing_MessagesViewWithTyping_Day_1_en","features.messages.impl.typing_MessagesViewWithTyping_Night_1_en",19916,], ["features.messages.impl.typing_MessagesViewWithTyping_Day_2_en","features.messages.impl.typing_MessagesViewWithTyping_Night_2_en",0,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",19909,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",19909,], -["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",19909,], -["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",19909,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",19916,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",19916,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",19916,], +["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",19916,], ["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",0,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",19909,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",19909,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",19909,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",19909,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",19909,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",19909,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",19909,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",19909,], -["features.roomlist.impl.migration_MigrationScreenView_Day_0_en","features.roomlist.impl.migration_MigrationScreenView_Night_0_en",19909,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",19916,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",19916,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",19916,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",19916,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",19916,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",19916,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",19916,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",19916,], +["features.roomlist.impl.migration_MigrationScreenView_Day_0_en","features.roomlist.impl.migration_MigrationScreenView_Night_0_en",19916,], ["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",19909,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",19916,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom Sheets_en","",0,], ["appicon.element_MonochromeIcon_en","",0,], @@ -467,28 +469,28 @@ 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.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",19909,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",19909,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",19909,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",19916,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",19916,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",19916,], ["features.login.impl.oidc.webview_OidcView_Day_0_en","features.login.impl.oidc.webview_OidcView_Night_0_en",0,], ["features.login.impl.oidc.webview_OidcView_Day_1_en","features.login.impl.oidc.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",19909,], -["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",19909,], -["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",19909,], -["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",19909,], -["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",19909,], +["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",19916,], +["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",19916,], +["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",19916,], +["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",19916,], +["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",19916,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonMedium_Buttons_en","",0,], @@ -501,47 +503,47 @@ export const screenshots = [ ["libraries.designsystem.components_PageTitleWithIconFull_Day_3_en","libraries.designsystem.components_PageTitleWithIconFull_Night_3_en",0,], ["libraries.designsystem.components_PageTitleWithIconFull_Day_4_en","libraries.designsystem.components_PageTitleWithIconFull_Night_4_en",0,], ["libraries.designsystem.components_PageTitleWithIconMinimal_Day_0_en","libraries.designsystem.components_PageTitleWithIconMinimal_Night_0_en",0,], -["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",19909,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",19909,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",19909,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",19909,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",19909,], +["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",19916,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",19916,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",19916,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",19916,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",19916,], ["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",19909,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",19909,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",19909,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",19916,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",19916,], ["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",19909,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",19909,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",19909,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",19909,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",19909,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",19916,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",19916,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",19916,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",19916,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",19916,], ["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",19909,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",19909,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",19909,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",19909,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",19909,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",19909,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",19909,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",19909,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",19909,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",19909,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",19909,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",19916,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",19916,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",19916,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",19916,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",19916,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",19916,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",19916,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",19916,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",19916,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",19916,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",19916,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceDivider_Preferences_en","",0,], @@ -557,181 +559,181 @@ 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","",19909,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",19909,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",19909,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",19909,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",19916,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",19916,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",19916,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",19916,], ["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","",19909,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",19909,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",19909,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",19909,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",19909,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",19909,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",19909,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",19909,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",19909,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",19909,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",19909,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",19909,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",19916,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",19916,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",19916,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",19916,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",19916,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",19916,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",19916,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",19916,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",19916,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",19916,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",19916,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",19916,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",19909,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",19909,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",19916,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",19916,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",19909,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",19909,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",19909,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",19909,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",19909,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",19909,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",19909,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",19916,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",19916,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",19916,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",19916,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",19916,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",19916,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",19916,], ["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",19909,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",19909,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",19909,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",19909,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",19909,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",19909,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",19909,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",19909,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",19909,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",19916,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",19916,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",19916,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",19916,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",19916,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",19916,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",19916,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",19916,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",19916,], ["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",19909,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",19916,], ["features.roomdetails.impl.components_RoomBadgeNegative_Day_0_en","features.roomdetails.impl.components_RoomBadgeNegative_Night_0_en",0,], ["features.roomdetails.impl.components_RoomBadgeNeutral_Day_0_en","features.roomdetails.impl.components_RoomBadgeNeutral_Night_0_en",0,], ["features.roomdetails.impl.components_RoomBadgePositive_Day_0_en","features.roomdetails.impl.components_RoomBadgePositive_Night_0_en",0,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",19909,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",19909,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",19909,], -["features.roomdetails.impl_RoomDetails_0_en","",19909,], -["features.roomdetails.impl_RoomDetails_10_en","",19909,], -["features.roomdetails.impl_RoomDetails_11_en","",19909,], -["features.roomdetails.impl_RoomDetails_12_en","",19909,], -["features.roomdetails.impl_RoomDetails_1_en","",19909,], -["features.roomdetails.impl_RoomDetails_2_en","",19909,], -["features.roomdetails.impl_RoomDetails_3_en","",19909,], -["features.roomdetails.impl_RoomDetails_4_en","",19909,], -["features.roomdetails.impl_RoomDetails_5_en","",19909,], -["features.roomdetails.impl_RoomDetails_6_en","",19909,], -["features.roomdetails.impl_RoomDetails_7_en","",19909,], -["features.roomdetails.impl_RoomDetails_8_en","",19909,], -["features.roomdetails.impl_RoomDetails_9_en","",19909,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",19909,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",19909,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",19909,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",19909,], -["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",19909,], -["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",19909,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",19916,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",19916,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",19916,], +["features.roomdetails.impl_RoomDetails_0_en","",19916,], +["features.roomdetails.impl_RoomDetails_10_en","",19916,], +["features.roomdetails.impl_RoomDetails_11_en","",19916,], +["features.roomdetails.impl_RoomDetails_12_en","",19916,], +["features.roomdetails.impl_RoomDetails_1_en","",19916,], +["features.roomdetails.impl_RoomDetails_2_en","",19916,], +["features.roomdetails.impl_RoomDetails_3_en","",19916,], +["features.roomdetails.impl_RoomDetails_4_en","",19916,], +["features.roomdetails.impl_RoomDetails_5_en","",19916,], +["features.roomdetails.impl_RoomDetails_6_en","",19916,], +["features.roomdetails.impl_RoomDetails_7_en","",19916,], +["features.roomdetails.impl_RoomDetails_8_en","",19916,], +["features.roomdetails.impl_RoomDetails_9_en","",19916,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",19916,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",19916,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",19916,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",19916,], +["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",19916,], +["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",19916,], ["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",19909,], -["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",19909,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",19909,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",19909,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",19909,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",19909,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",19909,], +["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",19916,], +["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",19916,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",19916,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",19916,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",19916,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",19916,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",19916,], ["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",19909,], -["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",19909,], -["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",19909,], +["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",19916,], +["features.roomlist.impl.search_RoomListSearchContent_Day_2_en","features.roomlist.impl.search_RoomListSearchContent_Night_2_en",19916,], +["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",19916,], ["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",0,], -["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",19909,], -["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",19909,], -["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",19909,], -["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",19909,], -["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",19909,], -["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",19909,], -["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",19909,], +["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",19916,], +["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",19916,], +["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",19916,], +["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",19916,], +["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",19916,], +["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",19916,], +["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",19916,], ["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",19909,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",19909,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",19909,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",19909,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",19909,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",19909,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",19909,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",19909,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",19909,], +["features.roomlist.impl_RoomListView_Day_9_en","features.roomlist.impl_RoomListView_Night_9_en",19916,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",19916,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",19916,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",19916,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",19916,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",19916,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",19916,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",19916,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",19916,], ["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",19909,], -["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",19909,], +["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",19916,], +["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",19916,], ["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",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",19909,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",19909,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",19916,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",19916,], ["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",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",19909,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",19909,], -["features.createroom.impl.components_RoomPrivacyOption_Day_0_en","features.createroom.impl.components_RoomPrivacyOption_Night_0_en",19909,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",19909,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",19909,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",19909,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",19909,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",19909,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",19909,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",19916,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",19916,], +["features.createroom.impl.components_RoomPrivacyOption_Day_0_en","features.createroom.impl.components_RoomPrivacyOption_Night_0_en",19916,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",19916,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",19916,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",19916,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",19916,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",19916,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",19916,], ["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,], @@ -754,10 +756,10 @@ 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",19909,], -["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",19909,], -["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",19909,], -["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",19909,], +["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",19916,], +["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",19916,], +["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",19916,], +["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",19916,], ["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,], @@ -765,64 +767,64 @@ 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",19909,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",19909,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",19909,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",19916,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",19916,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",19916,], ["appicon.enterprise_RoundIcon_en","",0,], ["appicon.element_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",19909,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",19909,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",19909,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",19916,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",19916,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",19916,], ["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","",19909,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search views_en","",19916,], ["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","",19909,], -["features.createroom.impl.components_SearchSingleUserResultItem_en","",19909,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",19909,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",19909,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",19909,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",19909,], -["features.securebackup.impl.enable_SecureBackupEnableView_Day_0_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_0_en",19909,], -["features.securebackup.impl.enable_SecureBackupEnableView_Day_1_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_1_en",19909,], -["features.securebackup.impl.enable_SecureBackupEnableView_Day_2_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_2_en",19909,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",19909,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",19909,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",19909,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",19909,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",19909,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",19909,], +["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",19916,], +["features.createroom.impl.components_SearchSingleUserResultItem_en","",19916,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",19916,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",19916,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",19916,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",19916,], +["features.securebackup.impl.enable_SecureBackupEnableView_Day_0_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_0_en",19916,], +["features.securebackup.impl.enable_SecureBackupEnableView_Day_1_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_1_en",19916,], +["features.securebackup.impl.enable_SecureBackupEnableView_Day_2_en","features.securebackup.impl.enable_SecureBackupEnableView_Night_2_en",19916,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",19916,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",19916,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",19916,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",19916,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",19916,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",19916,], ["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_SelectedUserCannotRemove_Day_0_en","libraries.matrix.ui.components_SelectedUserCannotRemove_Night_0_en",0,], ["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",19909,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",19909,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",19909,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",19909,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",19909,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",19916,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",19916,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",19916,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",19916,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",19916,], ["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,], @@ -832,37 +834,37 @@ 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.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",19909,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",19909,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",19909,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",19909,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",19909,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",19909,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",19916,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",19916,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",19916,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",19916,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",19916,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",19916,], ["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",19909,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",19916,], ["features.messages.impl.actionlist_SheetContent_Day_0_en","features.messages.impl.actionlist_SheetContent_Night_0_en",0,], ["features.messages.impl.timeline.components.reactionsummary_SheetContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_SheetContent_Night_0_en",0,], -["features.messages.impl.actionlist_SheetContent_Day_10_en","features.messages.impl.actionlist_SheetContent_Night_10_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_10_en","features.messages.impl.actionlist_SheetContent_Night_10_en",19916,], ["features.messages.impl.actionlist_SheetContent_Day_1_en","features.messages.impl.actionlist_SheetContent_Night_1_en",0,], -["features.messages.impl.actionlist_SheetContent_Day_2_en","features.messages.impl.actionlist_SheetContent_Night_2_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_3_en","features.messages.impl.actionlist_SheetContent_Night_3_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_4_en","features.messages.impl.actionlist_SheetContent_Night_4_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_5_en","features.messages.impl.actionlist_SheetContent_Night_5_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_6_en","features.messages.impl.actionlist_SheetContent_Night_6_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_7_en","features.messages.impl.actionlist_SheetContent_Night_7_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_8_en","features.messages.impl.actionlist_SheetContent_Night_8_en",19909,], -["features.messages.impl.actionlist_SheetContent_Day_9_en","features.messages.impl.actionlist_SheetContent_Night_9_en",19909,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",19909,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",19909,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",19909,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",19909,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",19909,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",19909,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",19909,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",19909,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",19909,], +["features.messages.impl.actionlist_SheetContent_Day_2_en","features.messages.impl.actionlist_SheetContent_Night_2_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_3_en","features.messages.impl.actionlist_SheetContent_Night_3_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_4_en","features.messages.impl.actionlist_SheetContent_Night_4_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_5_en","features.messages.impl.actionlist_SheetContent_Night_5_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_6_en","features.messages.impl.actionlist_SheetContent_Night_6_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_7_en","features.messages.impl.actionlist_SheetContent_Night_7_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_8_en","features.messages.impl.actionlist_SheetContent_Night_8_en",19916,], +["features.messages.impl.actionlist_SheetContent_Day_9_en","features.messages.impl.actionlist_SheetContent_Night_9_en",19916,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",19916,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",19916,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",19916,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",19916,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",19916,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",19916,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",19916,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",19916,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",19916,], ["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,], @@ -871,7 +873,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",19909,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",19916,], ["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,], @@ -881,34 +883,34 @@ 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",19909,], +["features.location.api.internal_StaticMapPlaceholder_Day_1_en","features.location.api.internal_StaticMapPlaceholder_Night_1_en",19916,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], ["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",19909,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",19916,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",19909,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",19909,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",19909,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",19909,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",19909,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",19909,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",19909,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",19916,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",19916,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",19916,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",19916,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",19916,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",19916,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",19916,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], ["libraries.designsystem.theme.components_TextFieldDark_TextFields_en","",0,], @@ -920,38 +922,38 @@ export const screenshots = [ ["libraries.designsystem.theme.components_TextFieldValueTextFieldDark_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","",19909,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime pickers_en","",19909,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime pickers_en","",19909,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime pickers_en","",19916,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime pickers_en","",19916,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime pickers_en","",19916,], ["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",19909,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",19909,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",19916,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",19916,], ["features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_1_en",0,], ["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_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",19916,], ["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",19909,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",19909,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",19916,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",19916,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",19916,], ["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,], ["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",19909,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",19909,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",19916,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",19916,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",19916,], ["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",19909,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",19916,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",19916,], ["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,], @@ -960,36 +962,36 @@ 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",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",19916,], ["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",19909,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",19916,], ["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","",19909,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",19916,], ["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_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",19909,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",19916,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",19916,], ["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_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",19909,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",19916,], ["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",19909,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",19909,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",19909,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",19909,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",19916,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",19916,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",19916,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",19916,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",19916,], ["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",19909,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",19909,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",19916,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",19916,], ["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",19909,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",19916,], ["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,], @@ -998,8 +1000,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",19909,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",19909,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",19916,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",19916,], ["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,], @@ -1011,7 +1013,7 @@ 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",19909,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",19916,], ["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,], @@ -1033,79 +1035,79 @@ 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_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",19916,], ["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",19909,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",19909,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",19909,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",19909,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",19909,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",19909,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",19909,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",19916,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",19916,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",19916,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",19916,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",19916,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",19916,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",19916,], ["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",19909,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",19916,], ["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",19909,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",19916,], ["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",19909,], +["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",19916,], ["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",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",19909,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",19909,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",19916,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",19916,], ["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",19909,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",19909,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",19909,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",19909,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",19909,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",19909,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",19916,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",19916,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",19916,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",19916,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",19916,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",19916,], ["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","",19909,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",19916,], ["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",19909,], -["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",19909,], -["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",19909,], -["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",19909,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",19916,], +["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",19916,], +["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",19916,], +["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",19916,], ["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",19909,], +["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",19916,], ["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",19909,], +["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",19916,], ["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",0,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",19909,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",19909,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",19909,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",19909,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",19909,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",19909,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",19909,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",19909,], -["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_0_en","features.verifysession.impl_VerifySelfSessionView_Night_0_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_1_en","features.verifysession.impl_VerifySelfSessionView_Night_1_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_2_en","features.verifysession.impl_VerifySelfSessionView_Night_2_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_3_en","features.verifysession.impl_VerifySelfSessionView_Night_3_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_4_en","features.verifysession.impl_VerifySelfSessionView_Night_4_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_5_en","features.verifysession.impl_VerifySelfSessionView_Night_5_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_6_en","features.verifysession.impl_VerifySelfSessionView_Night_6_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_7_en","features.verifysession.impl_VerifySelfSessionView_Night_7_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_8_en","features.verifysession.impl_VerifySelfSessionView_Night_8_en",19909,], -["features.verifysession.impl_VerifySelfSessionView_Day_9_en","features.verifysession.impl_VerifySelfSessionView_Night_9_en",19909,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",19916,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",19916,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",19916,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",19916,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",19916,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",19916,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",19916,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",19916,], +["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_0_en","features.verifysession.impl_VerifySelfSessionView_Night_0_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_1_en","features.verifysession.impl_VerifySelfSessionView_Night_1_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_2_en","features.verifysession.impl_VerifySelfSessionView_Night_2_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_3_en","features.verifysession.impl_VerifySelfSessionView_Night_3_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_4_en","features.verifysession.impl_VerifySelfSessionView_Night_4_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_5_en","features.verifysession.impl_VerifySelfSessionView_Night_5_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_6_en","features.verifysession.impl_VerifySelfSessionView_Night_6_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_7_en","features.verifysession.impl_VerifySelfSessionView_Night_7_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_8_en","features.verifysession.impl_VerifySelfSessionView_Night_8_en",19916,], +["features.verifysession.impl_VerifySelfSessionView_Day_9_en","features.verifysession.impl_VerifySelfSessionView_Night_9_en",19916,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], @@ -1119,12 +1121,12 @@ export const screenshots = [ ["libraries.textcomposer.components_VoiceMessageRecorderButton_Day_0_en","libraries.textcomposer.components_VoiceMessageRecorderButton_Night_0_en",0,], ["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,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_0_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_0_en",19909,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_1_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_1_en",19909,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_2_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_2_en",19909,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_3_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_3_en",19909,], -["features.login.impl.screens.waitlistscreen_WaitListView_Day_4_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_4_en",19909,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_0_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_0_en",19916,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_1_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_1_en",19916,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_2_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_2_en",19916,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_3_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_3_en",19916,], +["features.login.impl.screens.waitlistscreen_WaitListView_Day_4_en","features.login.impl.screens.waitlistscreen_WaitListView_Night_4_en",19916,], ["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",19909,], +["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",19916,], ["libraries.designsystem.ruler_WithRulers_Day_0_en","libraries.designsystem.ruler_WithRulers_Night_0_en",0,], ]; From 77630a7a06e912a559935f787ba2f8b3d3fe5eac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:15:10 +0000 Subject: [PATCH 053/115] Update telephoto to v0.12.0 (#3191) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff6aec12ec..68a335e331 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ showkase = "1.0.3" appyx = "1.4.0" sqldelight = "2.0.2" wysiwyg = "2.37.5" -telephoto = "0.11.2" +telephoto = "0.12.0" # DI dagger = "2.51.1" From 7ae5e996433fa0e90460eb9ba16c25b47c0d3628 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:11:30 +0200 Subject: [PATCH 054/115] Update dependency com.squareup:kotlinpoet to v1.18.1 (#3194) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb4a41facc..ea24410701 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -180,7 +180,7 @@ maplibre = "org.maplibre.gl:android-sdk:11.0.1" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.0" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.0" opusencoder = "io.element.android:opusencoder:1.1.0" -kotlinpoet = "com.squareup:kotlinpoet:1.18.0" +kotlinpoet = "com.squareup:kotlinpoet:1.18.1" zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics From d758984941dc5c04a00f6c0f6f2054082f36bec8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Jul 2024 15:20:18 +0200 Subject: [PATCH 055/115] Update gradle using command line: ./gradlew wrapper --gradle-version 8.9 --gradle-distribution-sha256-sum d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4094e92b2d..68e8816d71 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=f8b4f4772d302c8ff580bc40d0f56e715de69b163546944f787c87abf209c961 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From d27e86f3963debc9b887f32513d7a1d082cd09f8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Jul 2024 16:47:24 +0200 Subject: [PATCH 056/115] Call in PiP: add feature flag, disabled in release builds. --- features/call/impl/build.gradle.kts | 1 + .../features/call/impl/pip/PipSupportProvider.kt | 13 +++++++++++-- .../libraries/featureflag/api/FeatureFlags.kt | 7 +++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/features/call/impl/build.gradle.kts b/features/call/impl/build.gradle.kts index b2d845a003..50c76b3b55 100644 --- a/features/call/impl/build.gradle.kts +++ b/features/call/impl/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.core) implementation(projects.libraries.designsystem) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.impl) implementation(projects.libraries.matrixui) implementation(projects.libraries.network) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt index 16dcb8d66c..6cf3f080ad 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt @@ -24,6 +24,9 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import kotlinx.coroutines.runBlocking import javax.inject.Inject interface PipSupportProvider { @@ -34,9 +37,15 @@ interface PipSupportProvider { @ContributesBinding(AppScope::class) class DefaultPipSupportProvider @Inject constructor( @ApplicationContext private val context: Context, + private val featureFlagService: FeatureFlagService, ) : PipSupportProvider { override fun isPipSupported(): Boolean { - val hasSystemFeaturePip = context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse() - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasSystemFeaturePip + val isSupportedByTheOs = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse() + return if (isSupportedByTheOs) { + runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.PictureInPicture) } + } else { + false + } } } 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 17ea03f7a9..616a549e63 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 @@ -113,4 +113,11 @@ enum class FeatureFlags( defaultValue = { true }, isFinished = false, ), + PictureInPicture( + key = "feature.pictureInPicture", + title = "Picture in Picture for Calls", + description = "Allow the Call to be rendered in PiP mode", + defaultValue = { it.buildType != BuildType.RELEASE }, + isFinished = false, + ), } From 497f5d9f381306df81c78a1b7caf8997ca9df274 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Jul 2024 16:45:57 +0200 Subject: [PATCH 057/115] Call in PiP: when closing PiP, hang up the call. The Activity is actually not destroyed as I expected. --- .../features/call/impl/ui/ElementCallActivity.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index b0d41efe9e..3af6c7cf95 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -32,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.mutableStateOf import androidx.core.content.IntentCompat +import androidx.lifecycle.Lifecycle import io.element.android.features.call.api.CallType import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings @@ -41,6 +42,7 @@ import io.element.android.features.call.impl.utils.CallIntentDataParser import io.element.android.libraries.architecture.bindings import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import timber.log.Timber import javax.inject.Inject class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { @@ -63,6 +65,8 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { private var isDarkMode = false private val webViewTarget = mutableStateOf(null) + private var eventSink: ((CallScreenEvents) -> Unit)? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -91,6 +95,7 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { val pipState = pictureInPicturePresenter.present() ElementThemeApp(appPreferencesStore) { val state = presenter.present() + eventSink = state.eventSink CallScreenView( state = state, pipState = pipState, @@ -111,6 +116,11 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) pictureInPicturePresenter.onPictureInPictureModeChanged(isInPictureInPictureMode) + + if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + Timber.d("Exiting PiP mode: Hangup the call") + eventSink?.invoke(CallScreenEvents.Hangup) + } } override fun onNewIntent(intent: Intent) { From 15d4782aa638e27d627befb6529bf19b9fd3f12e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Jul 2024 17:04:17 +0200 Subject: [PATCH 058/115] When PiP is supported, use Back for top left icon instead of Close. #3197 --- .../io/element/android/features/call/impl/ui/CallScreenView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt index c8d202f8cc..71e6fd0e7b 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt @@ -81,7 +81,7 @@ internal fun CallScreenView( title = { Text(stringResource(R.string.element_call)) }, navigationIcon = { BackButton( - imageVector = CompoundIcons.Close(), + imageVector = if (pipState.supportPip) CompoundIcons.ArrowLeft() else CompoundIcons.Close(), onClick = ::handleBack, ) } From a0b86797768ea13de31d2a4deae2f92d01b18f2f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Jul 2024 17:07:50 +0200 Subject: [PATCH 059/115] Add preview for Call screen in different PipState. --- .../call/impl/pip/PictureInPictureStateProvider.kt | 10 ++++++++++ .../android/features/call/impl/ui/CallScreenView.kt | 13 +++++++++++++ .../android/tests/konsist/KonsistPreviewTest.kt | 1 + 3 files changed, 24 insertions(+) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt index 360ee54d3f..8270e27e7d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt @@ -16,6 +16,16 @@ package io.element.android.features.call.impl.pip +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class PictureInPictureStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aPictureInPictureState(supportPip = true), + aPictureInPictureState(supportPip = true, isInPictureInPicture = true), + ) +} + fun aPictureInPictureState( supportPip: Boolean = false, isInPictureInPicture: Boolean = false, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt index 71e6fd0e7b..70ab2e30c2 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt @@ -38,6 +38,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.call.impl.R import io.element.android.features.call.impl.pip.PictureInPictureEvents import io.element.android.features.call.impl.pip.PictureInPictureState +import io.element.android.features.call.impl.pip.PictureInPictureStateProvider import io.element.android.features.call.impl.pip.aPictureInPictureState import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor import io.element.android.libraries.architecture.AsyncData @@ -195,3 +196,15 @@ internal fun CallScreenViewPreview( requestPermissions = { _, _ -> }, ) } + +@PreviewsDayNight +@Composable +internal fun CallScreenPipViewPreview( + @PreviewParameter(PictureInPictureStateProvider::class) state: PictureInPictureState, +) = ElementPreview { + CallScreenView( + state = aCallScreenState(), + pipState = state, + requestPermissions = { _, _ -> }, + ) +} 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 52ae3660c6..ccf6754fc5 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 @@ -72,6 +72,7 @@ class KonsistPreviewTest { "AsyncIndicatorLoadingPreview", "BloomInitialsPreview", "BloomPreview", + "CallScreenPipViewPreview", "ColorAliasesPreview", "DefaultRoomListTopBarWithIndicatorPreview", "GradientFloatingActionButtonCircleShapePreview", From 788a20c24a551e5845a2dd22955a8f9f2218aff8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:24:01 +0000 Subject: [PATCH 060/115] Update dependency io.mockk:mockk to v1.13.12 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ea24410701..d0dd4eb923 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -138,7 +138,7 @@ test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" } 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.1" -test_mockk = "io.mockk:mockk:1.13.11" +test_mockk = "io.mockk:mockk:1.13.12" test_konsist = "com.lemonappdev:konsist:0.15.1" test_turbine = "app.cash.turbine:turbine:1.1.0" test_truth = "com.google.truth:truth:1.4.4" From 9c42c2d46d7e21b0d3fda9dd63112da0294615d7 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 15 Jul 2024 15:31:46 +0000 Subject: [PATCH 061/115] Update screenshots --- .../features.call.impl.ui_CallScreenPipView_Day_0_en.png | 3 +++ .../features.call.impl.ui_CallScreenPipView_Day_1_en.png | 3 +++ .../features.call.impl.ui_CallScreenPipView_Night_0_en.png | 3 +++ .../features.call.impl.ui_CallScreenPipView_Night_1_en.png | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_0_en.png new file mode 100644 index 0000000000..42d7e86fd0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b61b391f6380189603939badc2ae5912f5cb072200a001ff946af2d52e81b95a +size 12734 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_1_en.png new file mode 100644 index 0000000000..8d0995fd9f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7696eb0f5bf8698d001ea44be6eb2005f414057f9a65703e6d987e8eb75f7f94 +size 9441 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_0_en.png new file mode 100644 index 0000000000..ba938a8d88 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3d14805d85b33ce1f0fdd26eec433db1973924542cd85a3bb0e4514cdcd857d +size 12392 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_1_en.png new file mode 100644 index 0000000000..f8879f9ecb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_CallScreenPipView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:146fc2ce4d344c0ea947fe0370e6b8d94c2e724c69c01c2cc3476a756e1f09e4 +size 9315 From 2ff5fa67fc683d6ae93ad65547c4da8362e0b856 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 15 Jul 2024 18:27:59 +0200 Subject: [PATCH 062/115] Restore intentional mentions in the markdown/plain text editor (#3193) * Restore intentional mentions in the markdown/plain text editor --------- Co-authored-by: ElementBot --- .../messages/impl/MessagesFlowNode.kt | 13 +- .../MessageComposerPresenter.kt | 39 +++- .../messagecomposer/MessageComposerState.kt | 4 +- .../MessageComposerStateProvider.kt | 7 +- .../messagecomposer/MessageComposerView.kt | 2 +- .../timeline/DefaultHtmlConverterProvider.kt | 20 +- .../components/event/TimelineItemTextView.kt | 6 +- .../impl/utils/TextPillificationHelper.kt | 80 ++++++++ .../messages/impl/MessagesPresenterTest.kt | 7 + .../MessageComposerPresenterTest.kt | 24 ++- .../DefaultHtmlConverterProviderTest.kt | 6 +- .../components/event/TimelineTextViewTest.kt | 4 - .../impl/utils/TextPillificationHelperTest.kt | 128 +++++++++++++ .../matrix/api/core/MatrixPatterns.kt | 57 +++++- .../matrix/api/permalink/PermalinkBuilder.kt | 3 + .../matrix/api/core/MatrixPatternsTest.kt | 98 ++++++++++ .../impl/permalink/DefaultPermalinkBuilder.kt | 11 ++ .../test/permalink/FakePermalinkBuilder.kt | 10 +- .../ui/messages/RoomMemberProfilesCache.kt | 3 + .../libraries/textcomposer/TextComposer.kt | 25 +-- .../components/markdown/MarkdownTextInput.kt | 8 +- .../textcomposer/mentions/MentionSpan.kt | 30 ++- .../mentions/MentionSpanProvider.kt | 154 +--------------- .../textcomposer/mentions/MentionSpanTheme.kt | 172 ++++++++++++++++++ .../model/MarkdownTextEditorState.kt | 11 +- .../markdown/MarkdownTextInputTest.kt | 6 +- .../impl/mentions/MentionSpanProviderTest.kt | 56 ++---- .../impl/model/MarkdownTextEditorStateTest.kt | 15 +- ...er.mentions_MentionSpanTheme_Day_0_en.png} | 0 ....mentions_MentionSpanTheme_Night_0_en.png} | 0 tools/detekt/detekt.yml | 2 +- 31 files changed, 715 insertions(+), 286 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt create mode 100644 libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt create mode 100644 libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt rename tests/uitests/src/test/snapshots/images/{libraries.textcomposer.mentions_MentionSpan_Day_0_en.png => libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.textcomposer.mentions_MentionSpan_Night_0_en.png => libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png} (100%) 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 3999a5fe0c..4fd59be6cb 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 @@ -73,8 +73,8 @@ import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCa import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.mediaviewer.api.local.MediaInfo import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanProvider -import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.ImmutableList @@ -94,7 +94,7 @@ class MessagesFlowNode @AssistedInject constructor( private val analyticsService: AnalyticsService, private val room: MatrixRoom, private val roomMemberProfilesCache: RoomMemberProfilesCache, - mentionSpanProviderFactory: MentionSpanProvider.Factory, + private val mentionSpanTheme: MentionSpanTheme, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Messages, @@ -151,8 +151,6 @@ class MessagesFlowNode @AssistedInject constructor( private val callbacks = plugins() - private val mentionSpanProvider = mentionSpanProviderFactory.create(room.sessionId.value) - override fun onBuilt() { super.onBuilt() @@ -371,11 +369,10 @@ class MessagesFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - mentionSpanProvider.updateStyles() - + mentionSpanTheme.updateStyles(currentUserId = room.sessionId) CompositionLocalProvider( LocalRoomMemberProfilesCache provides roomMemberProfilesCache, - LocalMentionSpanProvider provides mentionSpanProvider, + LocalMentionSpanTheme provides mentionSpanTheme, ) { BackstackWithOverlayBox(modifier) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 85eacc38ae..7bd6bd1867 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -42,6 +42,7 @@ import io.element.android.features.messages.impl.attachments.preview.error.sendA import io.element.android.features.messages.impl.draft.ComposerDraftService import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor import io.element.android.features.messages.impl.timeline.TimelineController +import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage @@ -52,12 +53,14 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.mediapickers.api.PickerProvider @@ -66,7 +69,8 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.preferences.api.store.SessionPreferencesStore -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState import io.element.android.libraries.textcomposer.model.Message @@ -77,6 +81,7 @@ import io.element.android.libraries.textcomposer.model.rememberMarkdownTextEdito import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import io.element.android.wysiwyg.compose.RichTextEditorState +import io.element.android.wysiwyg.display.TextDisplay import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CancellationException @@ -116,6 +121,9 @@ class MessageComposerPresenter @Inject constructor( permissionsPresenterFactory: PermissionsPresenter.Factory, private val timelineController: TimelineController, private val draftService: ComposerDraftService, + private val mentionSpanProvider: MentionSpanProvider, + private val pillificationHelper: TextPillificationHelper, + private val roomMemberProfilesCache: RoomMemberProfilesCache, ) : Presenter { private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) private var pendingEvent: MessageComposerEvents? = null @@ -139,7 +147,6 @@ class MessageComposerPresenter @Inject constructor( richTextEditorState.isReadyToProcessActions = true } val markdownTextEditorState = rememberMarkdownTextEditorState(initialText = null, initialFocus = false) - var isMentionsEnabled by remember { mutableStateOf(false) } LaunchedEffect(Unit) { isMentionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.Mentions) @@ -260,8 +267,6 @@ class MessageComposerPresenter @Inject constructor( } } - val mentionSpanProvider = LocalMentionSpanProvider.current - fun handleEvents(event: MessageComposerEvents) { when (event) { MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value @@ -386,9 +391,24 @@ class MessageComposerPresenter @Inject constructor( } } + val mentionSpanTheme = LocalMentionSpanTheme.current + val resolveMentionDisplay = remember(mentionSpanTheme) { + { text: String, url: String -> + val permalinkData = permalinkParser.parse(url) + if (permalinkData is PermalinkData.UserLink) { + val displayNameOrId = roomMemberProfilesCache.getDisplayName(permalinkData.userId) ?: permalinkData.userId.value + val mentionSpan = mentionSpanProvider.getMentionSpanFor(displayNameOrId, url) + mentionSpan.update(mentionSpanTheme) + TextDisplay.Custom(mentionSpan) + } else { + val mentionSpan = mentionSpanProvider.getMentionSpanFor(text, url) + mentionSpan.update(mentionSpanTheme) + TextDisplay.Custom(mentionSpan) + } + } + } return MessageComposerState( textEditorState = textEditorState, - permalinkParser = permalinkParser, isFullScreen = isFullScreen.value, mode = messageComposerContext.composerMode, showAttachmentSourcePicker = showAttachmentSourcePicker, @@ -397,7 +417,8 @@ class MessageComposerPresenter @Inject constructor( canCreatePoll = canCreatePoll.value, attachmentsState = attachmentsState.value, memberSuggestions = memberSuggestions.toPersistentList(), - eventSink = { handleEvents(it) } + resolveMentionDisplay = resolveMentionDisplay, + eventSink = { handleEvents(it) }, ) } @@ -627,7 +648,8 @@ class MessageComposerPresenter @Inject constructor( analyticsService.captureInteraction(Interaction.Name.MobileRoomComposerFormattingEnabled) } else { val markdown = richTextEditorState.messageMarkdown - markdownTextEditorState.text.update(markdown, true) + val pilliefiedMarkdown = pillificationHelper.pillify(markdown) + markdownTextEditorState.text.update(pilliefiedMarkdown, true) // Give some time for the focus of the previous editor to be cleared delay(100) markdownTextEditorState.requestFocusAction() @@ -688,7 +710,8 @@ class MessageComposerPresenter @Inject constructor( if (content.isEmpty()) { markdownTextEditorState.selection = IntRange.EMPTY } - markdownTextEditorState.text.update(content, true) + val pillifiedContent = pillificationHelper.pillify(content) + markdownTextEditorState.text.update(pillifiedContent, true) if (requestFocus) { markdownTextEditorState.requestFocusAction() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index b1c7ad79b5..332a9e75f8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -19,16 +19,15 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState +import io.element.android.wysiwyg.display.TextDisplay import kotlinx.collections.immutable.ImmutableList @Stable data class MessageComposerState( val textEditorState: TextEditorState, - val permalinkParser: PermalinkParser, val isFullScreen: Boolean, val mode: MessageComposerMode, val showAttachmentSourcePicker: Boolean, @@ -37,6 +36,7 @@ data class MessageComposerState( val canCreatePoll: Boolean, val attachmentsState: AttachmentsState, val memberSuggestions: ImmutableList, + val resolveMentionDisplay: (String, String) -> TextDisplay, val eventSink: (MessageComposerEvents) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index 31912f21e6..824f87bb8e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -17,12 +17,11 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.matrix.api.permalink.PermalinkData -import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.textcomposer.aRichTextEditorState import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState +import io.element.android.wysiwyg.display.TextDisplay import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -45,9 +44,6 @@ fun aMessageComposerState( memberSuggestions: ImmutableList = persistentListOf(), ) = MessageComposerState( textEditorState = textEditorState, - permalinkParser = object : PermalinkParser { - override fun parse(uriString: String): PermalinkData = TODO() - }, isFullScreen = isFullScreen, mode = mode, showTextFormatting = showTextFormatting, @@ -56,5 +52,6 @@ fun aMessageComposerState( canCreatePoll = canCreatePoll, attachmentsState = attachmentsState, memberSuggestions = memberSuggestions, + resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, eventSink = {}, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index a033791f34..6e261e8ac9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -107,7 +107,6 @@ internal fun MessageComposerView( modifier = modifier, state = state.textEditorState, voiceMessageState = voiceMessageState.voiceMessageState, - permalinkParser = state.permalinkParser, subcomposing = subcomposing, onRequestFocus = ::onRequestFocus, onSendMessage = ::sendMessage, @@ -122,6 +121,7 @@ internal fun MessageComposerView( onSendVoiceMessage = onSendVoiceMessage, onDeleteVoiceMessage = onDeleteVoiceMessage, onReceiveSuggestion = ::onSuggestionReceived, + resolveMentionDisplay = state.resolveMentionDisplay, onError = ::onError, onTyping = ::onTyping, onSelectRichContent = ::sendUri, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt index 1987b46053..6eb93756fa 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt @@ -29,7 +29,8 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.wysiwyg.compose.StyledHtmlConverter import io.element.android.wysiwyg.display.MentionDisplayHandler import io.element.android.wysiwyg.display.TextDisplay @@ -39,7 +40,9 @@ import javax.inject.Inject @ContributesBinding(SessionScope::class) @SingleIn(SessionScope::class) -class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider { +class DefaultHtmlConverterProvider @Inject constructor( + private val mentionSpanProvider: MentionSpanProvider, +) : HtmlConverterProvider { private val htmlConverter: MutableState = mutableStateOf(null) @Composable @@ -50,20 +53,23 @@ class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider } val editorStyle = ElementRichTextEditorStyle.textStyle() - val mentionSpanProvider = LocalMentionSpanProvider.current - + val mentionSpanTheme = LocalMentionSpanTheme.current val context = LocalContext.current - htmlConverter.value = remember(editorStyle, mentionSpanProvider) { + htmlConverter.value = remember(editorStyle, mentionSpanTheme) { StyledHtmlConverter( context = context, mentionDisplayHandler = object : MentionDisplayHandler { override fun resolveAtRoomMentionDisplay(): TextDisplay { - return TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text = "@room", url = "#")) + val mentionSpan = mentionSpanProvider.getMentionSpanFor(text = "@room", url = "#") + mentionSpan.update(mentionSpanTheme) + return TextDisplay.Custom(mentionSpan) } override fun resolveMentionDisplay(text: String, url: String): TextDisplay { - return TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) + val mentionSpan = mentionSpanProvider.getMentionSpanFor(text, url) + mentionSpan.update(mentionSpanTheme) + return TextDisplay.Custom(mentionSpan) } }, isMention = { _, url -> mentionDetector?.isMention(url).orFalse() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index b9e441f84e..a04d524d43 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -41,8 +41,10 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme import io.element.android.libraries.textcomposer.mentions.MentionSpan import io.element.android.libraries.textcomposer.mentions.getMentionSpans +import io.element.android.libraries.textcomposer.mentions.updateMentionStyles import io.element.android.wysiwyg.compose.EditorStyledText @Composable @@ -74,9 +76,11 @@ fun TimelineItemTextView( internal fun getTextWithResolvedMentions(content: TimelineItemTextBasedContent): CharSequence { val userProfileCache = LocalRoomMemberProfilesCache.current val lastCacheUpdate by userProfileCache.lastCacheUpdate.collectAsState() - val formattedBody = remember(content.formattedBody, lastCacheUpdate) { + val mentionSpanTheme = LocalMentionSpanTheme.current + val formattedBody = remember(content.formattedBody, mentionSpanTheme, lastCacheUpdate) { content.formattedBody?.let { formattedBody -> updateMentionSpans(formattedBody, userProfileCache) + mentionSpanTheme.updateMentionStyles(formattedBody) formattedBody } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt new file mode 100644 index 0000000000..ad4e6aa64f --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.utils + +import android.text.Spannable +import android.text.SpannableStringBuilder +import androidx.core.text.getSpans +import io.element.android.libraries.matrix.api.core.MatrixPatternType +import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.textcomposer.mentions.MentionSpan +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import javax.inject.Inject + +class TextPillificationHelper @Inject constructor( + private val mentionSpanProvider: MentionSpanProvider, + private val permalinkBuilder: PermalinkBuilder, + private val permalinkParser: PermalinkParser, + private val roomMemberProfilesCache: RoomMemberProfilesCache, +) { + @Suppress("LoopWithTooManyJumpStatements") + fun pillify(text: CharSequence): CharSequence { + val matches = MatrixPatterns.findPatterns(text, permalinkParser).sortedByDescending { it.end } + if (matches.isEmpty()) return text + + val spannable = SpannableStringBuilder(text) + for (match in matches) { + when (match.type) { + MatrixPatternType.USER_ID -> { + val mentionSpanExists = spannable.getSpans(match.start, match.end).isNotEmpty() + if (!mentionSpanExists) { + val userId = UserId(match.value) + val permalink = permalinkBuilder.permalinkForUser(userId).getOrNull() ?: continue + val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, permalink) + roomMemberProfilesCache.getDisplayName(userId)?.let { mentionSpan.text = it } + spannable.replace(match.start, match.end, "@ ") + spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + MatrixPatternType.ROOM_ALIAS -> { + val mentionSpanExists = spannable.getSpans(match.start, match.end).isNotEmpty() + if (!mentionSpanExists) { + val permalink = permalinkBuilder.permalinkForRoomAlias(RoomAlias(match.value)).getOrNull() ?: continue + val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, permalink) + spannable.replace(match.start, match.end, "@ ") + spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + MatrixPatternType.AT_ROOM -> { + val mentionSpanExists = spannable.getSpans(match.start, match.end).isNotEmpty() + if (!mentionSpanExists) { + val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "") + spannable.replace(match.start, match.end, "@ ") + spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + else -> Unit + } + } + return spannable + } +} 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 74cffb30fd..906b8d4ac1 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 @@ -44,6 +44,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.typing.TypingNotificationPresenter +import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager @@ -82,6 +83,7 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeTimeline +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer @@ -93,6 +95,7 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder import io.element.android.services.analytics.test.FakeAnalyticsService @@ -765,6 +768,7 @@ class MessagesPresenterTest { val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) val appPreferencesStore = InMemoryAppPreferencesStore() val sessionPreferencesStore = InMemorySessionPreferencesStore() + val mentionSpanProvider = MentionSpanProvider(FakePermalinkParser()) val messageComposerPresenter = MessageComposerPresenter( appCoroutineScope = this, room = matrixRoom, @@ -782,6 +786,9 @@ class MessagesPresenterTest { permalinkBuilder = FakePermalinkBuilder(), timelineController = TimelineController(matrixRoom), draftService = FakeComposerDraftService(), + mentionSpanProvider = mentionSpanProvider, + pillificationHelper = TextPillificationHelper(mentionSpanProvider, FakePermalinkBuilder(), FakePermalinkParser(), RoomMemberProfilesCache()), + roomMemberProfilesCache = RoomMemberProfilesCache(), ).apply { showTextFormatting = true isTesting = true diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index 667d43b56e..c7ebab4c82 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.timeline.TimelineController +import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -46,6 +47,7 @@ import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.Mention @@ -69,6 +71,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeTimeline +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.test.FakePickerProvider @@ -82,6 +85,7 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion @@ -318,7 +322,7 @@ class MessageComposerPresenterTest { @Test fun `present - send message with plain text enabled`() = runTest { - val permalinkBuilder = FakePermalinkBuilder(result = { Result.success("") }) + val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("") }) val presenter = createPresenter(this, isRichTextEditorEnabled = false) moleculeFlow(RecompositionMode.Immediate) { val state = presenter.present() @@ -934,11 +938,11 @@ class MessageComposerPresenterTest { } @Test - fun `present - insertMention`() = runTest { + fun `present - insertMention for user in rich text editor`() = runTest { val presenter = createPresenter( coroutineScope = this, permalinkBuilder = FakePermalinkBuilder( - result = { + permalinkForUserLambda = { Result.success("https://matrix.to/#/${A_USER_ID_2.value}") } ) @@ -1355,6 +1359,15 @@ class MessageComposerPresenterTest { snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher, permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(), permalinkBuilder: PermalinkBuilder = FakePermalinkBuilder(), + permalinkParser: PermalinkParser = FakePermalinkParser(), + mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkParser), + roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), + textPillificationHelper: TextPillificationHelper = TextPillificationHelper( + mentionSpanProvider = mentionSpanProvider, + permalinkBuilder = permalinkBuilder, + permalinkParser = permalinkParser, + roomMemberProfilesCache = roomMemberProfilesCache, + ), isRichTextEditorEnabled: Boolean = true, draftService: ComposerDraftService = FakeComposerDraftService(), ) = MessageComposerPresenter( @@ -1370,10 +1383,13 @@ class MessageComposerPresenterTest { DefaultMessageComposerContext(), TestRichTextEditorStateFactory(), permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter), - permalinkParser = FakePermalinkParser(), + permalinkParser = permalinkParser, permalinkBuilder = permalinkBuilder, timelineController = TimelineController(room), draftService = draftService, + mentionSpanProvider = mentionSpanProvider, + pillificationHelper = textPillificationHelper, + roomMemberProfilesCache = roomMemberProfilesCache, ).apply { isTesting = true showTextFormatting = isRichTextEditorEnabled diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index 276544a057..d40f81a10c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -21,6 +21,8 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.test.junit4.createComposeRule import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -32,7 +34,7 @@ class DefaultHtmlConverterProviderTest { @Test fun `calling provide without calling Update first should throw an exception`() { - val provider = DefaultHtmlConverterProvider() + val provider = DefaultHtmlConverterProvider(mentionSpanProvider = MentionSpanProvider(FakePermalinkParser())) val exception = runCatching { provider.provide() }.exceptionOrNull() @@ -41,7 +43,7 @@ class DefaultHtmlConverterProviderTest { @Test fun `calling provide after calling Update first should return an HtmlConverter`() { - val provider = DefaultHtmlConverterProvider() + val provider = DefaultHtmlConverterProvider(mentionSpanProvider = MentionSpanProvider(FakePermalinkParser())) composeTestRule.setContent { CompositionLocalProvider(LocalInspectionMode provides true) { provider.Update(currentUserId = A_USER_ID) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt index 1a08cfd750..bf8c1d4ad1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt @@ -159,10 +159,6 @@ class TimelineTextViewTest { text = text, rawValue = rawValue, type = type, - backgroundColor = 0, - textColor = 0, - startPadding = 0, - endPadding = 0, ) private fun aTextContentWithFormattedBody(formattedBody: CharSequence?, body: String = "") = diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt new file mode 100644 index 0000000000..4cc695c1e8 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.utils + +import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.textcomposer.mentions.MentionSpan +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.getMentionSpans +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TextPillificationHelperTest { + @Test + fun `pillify - adds pills for user ids`() { + val text = "A @user:server.com" + val helper = aTextPillificationHelper( + permalinkparser = FakePermalinkParser(result = { + PermalinkData.UserLink(UserId("@user:server.com")) + }), + permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { + Result.success("https://matrix.to/#/@user:server.com") + }), + ) + val pillified = helper.pillify(text) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(1) + val mentionSpan = mentionSpans.firstOrNull() + assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.USER) + assertThat(mentionSpan?.rawValue).isEqualTo("@user:server.com") + assertThat(mentionSpan?.text).isEqualTo("@user:server.com") + } + + @Test + fun `pillify - uses the cached display name for user mentions`() { + val text = "A @user:server.com" + val helper = aTextPillificationHelper( + permalinkparser = FakePermalinkParser(result = { + PermalinkData.UserLink(UserId("@user:server.com")) + }), + permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { + Result.success("https://matrix.to/#/@user:server.com") + }), + roomMemberProfilesCache = RoomMemberProfilesCache().apply { + replace(listOf(aRoomMember(userId = UserId("@user:server.com"), displayName = "Alice"))) + }, + ) + val pillified = helper.pillify(text) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(1) + val mentionSpan = mentionSpans.firstOrNull() + assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.USER) + assertThat(mentionSpan?.rawValue).isEqualTo("@user:server.com") + assertThat(mentionSpan?.text).isEqualTo("Alice") + } + + @Test + fun `pillify - adds pills for room aliases`() { + val text = "A #room:server.com" + val helper = aTextPillificationHelper( + permalinkparser = FakePermalinkParser(result = { + PermalinkData.RoomLink(RoomIdOrAlias.Alias(RoomAlias("#room:server.com"))) + }), + permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { + Result.success("https://matrix.to/#/#room:server.com") + }), + ) + val pillified = helper.pillify(text) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(1) + val mentionSpan = mentionSpans.firstOrNull() + assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.ROOM) + assertThat(mentionSpan?.rawValue).isEqualTo("#room:server.com") + assertThat(mentionSpan?.text).isEqualTo("#room:server.com") + } + + @Test + fun `pillify - adds pills for @room mentions`() { + val text = "An @room mention" + val helper = aTextPillificationHelper(permalinkparser = FakePermalinkParser(result = { + PermalinkData.FallbackLink(Uri.EMPTY) + })) + val pillified = helper.pillify(text) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(1) + val mentionSpan = mentionSpans.firstOrNull() + assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.EVERYONE) + assertThat(mentionSpan?.rawValue).isEqualTo("@room") + assertThat(mentionSpan?.text).isEqualTo("@room") + } + + private fun aTextPillificationHelper( + permalinkparser: PermalinkParser = FakePermalinkParser(), + permalinkBuilder: FakePermalinkBuilder = FakePermalinkBuilder(), + mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkparser), + roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), + ) = TextPillificationHelper( + mentionSpanProvider = mentionSpanProvider, + permalinkBuilder = permalinkBuilder, + permalinkParser = permalinkparser, + roomMemberProfilesCache = roomMemberProfilesCache, + ) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index ac77e58986..20e7746e3f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -16,13 +16,16 @@ package io.element.android.libraries.matrix.api.core +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser + /** * This class contains pattern to match the different Matrix ids * Ref: https://matrix.org/docs/spec/appendices#identifier-grammar */ object MatrixPatterns { // Note: TLD is not mandatory (localhost, IP address...) - private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" + private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?" // regex pattern to find matrix user ids in a string. // See https://matrix.org/docs/spec/appendices#historical-user-ids @@ -109,4 +112,56 @@ object MatrixPatterns { * @return true if the string is a valid thread id. */ fun isThreadId(str: String?) = isEventId(str) + + /** + * Finds existing ids or aliases in a [CharSequence]. + * Note not all cases are implemented. + */ + fun findPatterns(text: CharSequence, permalinkParser: PermalinkParser): List { + val rawTextMatches = "\\S+?$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text) + val urlMatches = "\\[\\S+?\\]\\((\\S+?)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text) + val atRoomMatches = Regex("@room").findAll(text) + return buildList { + for (match in rawTextMatches) { + // Match existing id and alias patterns in the text + val type = when { + isUserId(match.value) -> MatrixPatternType.USER_ID + isRoomId(match.value) -> MatrixPatternType.ROOM_ID + isRoomAlias(match.value) -> MatrixPatternType.ROOM_ALIAS + isEventId(match.value) -> MatrixPatternType.EVENT_ID + else -> null + } + if (type != null) { + add(MatrixPatternResult(type, match.value, match.range.first, match.range.last + 1)) + } + } + for (match in urlMatches) { + // Extract the link and check if it's a valid permalink + val urlMatch = match.groupValues[1] + when (val permalink = permalinkParser.parse(urlMatch)) { + is PermalinkData.UserLink -> { + add(MatrixPatternResult(MatrixPatternType.USER_ID, permalink.userId.toString(), match.range.first, match.range.last + 1)) + } + is PermalinkData.RoomLink -> { + add(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, permalink.roomIdOrAlias.identifier, match.range.first, match.range.last + 1)) + } + else -> Unit + } + } + for (match in atRoomMatches) { + // Special case for `@room` mentions + add(MatrixPatternResult(MatrixPatternType.AT_ROOM, match.value, match.range.first, match.range.last + 1)) + } + } + } +} + +enum class MatrixPatternType { + USER_ID, + ROOM_ID, + ROOM_ALIAS, + EVENT_ID, + AT_ROOM } + +data class MatrixPatternResult(val type: MatrixPatternType, val value: String, val start: Int, val end: Int) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt index 76bb327c2e..5e562f43c5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt @@ -16,12 +16,15 @@ package io.element.android.libraries.matrix.api.permalink +import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.UserId interface PermalinkBuilder { fun permalinkForUser(userId: UserId): Result + fun permalinkForRoomAlias(roomAlias: RoomAlias): Result } sealed class PermalinkBuilderError : Throwable() { data object InvalidUserId : PermalinkBuilderError() + data object InvalidRoomAlias : PermalinkBuilderError() } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt new file mode 100644 index 0000000000..d195789d14 --- /dev/null +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.core + +import android.net.Uri +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import org.junit.Test + +class MatrixPatternsTest { + @Test + fun `findPatterns - returns raw user ids`() { + val text = "A @user:server.com and @user2:server.com" + val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) + assertThat(patterns).containsExactly( + MatrixPatternResult(MatrixPatternType.USER_ID, "@user:server.com", 2, 18), + MatrixPatternResult(MatrixPatternType.USER_ID, "@user2:server.com", 23, 40) + ) + } + + @Test + fun `findPatterns - returns raw room ids`() { + val text = "A !room:server.com and !room2:server.com" + val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) + assertThat(patterns).containsExactly( + MatrixPatternResult(MatrixPatternType.ROOM_ID, "!room:server.com", 2, 18), + MatrixPatternResult(MatrixPatternType.ROOM_ID, "!room2:server.com", 23, 40) + ) + } + + @Test + fun `findPatterns - returns raw room aliases`() { + val text = "A #room:server.com and #room2:server.com" + val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) + assertThat(patterns).containsExactly( + MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 18), + MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room2:server.com", 23, 40) + ) + } + + @Test + fun `findPatterns - returns raw room event ids`() { + val text = "A \$event:server.com and \$event2:server.com" + val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) + assertThat(patterns).containsExactly( + MatrixPatternResult(MatrixPatternType.EVENT_ID, "\$event:server.com", 2, 19), + MatrixPatternResult(MatrixPatternType.EVENT_ID, "\$event2:server.com", 24, 42) + ) + } + + @Test + fun `findPatterns - returns @room mention`() { + val text = "A @room mention" + val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) + assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.AT_ROOM, "@room", 2, 7)) + } + + @Test + fun `findPatterns - returns user ids in permalinks`() { + val text = "A [User](https://matrix.to/#/@user:server.com)" + val permalinkParser = aPermalinkParser { _ -> + PermalinkData.UserLink(UserId("@user:server.com")) + } + val patterns = MatrixPatterns.findPatterns(text, permalinkParser) + assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.USER_ID, "@user:server.com", 2, 46)) + } + + @Test + fun `findPatterns - returns room aliases in permalinks`() { + val text = "A [Room](https://matrix.to/#/#room:server.com)" + val permalinkParser = aPermalinkParser { _ -> + PermalinkData.RoomLink(RoomIdOrAlias.Alias(RoomAlias("#room:server.com"))) + } + val patterns = MatrixPatterns.findPatterns(text, permalinkParser) + assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 46)) + } + + private fun aPermalinkParser(block: (String) -> PermalinkData = { PermalinkData.FallbackLink(Uri.EMPTY) }) = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return block(uriString) + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt index 3103ff4c1e..30a458b28f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt @@ -19,9 +19,11 @@ package io.element.android.libraries.matrix.impl.permalink import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilderError +import org.matrix.rustcomponents.sdk.matrixToRoomAliasPermalink import org.matrix.rustcomponents.sdk.matrixToUserPermalink import javax.inject.Inject @@ -35,4 +37,13 @@ class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder { matrixToUserPermalink(userId.value) } } + + override fun permalinkForRoomAlias(roomAlias: RoomAlias): Result { + if (!MatrixPatterns.isRoomAlias(roomAlias.value)) { + return Result.failure(PermalinkBuilderError.InvalidRoomAlias) + } + return runCatching { + matrixToRoomAliasPermalink(roomAlias.value) + } + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt index f700c3b6af..3510a362ec 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt @@ -16,13 +16,19 @@ package io.element.android.libraries.matrix.test.permalink +import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder class FakePermalinkBuilder( - private val result: (UserId) -> Result = { Result.failure(Exception("Not implemented")) } + private val permalinkForUserLambda: (UserId) -> Result = { Result.failure(Exception("Not implemented")) }, + private val permalinkForRoomAliasLambda: (RoomAlias) -> Result = { Result.failure(Exception("Not implemented")) }, ) : PermalinkBuilder { override fun permalinkForUser(userId: UserId): Result { - return result(userId) + return permalinkForUserLambda(userId) + } + + override fun permalinkForRoomAlias(roomAlias: RoomAlias): Result { + return permalinkForRoomAliasLambda(roomAlias) } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt index 32f82cece1..e2e0327bad 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt @@ -17,12 +17,15 @@ package io.element.android.libraries.matrix.ui.messages import androidx.compose.runtime.staticCompositionLocalOf +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject +@SingleIn(RoomScope::class) class RoomMemberProfilesCache @Inject constructor() { private val cache = MutableStateFlow(mapOf()) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 136cc6eeae..8da508716c 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -52,9 +52,6 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.permalink.PermalinkData -import io.element.android.libraries.matrix.api.permalink.PermalinkParser -import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider import io.element.android.libraries.testtags.TestTags @@ -70,7 +67,6 @@ import io.element.android.libraries.textcomposer.components.VoiceMessageRecordin import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput import io.element.android.libraries.textcomposer.components.markdown.aMarkdownTextEditorState import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanProvider import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.TextEditorState @@ -90,7 +86,6 @@ import kotlin.time.Duration.Companion.seconds fun TextComposer( state: TextEditorState, voiceMessageState: VoiceMessageState, - permalinkParser: PermalinkParser, composerMode: MessageComposerMode, enableVoiceMessages: Boolean, onRequestFocus: () -> Unit, @@ -106,6 +101,7 @@ fun TextComposer( onTyping: (Boolean) -> Unit, onReceiveSuggestion: (Suggestion?) -> Unit, onSelectRichContent: ((Uri) -> Unit)?, + resolveMentionDisplay: (text: String, url: String) -> TextDisplay, modifier: Modifier = Modifier, showTextFormatting: Boolean = false, subcomposing: Boolean = false, @@ -144,8 +140,6 @@ fun TextComposer( } } - val userProfileCache = LocalRoomMemberProfilesCache.current - val placeholder = if (composerMode.inThread) { stringResource(id = CommonStrings.action_reply_in_thread) } else { @@ -155,23 +149,14 @@ fun TextComposer( is TextEditorState.Rich -> { remember(state.richTextEditorState, subcomposing, composerMode, onResetComposerMode, onError) { @Composable { - val mentionSpanProvider = LocalMentionSpanProvider.current TextInput( state = state.richTextEditorState, subcomposing = subcomposing, placeholder = placeholder, composerMode = composerMode, onResetComposerMode = onResetComposerMode, - resolveMentionDisplay = { text, url -> - val permalinkData = permalinkParser.parse(url) - if (permalinkData is PermalinkData.UserLink) { - val displayNameOrId = userProfileCache.getDisplayName(permalinkData.userId) ?: permalinkData.userId.value - TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(displayNameOrId, url)) - } else { - TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) - } - }, - resolveRoomMentionDisplay = { TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor("@room", "#")) }, + resolveMentionDisplay = resolveMentionDisplay, + resolveRoomMentionDisplay = { resolveMentionDisplay("@room", "#") }, onError = onError, onTyping = onTyping, onSelectRichContent = onSelectRichContent, @@ -709,9 +694,6 @@ private fun ATextComposer( state = state, showTextFormatting = showTextFormatting, voiceMessageState = voiceMessageState, - permalinkParser = object : PermalinkParser { - override fun parse(uriString: String): PermalinkData = TODO("Not yet implemented") - }, composerMode = composerMode, enableVoiceMessages = enableVoiceMessages, onRequestFocus = {}, @@ -726,6 +708,7 @@ private fun ATextComposer( onError = {}, onTyping = {}, onReceiveSuggestion = {}, + resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, onSelectRichContent = null, ) } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt index 8016fa637e..ea31f51585 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt @@ -39,7 +39,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme import io.element.android.libraries.textcomposer.mentions.MentionSpan +import io.element.android.libraries.textcomposer.mentions.updateMentionStyles import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.SuggestionType @@ -81,6 +83,8 @@ fun MarkdownTextInput( } } + val mentionSpanTheme = LocalMentionSpanTheme.current + AndroidView( modifier = Modifier .padding(top = 6.dp, bottom = 6.dp) @@ -130,7 +134,9 @@ fun MarkdownTextInput( editText.applyStyleInCompose(richTextEditorStyle) if (state.text.needsDisplaying()) { - editText.updateEditableText(state.text.value()) + val text = state.text.value() + mentionSpanTheme.updateMentionStyles(text) + editText.updateEditableText(text) if (canUpdateState) { state.text.update(editText.editableText, false) } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt index 286c45684e..a095320e0a 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt @@ -31,16 +31,17 @@ class MentionSpan( text: String, val rawValue: String, val type: Type, - val backgroundColor: Int, - val textColor: Int, - val startPadding: Int, - val endPadding: Int, - val typeface: Typeface = Typeface.DEFAULT, ) : ReplacementSpan() { companion object { private const val MAX_LENGTH = 20 } + var backgroundColor: Int = 0 + var textColor: Int = 0 + var startPadding: Int = 0 + var endPadding: Int = 0 + var typeface: Typeface = Typeface.DEFAULT + private var textWidth = 0 private val backgroundPaint = Paint().apply { isAntiAlias = true @@ -55,6 +56,25 @@ class MentionSpan( private var mentionText: CharSequence = getActualText(text) + fun update(mentionSpanTheme: MentionSpanTheme) { + val isCurrentUser = rawValue == mentionSpanTheme.currentUserId?.value + backgroundColor = when (type) { + Type.USER -> if (isCurrentUser) mentionSpanTheme.currentUserBackgroundColor else mentionSpanTheme.otherBackgroundColor + Type.ROOM -> mentionSpanTheme.otherBackgroundColor + Type.EVERYONE -> mentionSpanTheme.otherBackgroundColor + } + textColor = when (type) { + Type.USER -> if (isCurrentUser) mentionSpanTheme.currentUserTextColor else mentionSpanTheme.otherTextColor + Type.ROOM -> mentionSpanTheme.otherTextColor + Type.EVERYONE -> mentionSpanTheme.otherTextColor + } + backgroundPaint.color = backgroundColor + val (startPaddingPx, endPaddingPx) = mentionSpanTheme.paddingValuesPx.value + startPadding = startPaddingPx + endPadding = endPaddingPx + typeface = mentionSpanTheme.typeface.value + } + override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { paint.typeface = typeface textWidth = paint.measureText(mentionText, 0, mentionText.length).roundToInt() diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt index 4e33a461b8..9ce49a2f1b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt @@ -16,92 +16,23 @@ package io.element.android.libraries.textcomposer.mentions -import android.graphics.Color -import android.graphics.Typeface -import android.net.Uri -import android.view.ViewGroup -import android.widget.TextView -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.text.buildSpannedString -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import io.element.android.compound.theme.ElementTheme -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.text.rememberTypeface -import io.element.android.libraries.designsystem.theme.currentUserMentionPillBackground -import io.element.android.libraries.designsystem.theme.currentUserMentionPillText -import io.element.android.libraries.designsystem.theme.mentionPillBackground -import io.element.android.libraries.designsystem.theme.mentionPillText -import io.element.android.libraries.matrix.api.core.RoomAlias -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser -import kotlinx.collections.immutable.persistentListOf +import javax.inject.Inject @Stable -class MentionSpanProvider @AssistedInject constructor( - @Assisted private val currentSessionId: String, +open class MentionSpanProvider @Inject constructor( private val permalinkParser: PermalinkParser, ) { - @AssistedFactory - interface Factory { - fun create(currentSessionId: String): MentionSpanProvider - } - - private val paddingValues = PaddingValues(start = 4.dp, end = 6.dp) - - private val paddingValuesPx = mutableStateOf(0 to 0) - private val typeface = mutableStateOf(Typeface.DEFAULT) - - internal var currentUserTextColor: Int = 0 - internal var currentUserBackgroundColor: Int = Color.WHITE - internal var otherTextColor: Int = 0 - internal var otherBackgroundColor: Int = Color.WHITE - - @Suppress("ComposableNaming") - @Composable - fun updateStyles() { - currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb() - currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb() - otherTextColor = ElementTheme.colors.mentionPillText.toArgb() - otherBackgroundColor = ElementTheme.colors.mentionPillBackground.toArgb() - - typeface.value = ElementTheme.typography.fontBodyLgMedium.rememberTypeface().value - with(LocalDensity.current) { - val leftPadding = paddingValues.calculateLeftPadding(LocalLayoutDirection.current).roundToPx() - val rightPadding = paddingValues.calculateRightPadding(LocalLayoutDirection.current).roundToPx() - paddingValuesPx.value = leftPadding to rightPadding - } - } - fun getMentionSpanFor(text: String, url: String): MentionSpan { val permalinkData = permalinkParser.parse(url) - val (startPaddingPx, endPaddingPx) = paddingValuesPx.value return when { permalinkData is PermalinkData.UserLink -> { - val isCurrentUser = permalinkData.userId.value == currentSessionId MentionSpan( text = text, rawValue = permalinkData.userId.toString(), type = MentionSpan.Type.USER, - backgroundColor = if (isCurrentUser) currentUserBackgroundColor else otherBackgroundColor, - textColor = if (isCurrentUser) currentUserTextColor else otherTextColor, - startPadding = startPaddingPx, - endPadding = endPaddingPx, - typeface = typeface.value, ) } text == "@room" && permalinkData is PermalinkData.FallbackLink -> { @@ -109,23 +40,13 @@ class MentionSpanProvider @AssistedInject constructor( text = text, rawValue = "@room", type = MentionSpan.Type.EVERYONE, - backgroundColor = otherBackgroundColor, - textColor = otherTextColor, - startPadding = startPaddingPx, - endPadding = endPaddingPx, - typeface = typeface.value, ) } permalinkData is PermalinkData.RoomLink -> { MentionSpan( text = text, - rawValue = permalinkData.roomIdOrAlias.toString(), + rawValue = permalinkData.roomIdOrAlias.identifier, type = MentionSpan.Type.ROOM, - backgroundColor = otherBackgroundColor, - textColor = otherTextColor, - startPadding = startPaddingPx, - endPadding = endPaddingPx, - typeface = typeface.value, ) } else -> { @@ -133,77 +54,8 @@ class MentionSpanProvider @AssistedInject constructor( text = text, rawValue = text, type = MentionSpan.Type.ROOM, - backgroundColor = otherBackgroundColor, - textColor = otherTextColor, - startPadding = startPaddingPx, - endPadding = endPaddingPx, - typeface = typeface.value, ) } } } } - -@PreviewsDayNight -@Composable -internal fun MentionSpanPreview() { - ElementPreview { - val provider = remember { - MentionSpanProvider( - currentSessionId = "@me:matrix.org", - permalinkParser = object : PermalinkParser { - override fun parse(uriString: String): PermalinkData { - return when (uriString) { - "https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org")) - "https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org")) - "https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink( - roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(), - eventId = null, - viaParameters = persistentListOf(), - ) - else -> throw AssertionError("Unexpected value $uriString") - } - } - }, - ) - } - provider.updateStyles() - - val textColor = ElementTheme.colors.textPrimary.toArgb() - fun mentionSpanMe() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@me:matrix.org") - fun mentionSpanOther() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@other:matrix.org") - fun mentionSpanRoom() = provider.getMentionSpanFor("room", "https://matrix.to/#/#room:matrix.org") - AndroidView(factory = { context -> - TextView(context).apply { - includeFontPadding = false - layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - text = buildSpannedString { - append("This is a ") - append("@mention", mentionSpanMe(), 0) - append(" to the current user and this is a ") - append("@mention", mentionSpanOther(), 0) - append(" to other user. This one is for a room: ") - append("#room:matrix.org", mentionSpanRoom(), 0) - append("\n\n") - append("This ") - append("mention", mentionSpanMe(), 0) - append(" didn't have an '@' and it was automatically added, same as this ") - append("room:matrix.org", mentionSpanRoom(), 0) - append(" one, which had no leading '#'.") - } - setTextColor(textColor) - } - }) - } -} - -val LocalMentionSpanProvider = staticCompositionLocalOf { - MentionSpanProvider( - currentSessionId = "@dummy:value.org", - permalinkParser = object : PermalinkParser { - override fun parse(uriString: String): PermalinkData { - return PermalinkData.FallbackLink(Uri.EMPTY) - } - }, - ) -} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt new file mode 100644 index 0000000000..f3befe34cb --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.textcomposer.mentions + +import android.graphics.Color +import android.graphics.Typeface +import android.text.Spanned +import android.view.ViewGroup +import android.widget.TextView +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.text.buildSpannedString +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.rememberTypeface +import io.element.android.libraries.designsystem.theme.currentUserMentionPillBackground +import io.element.android.libraries.designsystem.theme.currentUserMentionPillText +import io.element.android.libraries.designsystem.theme.mentionPillBackground +import io.element.android.libraries.designsystem.theme.mentionPillText +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import kotlinx.collections.immutable.persistentListOf +import javax.inject.Inject + +/** + * Theme used for mention spans. + * To make this work, you need to: + * 1. Provide [LocalMentionSpanTheme] in a composable that wraps the ones where you want to use mentions. + * 2. Call [MentionSpanTheme.updateStyles] with the current [UserId] so the colors and sizes are computed. + * 3. Use either [MentionSpanTheme.updateMentionStyles] or [MentionSpan.update] to update the styles of the mention spans. + */ +@Stable +class MentionSpanTheme @Inject constructor() { + internal var currentUserId: UserId? = null + internal var currentUserTextColor: Int = 0 + internal var currentUserBackgroundColor: Int = Color.WHITE + internal var otherTextColor: Int = 0 + internal var otherBackgroundColor: Int = Color.WHITE + + private val paddingValues = PaddingValues(start = 4.dp, end = 6.dp) + internal val paddingValuesPx = mutableStateOf(0 to 0) + internal val typeface = mutableStateOf(Typeface.DEFAULT) + + /** + * Updates the styles of the mention spans based on the [ElementTheme] and [currentUserId]. + */ + @Suppress("ComposableNaming") + @Composable + fun updateStyles(currentUserId: UserId) { + this.currentUserId = currentUserId + currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb() + currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb() + otherTextColor = ElementTheme.colors.mentionPillText.toArgb() + otherBackgroundColor = ElementTheme.colors.mentionPillBackground.toArgb() + + typeface.value = ElementTheme.typography.fontBodyLgMedium.rememberTypeface().value + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + paddingValuesPx.value = remember(paddingValues, density, layoutDirection) { + with(density) { + val leftPadding = paddingValues.calculateLeftPadding(layoutDirection).roundToPx() + val rightPadding = paddingValues.calculateRightPadding(layoutDirection).roundToPx() + leftPadding to rightPadding + } + } + } +} + +/** + * Updates the styles of the mention spans in the given [CharSequence]. + */ +fun MentionSpanTheme.updateMentionStyles(charSequence: CharSequence) { + val spanned = charSequence as? Spanned ?: return + val mentionSpans = spanned.getMentionSpans() + for (span in mentionSpans) { + span.update(this) + } +} + +/** + * Composition local containing the current [MentionSpanTheme]. + */ +val LocalMentionSpanTheme = staticCompositionLocalOf { + MentionSpanTheme() +} + + @PreviewsDayNight + @Composable + internal fun MentionSpanThemePreview() { + ElementPreview { + val mentionSpanTheme = remember { MentionSpanTheme() } + val provider = remember { + MentionSpanProvider( + permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return when (uriString) { + "https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org")) + "https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org")) + "https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink( + roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(), + eventId = null, + viaParameters = persistentListOf(), + ) + else -> throw AssertionError("Unexpected value $uriString") + } + } + }, + ) + } + + val textColor = ElementTheme.colors.textPrimary.toArgb() + fun mentionSpanMe() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@me:matrix.org") + fun mentionSpanOther() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@other:matrix.org") + fun mentionSpanRoom() = provider.getMentionSpanFor("room", "https://matrix.to/#/#room:matrix.org") + mentionSpanTheme.updateStyles(currentUserId = UserId("@me:matrix.org")) + + CompositionLocalProvider( + LocalMentionSpanTheme provides mentionSpanTheme + ) { + AndroidView(factory = { context -> + TextView(context).apply { + includeFontPadding = false + layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + text = buildSpannedString { + append("This is a ") + append("@mention", mentionSpanMe(), 0) + append(" to the current user and this is a ") + append("@mention", mentionSpanOther(), 0) + append(" to other user. This one is for a room: ") + append("#room:matrix.org", mentionSpanRoom(), 0) + append("\n\n") + append("This ") + append("mention", mentionSpanMe(), 0) + append(" didn't have an '@' and it was automatically added, same as this ") + append("room:matrix.org", mentionSpanRoom(), 0) + append(" one, which had no leading '#'.") + } + mentionSpanTheme.updateMentionStyles(text) + setTextColor(textColor) + } + }) + } + } + } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt index 0d3bb5fdda..dd03c66366 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.textcomposer.components.markdown.StableCharS import io.element.android.libraries.textcomposer.mentions.MentionSpan import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion +import io.element.android.libraries.textcomposer.mentions.getMentionSpans import kotlinx.parcelize.Parcelize @Stable @@ -63,7 +64,7 @@ class MarkdownTextEditorState( val currentText = SpannableStringBuilder(text.value()) val replaceText = "@room" val roomPill = mentionSpanProvider.getMentionSpanFor(replaceText, "") - currentText.replace(suggestion.start, suggestion.end, ". ") + currentText.replace(suggestion.start, suggestion.end, "@ ") val end = suggestion.start + 1 currentText.setSpan(roomPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) text.update(currentText, true) @@ -74,7 +75,7 @@ class MarkdownTextEditorState( val text = mention.roomMember.displayName?.prependIndent("@") ?: mention.roomMember.userId.value val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link) - currentText.replace(suggestion.start, suggestion.end, ". ") + currentText.replace(suggestion.start, suggestion.end, "@ ") val end = suggestion.start + 1 currentText.setSpan(mentionPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) this.text.update(currentText, true) @@ -86,11 +87,11 @@ class MarkdownTextEditorState( fun getMessageMarkdown(permalinkBuilder: PermalinkBuilder): String { val charSequence = text.value() return if (charSequence is Spanned) { - val mentions = charSequence.getSpans(0, charSequence.length, MentionSpan::class.java) + val mentions = charSequence.getMentionSpans() buildString { append(charSequence.toString()) - if (mentions != null && mentions.isNotEmpty()) { - for (mention in mentions.reversed()) { + if (mentions.isNotEmpty()) { + for (mention in mentions.sortedByDescending { charSequence.getSpanEnd(it) }) { val start = charSequence.getSpanStart(mention) val end = charSequence.getSpanEnd(mention) when (mention.type) { diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt index 7147235603..c5949a3a41 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt @@ -155,7 +155,7 @@ class MarkdownTextInputTest { @Test fun `inserting a mention replaces the existing text with a span`() = runTest { val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(A_SESSION_ID) }) - val permalinkBuilder = FakePermalinkBuilder(result = { Result.success("https://matrix.to/#/$A_SESSION_ID") }) + val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$A_SESSION_ID") }) val state = aMarkdownTextEditorState(initialText = "@", initialFocus = true) state.currentMentionSuggestion = Suggestion(0, 1, SuggestionType.Mention, "") rule.setMarkdownTextInput(state = state) @@ -164,14 +164,14 @@ class MarkdownTextInputTest { editor = it.findEditor() state.insertMention( ResolvedMentionSuggestion.Member(roomMember = aRoomMember()), - MentionSpanProvider(currentSessionId = A_SESSION_ID.value, permalinkParser = permalinkParser), + MentionSpanProvider(permalinkParser = permalinkParser), permalinkBuilder, ) } rule.awaitIdle() // Text is replaced with a placeholder - assertThat(editor?.editableText.toString()).isEqualTo(". ") + assertThat(editor?.editableText.toString()).isEqualTo("@ ") // The placeholder contains a MentionSpan val mentionSpans = editor?.editableText?.getSpans(0, 2).orEmpty() assertThat(mentionSpans).isNotEmpty() diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt index bb53f0a79d..b6522305af 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt @@ -16,15 +16,14 @@ package io.element.android.libraries.textcomposer.impl.mentions -import android.graphics.Color import android.net.Uri import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.RoomAlias -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData -import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.textcomposer.mentions.MentionSpan import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.tests.testutils.WarmUpRule import org.junit.Rule @@ -37,66 +36,33 @@ class MentionSpanProviderTest { @JvmField @Rule val warmUpRule = WarmUpRule() - private val myUserColor = Color.RED - private val otherColor = Color.BLUE - private val currentUserId = A_SESSION_ID - private val permalinkParser = FakePermalinkParser() private val mentionSpanProvider = MentionSpanProvider( - currentSessionId = currentUserId.value, permalinkParser = permalinkParser, - ).apply { - currentUserBackgroundColor = myUserColor - currentUserTextColor = myUserColor - otherBackgroundColor = otherColor - otherTextColor = otherColor - } + ) @Test - fun `getting mention span for current user should return a MentionSpan with custom colors`() { - permalinkParser.givenResult(PermalinkData.UserLink(currentUserId)) - val mentionSpan = mentionSpanProvider.getMentionSpanFor("@me:matrix.org", "https://matrix.to/#/${currentUserId.value}") - assertThat(mentionSpan.backgroundColor).isEqualTo(myUserColor) - assertThat(mentionSpan.textColor).isEqualTo(myUserColor) + fun `getting mention span for a user returns a MentionSpan of type USER`() { + permalinkParser.givenResult(PermalinkData.UserLink(A_USER_ID)) + val mentionSpan = mentionSpanProvider.getMentionSpanFor("@me:matrix.org", "https://matrix.to/#/${A_USER_ID.value}") + assertThat(mentionSpan.type).isEqualTo(MentionSpan.Type.USER) } @Test - fun `getting mention span for other user should return a MentionSpan with normal colors`() { - permalinkParser.givenResult(PermalinkData.UserLink(UserId("@other:matrix.org"))) - val mentionSpan = mentionSpanProvider.getMentionSpanFor("@other:matrix.org", "https://matrix.to/#/@other:matrix.org") - assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) - assertThat(mentionSpan.textColor).isEqualTo(otherColor) - } - - @Test - fun `getting mention span for everyone in the room`() { + fun `getting mention span for everyone in the room returns a MentionSpan of type EVERYONE`() { permalinkParser.givenResult(PermalinkData.FallbackLink(uri = Uri.EMPTY)) val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#") - assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) - assertThat(mentionSpan.textColor).isEqualTo(otherColor) + assertThat(mentionSpan.type).isEqualTo(MentionSpan.Type.EVERYONE) } @Test - fun `getting mention span for a room should return a MentionSpan with normal colors`() { + fun `getting mention span for a room returns a MentionSpan of type ROOM`() { permalinkParser.givenResult( PermalinkData.RoomLink( roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(), ) ) val mentionSpan = mentionSpanProvider.getMentionSpanFor("#room:matrix.org", "https://matrix.to/#/#room:matrix.org") - assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) - assertThat(mentionSpan.textColor).isEqualTo(otherColor) - } - - @Test - fun `getting mention span for @room should return a MentionSpan with normal colors`() { - permalinkParser.givenResult( - PermalinkData.RoomLink( - roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(), - ) - ) - val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#room:matrix.org") - assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) - assertThat(mentionSpan.textColor).isEqualTo(otherColor) + assertThat(mentionSpan.type).isEqualTo(MentionSpan.Type.ROOM) } } diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt index 02c98da423..b4f3c143bd 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt @@ -21,10 +21,8 @@ import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.Mention -import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.aRoomMember @@ -60,7 +58,7 @@ class MarkdownTextEditorStateTest { val member = aRoomMember() val mention = ResolvedMentionSuggestion.Member(member) val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) }) - val permalinkBuilder = FakePermalinkBuilder(result = { Result.failure(IllegalStateException("Failed")) }) + val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.failure(IllegalStateException("Failed")) }) val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) state.insertMention(mention, mentionSpanProvider, permalinkBuilder) @@ -77,7 +75,7 @@ class MarkdownTextEditorStateTest { val member = aRoomMember() val mention = ResolvedMentionSuggestion.Member(member) val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) }) - val permalinkBuilder = FakePermalinkBuilder(result = { Result.success("https://matrix.to/#/${member.userId}") }) + val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/${member.userId}") }) val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) state.insertMention(mention, mentionSpanProvider, permalinkBuilder) @@ -117,7 +115,7 @@ class MarkdownTextEditorStateTest { @Test fun `getMessageMarkdown - when there are MentionSpans returns the same text with links to the mentions`() { val text = "No mentions here" - val permalinkBuilder = FakePermalinkBuilder(result = { Result.success("https://matrix.to/#/$it") }) + val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$it") }) val state = MarkdownTextEditorState(initialText = text, initialFocus = true) state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false) @@ -148,15 +146,14 @@ class MarkdownTextEditorStateTest { } private fun aMentionSpanProvider( - currentSessionId: SessionId = A_SESSION_ID, permalinkParser: FakePermalinkParser = FakePermalinkParser(), ): MentionSpanProvider { - return MentionSpanProvider(currentSessionId.value, permalinkParser) + return MentionSpanProvider(permalinkParser) } private fun aMarkdownTextWithMentions(): CharSequence { - val userMentionSpan = MentionSpan("@Alice", "@alice:matrix.org", MentionSpan.Type.USER, 0, 0, 0, 0) - val atRoomMentionSpan = MentionSpan("@room", "@room", MentionSpan.Type.EVERYONE, 0, 0, 0, 0) + val userMentionSpan = MentionSpan("@Alice", "@alice:matrix.org", MentionSpan.Type.USER) + val atRoomMentionSpan = MentionSpan("@room", "@room", MentionSpan.Type.EVERYONE) return buildSpannedString { append("Hello ") inSpans(userMentionSpan) { diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpan_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpan_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpan_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpan_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index 7212a58ce3..194bcd95bd 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -226,7 +226,7 @@ Compose: - LocalCameraPositionState - LocalTimelineItemPresenterFactories - LocalRoomMemberProfilesCache - - LocalMentionSpanProvider + - LocalMentionSpanTheme CompositionLocalNaming: active: true ContentEmitterReturningValues: From 696a430281a2974ff6f7dc8539139bc32f1e4618 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:41:19 +0000 Subject: [PATCH 063/115] Update dependency io.sentry:sentry-android to v7.12.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0dd4eb923..4d21bef0f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -185,7 +185,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics posthog = "com.posthog:posthog-android:3.4.2" -sentry = "io.sentry:sentry-android:7.11.0" +sentry = "io.sentry:sentry-android:7.12.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.23.1" From 83051d7231c3f6a80e6071d1e26ce27f4bb52a2d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 11:14:04 +0200 Subject: [PATCH 064/115] Update documentation --- .../push/impl/notifications/DefaultNotificationDrawerManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index f485e29847..12366aeb1e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -46,7 +46,7 @@ import javax.inject.Inject private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag.NotificationLoggerTag) /** - * The NotificationDrawerManager receives notification events as they arrive (from event stream or fcm) and + * This class receives notification events as they arrive from the PushHandler calling [onNotifiableEventReceived] and * organise them in order to display them in the notification drawer. * Events can be grouped into the same notification, old (already read) events can be removed to do some cleaning. */ From 6ea629a4d20dc76c00669dacc980ad1160f6b3f4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 11:24:41 +0200 Subject: [PATCH 065/115] Rename interface `NotificationDrawerManager` to `NotificationCleaner` since it actually contains only method to remove notifications. --- .../response/AcceptDeclineInvitePresenter.kt | 8 ++--- .../AcceptDeclineInvitePresenterTest.kt | 16 ++++----- ...rawerManager.kt => NotificationCleaner.kt} | 2 +- .../libraries/push/impl/di/PushBindsModule.kt | 4 +-- .../DefaultNotificationDrawerManager.kt | 4 +-- .../NotificationBroadcastReceiverHandler.kt | 18 +++++----- ...otificationBroadcastReceiverHandlerTest.kt | 36 +++++++++---------- ...rManager.kt => FakeNotificationCleaner.kt} | 6 ++-- .../android/samples/minimal/RoomListScreen.kt | 4 +-- 9 files changed, 49 insertions(+), 49 deletions(-) rename libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/{NotificationDrawerManager.kt => NotificationCleaner.kt} (96%) rename libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/{FakeNotificationDrawerManager.kt => FakeNotificationCleaner.kt} (96%) diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt index 0972eb3eb8..73a82a71f5 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt @@ -34,7 +34,7 @@ import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.join.JoinRoom -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.util.Optional @@ -44,7 +44,7 @@ import kotlin.jvm.optionals.getOrNull class AcceptDeclineInvitePresenter @Inject constructor( private val client: MatrixClient, private val joinRoom: JoinRoom, - private val notificationDrawerManager: NotificationDrawerManager, + private val notificationCleaner: NotificationCleaner, ) : Presenter { @Composable override fun present(): AcceptDeclineInviteState { @@ -112,7 +112,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( trigger = JoinedRoom.Trigger.Invite, ) .onSuccess { - notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId) + notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId) } .map { roomId } } @@ -122,7 +122,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( suspend { client.getRoom(roomId)?.use { it.leave().getOrThrow() - notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId) + notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId) } roomId }.runCatchingUpdatingState(declinedAction) diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt index faa1388fea..9a12e33b33 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt @@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager -import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner +import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -133,7 +133,7 @@ class AcceptDeclineInvitePresenterTest { val clearMembershipNotificationForRoomLambda = lambdaRecorder { _, _ -> Result.success(Unit) } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda ) val declineInviteSuccess = lambdaRecorder { -> @@ -149,7 +149,7 @@ class AcceptDeclineInvitePresenterTest { } val presenter = createAcceptDeclineInvitePresenter( client = client, - notificationDrawerManager = notificationDrawerManager, + notificationCleaner = fakeNotificationCleaner, ) presenter.test { val inviteData = anInviteData() @@ -219,7 +219,7 @@ class AcceptDeclineInvitePresenterTest { val clearMembershipNotificationForRoomLambda = lambdaRecorder { _, _ -> Result.success(Unit) } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda ) val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List, _: JoinedRoom.Trigger -> @@ -227,7 +227,7 @@ class AcceptDeclineInvitePresenterTest { } val presenter = createAcceptDeclineInvitePresenter( joinRoomLambda = joinRoomSuccess, - notificationDrawerManager = notificationDrawerManager, + notificationCleaner = fakeNotificationCleaner, ) presenter.test { val inviteData = anInviteData() @@ -274,12 +274,12 @@ class AcceptDeclineInvitePresenterTest { joinRoomLambda: (RoomId, List, JoinedRoom.Trigger) -> Result = { _, _, _ -> Result.success(Unit) }, - notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(), + notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), ): AcceptDeclineInvitePresenter { return AcceptDeclineInvitePresenter( client = client, joinRoom = FakeJoinRoom(joinRoomLambda), - notificationDrawerManager = notificationDrawerManager, + notificationCleaner = notificationCleaner, ) } } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationDrawerManager.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt similarity index 96% rename from libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationDrawerManager.kt rename to libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt index f7babb1e35..4183aa36c7 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationDrawerManager.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt @@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId -interface NotificationDrawerManager { +interface NotificationCleaner { fun clearAllMessagesEvents(sessionId: SessionId) fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) fun clearEvent(sessionId: SessionId, eventId: EventId) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt index 63c5198514..f87cdc28a4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt @@ -20,7 +20,7 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Binds import dagger.Module import io.element.android.libraries.di.AppScope -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager @Module @@ -29,5 +29,5 @@ abstract class PushBindsModule { @Binds abstract fun bindNotificationDrawerManager( defaultNotificationDrawerManager: DefaultNotificationDrawerManager - ): NotificationDrawerManager + ): NotificationCleaner } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index 12366aeb1e..63fed5c69d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -30,7 +30,7 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreEventInRoom @@ -59,7 +59,7 @@ class DefaultNotificationDrawerManager @Inject constructor( private val matrixClientProvider: MatrixClientProvider, private val imageLoaderHolder: ImageLoaderHolder, private val activeNotificationsProvider: ActiveNotificationsProvider, -) : NotificationDrawerManager { +) : NotificationCleaner { private var appNavigationStateObserver: Job? = null // TODO EAx add a setting per user for this diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index b1d26ce7ab..f5c407101e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -28,7 +28,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived @@ -47,7 +47,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( private val appCoroutineScope: CoroutineScope, private val matrixClientProvider: MatrixClientProvider, private val sessionPreferencesStore: SessionPreferencesStoreFactory, - private val notificationDrawerManager: NotificationDrawerManager, + private val notificationCleaner: NotificationCleaner, private val actionIds: NotificationActionIds, private val systemClock: SystemClock, private val onNotifiableEventReceived: OnNotifiableEventReceived, @@ -66,26 +66,26 @@ class NotificationBroadcastReceiverHandler @Inject constructor( handleSmartReply(sessionId, roomId, threadId, intent) } actionIds.dismissRoom -> if (roomId != null) { - notificationDrawerManager.clearMessagesForRoom(sessionId, roomId) + notificationCleaner.clearMessagesForRoom(sessionId, roomId) } actionIds.dismissSummary -> - notificationDrawerManager.clearAllMessagesEvents(sessionId) + notificationCleaner.clearAllMessagesEvents(sessionId) actionIds.dismissInvite -> if (roomId != null) { - notificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) + notificationCleaner.clearMembershipNotificationForRoom(sessionId, roomId) } actionIds.dismissEvent -> if (eventId != null) { - notificationDrawerManager.clearEvent(sessionId, eventId) + notificationCleaner.clearEvent(sessionId, eventId) } actionIds.markRoomRead -> if (roomId != null) { - notificationDrawerManager.clearMessagesForRoom(sessionId, roomId) + notificationCleaner.clearMessagesForRoom(sessionId, roomId) handleMarkAsRead(sessionId, roomId) } actionIds.join -> if (roomId != null) { - notificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) + notificationCleaner.clearMembershipNotificationForRoom(sessionId, roomId) handleJoinRoom(sessionId, roomId) } actionIds.reject -> if (roomId != null) { - notificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) + notificationCleaner.clearMembershipNotificationForRoom(sessionId, roomId) handleRejectRoom(sessionId, roomId) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt index 1948c7eb0c..d4706461d8 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt @@ -40,11 +40,11 @@ import io.element.android.libraries.preferences.api.store.SessionPreferencesStor import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.preferences.test.FakeSessionPreferencesStoreFactory import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.push.FakeOnNotifiableEventReceived import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived -import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager +import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.test.strings.FakeStringProvider @@ -90,11 +90,11 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test dismiss room`() = runTest { val clearMessagesForRoomLambda = lambdaRecorder { _, _ -> } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMessagesForRoomLambda = clearMessagesForRoomLambda, ) val sut = createNotificationBroadcastReceiverHandler( - notificationDrawerManager = notificationDrawerManager + notificationCleaner = fakeNotificationCleaner ) sut.onReceive( createIntent( @@ -111,11 +111,11 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test dismiss summary`() = runTest { val clearAllMessagesEventsLambda = lambdaRecorder { _ -> } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearAllMessagesEventsLambda = clearAllMessagesEventsLambda, ) val sut = createNotificationBroadcastReceiverHandler( - notificationDrawerManager = notificationDrawerManager + notificationCleaner = fakeNotificationCleaner ) sut.onReceive( createIntent( @@ -140,11 +140,11 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test dismiss Invite`() = runTest { val clearMembershipNotificationForRoomLambda = lambdaRecorder { _, _ -> } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, ) val sut = createNotificationBroadcastReceiverHandler( - notificationDrawerManager = notificationDrawerManager + notificationCleaner = fakeNotificationCleaner ) sut.onReceive( createIntent( @@ -170,11 +170,11 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test dismiss Event`() = runTest { val clearEventLambda = lambdaRecorder { _, _ -> } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearEventLambda = clearEventLambda, ) val sut = createNotificationBroadcastReceiverHandler( - notificationDrawerManager = notificationDrawerManager + notificationCleaner = fakeNotificationCleaner ) sut.onReceive( createIntent( @@ -227,13 +227,13 @@ class NotificationBroadcastReceiverHandlerTest { ) val clearMessagesForRoomLambda = lambdaRecorder { _, _ -> } val matrixRoom = FakeMatrixRoom() - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMessagesForRoomLambda = clearMessagesForRoomLambda, ) val sut = createNotificationBroadcastReceiverHandler( sessionPreferencesStore = sessionPreferencesStore, matrixRoom = matrixRoom, - notificationDrawerManager = notificationDrawerManager + notificationCleaner = fakeNotificationCleaner ) sut.onReceive( createIntent( @@ -262,12 +262,12 @@ class NotificationBroadcastReceiverHandlerTest { fun `Test join room`() = runTest { val joinRoom = lambdaRecorder> { _ -> Result.success(Unit) } val clearMembershipNotificationForRoomLambda = lambdaRecorder { _, _ -> } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, ) val sut = createNotificationBroadcastReceiverHandler( joinRoom = joinRoom, - notificationDrawerManager = notificationDrawerManager, + notificationCleaner = fakeNotificationCleaner, ) sut.onReceive( createIntent( @@ -301,12 +301,12 @@ class NotificationBroadcastReceiverHandlerTest { leaveRoomLambda = leaveRoom } val clearMembershipNotificationForRoomLambda = lambdaRecorder { _, _ -> } - val notificationDrawerManager = FakeNotificationDrawerManager( + val fakeNotificationCleaner = FakeNotificationCleaner( clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, ) val sut = createNotificationBroadcastReceiverHandler( matrixRoom = matrixRoom, - notificationDrawerManager = notificationDrawerManager + notificationCleaner = fakeNotificationCleaner ) sut.onReceive( createIntent( @@ -447,7 +447,7 @@ class NotificationBroadcastReceiverHandlerTest { joinRoomLambda = joinRoom }, sessionPreferencesStore: SessionPreferencesStoreFactory = FakeSessionPreferencesStoreFactory(), - notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(), + notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), systemClock: SystemClock = FakeSystemClock(), onNotifiableEventReceived: OnNotifiableEventReceived = FakeOnNotifiableEventReceived(), stringProvider: StringProvider = FakeStringProvider(), @@ -463,7 +463,7 @@ class NotificationBroadcastReceiverHandlerTest { } }, sessionPreferencesStore = sessionPreferencesStore, - notificationDrawerManager = notificationDrawerManager, + notificationCleaner = notificationCleaner, actionIds = actionIds, systemClock = systemClock, onNotifiableEventReceived = onNotifiableEventReceived, diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationDrawerManager.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt similarity index 96% rename from libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationDrawerManager.kt rename to libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt index b42f05aa5f..eeb3688730 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationDrawerManager.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt @@ -19,16 +19,16 @@ package io.element.android.libraries.push.test.notifications import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.tests.testutils.lambda.lambdaError -class FakeNotificationDrawerManager( +class FakeNotificationCleaner( private val clearAllMessagesEventsLambda: (SessionId) -> Unit = { lambdaError() }, private val clearMessagesForRoomLambda: (SessionId, RoomId) -> Unit = { _, _ -> lambdaError() }, private val clearEventLambda: (SessionId, EventId) -> Unit = { _, _ -> lambdaError() }, private val clearMembershipNotificationForSessionLambda: (SessionId) -> Unit = { lambdaError() }, private val clearMembershipNotificationForRoomLambda: (SessionId, RoomId) -> Unit = { _, _ -> lambdaError() } -) : NotificationDrawerManager { +) : NotificationCleaner { override fun clearAllMessagesEvents(sessionId: SessionId) { clearAllMessagesEventsLambda(sessionId) } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index fa19762800..c0b8fd929d 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -56,7 +56,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.impl.room.join.DefaultJoinRoom import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStore -import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager +import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.services.analytics.noop.NoopAnalyticsService import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import kotlinx.coroutines.launch @@ -144,7 +144,7 @@ class RoomListScreen( acceptDeclineInvitePresenter = AcceptDeclineInvitePresenter( client = matrixClient, joinRoom = DefaultJoinRoom(matrixClient, NoopAnalyticsService()), - notificationDrawerManager = FakeNotificationDrawerManager(), + notificationCleaner = FakeNotificationCleaner(), ), analyticsService = NoopAnalyticsService(), fullScreenIntentPermissionsPresenter = object : FullScreenIntentPermissionsPresenter { From 756e04493e4ca55b8f7699f42c56255c2b0edf91 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 11:29:55 +0200 Subject: [PATCH 066/115] Remove `PushBindsModule` and use `ContributesBinding`. --- .../libraries/push/impl/di/PushBindsModule.kt | 33 ------------------- .../DefaultNotificationDrawerManager.kt | 2 ++ 2 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt deleted file mode 100644 index f87cdc28a4..0000000000 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushBindsModule.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.push.impl.di - -import com.squareup.anvil.annotations.ContributesTo -import dagger.Binds -import dagger.Module -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.push.api.notifications.NotificationCleaner -import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager - -@Module -@ContributesTo(AppScope::class) -abstract class PushBindsModule { - @Binds - abstract fun bindNotificationDrawerManager( - defaultNotificationDrawerManager: DefaultNotificationDrawerManager - ): NotificationCleaner -} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index 63fed5c69d..5610356c55 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.notifications import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationManagerCompat +import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.AppScope @@ -51,6 +52,7 @@ private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag. * Events can be grouped into the same notification, old (already read) events can be removed to do some cleaning. */ @SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) class DefaultNotificationDrawerManager @Inject constructor( private val notificationManager: NotificationManagerCompat, private val notificationRenderer: NotificationRenderer, From 85ceb0296c5ecf7eb514970f28afbbe59ccac62d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 13:07:30 +0200 Subject: [PATCH 067/115] When user manually mark a room as read, also dismiss the notifications for this room. --- features/roomlist/impl/build.gradle.kts | 2 + .../roomlist/impl/RoomListPresenter.kt | 3 ++ .../roomlist/impl/RoomListPresenterTest.kt | 43 +++++++++++++++---- .../android/libraries/matrix/test/TestData.kt | 1 + .../android/samples/minimal/RoomListScreen.kt | 1 + 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index dac4e1c507..60403b25b0 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { implementation(projects.libraries.permissions.api) implementation(projects.libraries.permissions.noop) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.push.api) implementation(projects.features.invite.api) implementation(projects.features.networkmonitor.api) implementation(projects.features.leaveroom.api) @@ -79,6 +80,7 @@ dependencies { testImplementation(projects.libraries.permissions.noop) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) testImplementation(projects.features.networkmonitor.test) 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 493047bc8b..d5f9404eff 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 @@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList @@ -97,6 +98,7 @@ class RoomListPresenter @Inject constructor( private val analyticsService: AnalyticsService, private val acceptDeclineInvitePresenter: Presenter, private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter, + private val notificationCleaner: NotificationCleaner, ) : Presenter { private val encryptionService: EncryptionService = client.encryptionService() private val syncService: SyncService = client.syncService() @@ -268,6 +270,7 @@ class RoomListPresenter @Inject constructor( } private fun CoroutineScope.markAsRead(roomId: RoomId) = launch { + notificationCleaner.clearMessagesForRoom(client.sessionId, roomId) client.getRoom(roomId)?.use { room -> room.setUnreadFlag(isUnread = false) val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) { 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 e1e0970756..9528def3e1 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 @@ -51,6 +51,7 @@ import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPe import io.element.android.libraries.indicator.impl.DefaultIndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.room.CurrentUserMembership @@ -62,6 +63,9 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_ID_3 +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -75,6 +79,8 @@ import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.libraries.push.api.notifications.NotificationCleaner +import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.EventsRecorder @@ -503,35 +509,54 @@ class RoomListPresenterTest { @Test fun `present - check that the room is marked as read with correct RR and as unread`() = runTest { val room = FakeMatrixRoom() + val room2 = FakeMatrixRoom(roomId = A_ROOM_ID_2) + val room3 = FakeMatrixRoom(roomId = A_ROOM_ID_3) + val allRooms = setOf(room, room2, room3) val sessionPreferencesStore = InMemorySessionPreferencesStore() val matrixClient = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) + givenGetRoomResult(A_ROOM_ID_2, room2) + givenGetRoomResult(A_ROOM_ID_3, room3) } val analyticsService = FakeAnalyticsService() val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val clearMessagesForRoomLambda = lambdaRecorder { _, _ -> } + val notificationCleaner = FakeNotificationCleaner( + clearMessagesForRoomLambda = clearMessagesForRoomLambda, + ) val presenter = createRoomListPresenter( client = matrixClient, coroutineScope = scope, sessionPreferencesStore = sessionPreferencesStore, analyticsService = analyticsService, + notificationCleaner = notificationCleaner, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() - assertThat(room.markAsReadCalls).isEmpty() - assertThat(room.setUnreadFlagCalls).isEmpty() + allRooms.forEach { + assertThat(it.markAsReadCalls).isEmpty() + assertThat(it.setUnreadFlagCalls).isEmpty() + } initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID)) assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ)) assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false)) - initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID)) - assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ)) - assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true)) + clearMessagesForRoomLambda.assertions().isCalledOnce() + .with(value(A_SESSION_ID), value(A_ROOM_ID)) + initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID_2)) + assertThat(room2.markAsReadCalls).isEqualTo(emptyList()) + assertThat(room2.setUnreadFlagCalls).isEqualTo(listOf(true)) // Test again with private read receipts sessionPreferencesStore.setSendPublicReadReceipts(false) - initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID)) - assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ, ReceiptType.READ_PRIVATE)) - assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true, false)) + initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID_3)) + assertThat(room3.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ_PRIVATE)) + assertThat(room3.setUnreadFlagCalls).isEqualTo(listOf(false)) + clearMessagesForRoomLambda.assertions().isCalledExactly(2) + .withSequence( + listOf(value(A_SESSION_ID), value(A_ROOM_ID)), + listOf(value(A_SESSION_ID), value(A_ROOM_ID_3)), + ) assertThat(analyticsService.capturedEvents).containsExactly( Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle), Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle), @@ -633,6 +658,7 @@ class RoomListPresenterTest { filtersPresenter: Presenter = Presenter { aRoomListFiltersState() }, searchPresenter: Presenter = Presenter { aRoomListSearchState() }, acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, + notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), ) = RoomListPresenter( client = client, networkMonitor = networkMonitor, @@ -660,5 +686,6 @@ class RoomListPresenterTest { analyticsService = analyticsService, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, fullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter(), + notificationCleaner = notificationCleaner, ) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index d10d1ad0d2..a84fe1edf6 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -48,6 +48,7 @@ val A_SPACE_ID = SpaceId("!aSpaceId:domain") val A_SPACE_ID_2 = SpaceId("!aSpaceId2:domain") val A_ROOM_ID = RoomId("!aRoomId:domain") val A_ROOM_ID_2 = RoomId("!aRoomId2:domain") +val A_ROOM_ID_3 = RoomId("!aRoomId3:domain") val A_THREAD_ID = ThreadId("\$aThreadId") val A_THREAD_ID_2 = ThreadId("\$aThreadId2") val AN_EVENT_ID = EventId("\$anEventId") diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index c0b8fd929d..b91daeecfd 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -158,6 +158,7 @@ class RoomListScreen( ) } }, + notificationCleaner = FakeNotificationCleaner(), ) @Composable From d626660023e6efb2ff957bd3b2fdbdac1a05cf45 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 16 Jul 2024 13:17:04 +0200 Subject: [PATCH 068/115] Fix crash in the room list after a forced log out in background (#3180) --- .../matrix/impl/roomlist/RoomListExtensions.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index ad987e5cae..df30159c09 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.onEach import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind import org.matrix.rustcomponents.sdk.RoomListEntriesListener import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate -import org.matrix.rustcomponents.sdk.RoomListException import org.matrix.rustcomponents.sdk.RoomListInterface import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListLoadingState @@ -128,11 +127,10 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow Date: Tue, 16 Jul 2024 12:20:51 +0000 Subject: [PATCH 069/115] Update plugin dependencycheck to v10.0.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d21bef0f9..fe2efd26f2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -224,7 +224,7 @@ anvil = { id = "com.squareup.anvil", version.ref = "anvil" } detekt = "io.gitlab.arturbosch.detekt:1.23.6" ktlint = "org.jlleitschuh.gradle.ktlint:12.1.1" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:10.0.2" +dependencycheck = "org.owasp.dependencycheck:10.0.3" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:1.3.4" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } From 459c99e45feb875f13128dbc60669c153e6ba902 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 15:47:21 +0200 Subject: [PATCH 070/115] Fix typo. --- .../features/lockscreen/impl/unlock/keypad/PinKeypad.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt index 4033a1b6f7..dfd6b367b7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt @@ -129,7 +129,7 @@ private fun PinKeypadRow( ) } is PinKeypadModel.Number -> { - PinKeyBadDigitButton( + PinKeypadDigitButton( size = pinKeySize, modifier = commonModifier, digit = model.number.toString(), @@ -158,7 +158,7 @@ private fun PinKeypadButton( } @Composable -private fun PinKeyBadDigitButton( +private fun PinKeypadDigitButton( digit: String, size: Dp, onClick: (String) -> Unit, From 0db221e953874c2c8d7c81bec7c692086be4f2a5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 16:01:45 +0200 Subject: [PATCH 071/115] Ensure that pinKeyMaxWidth and pinKeyMaxHeight are never negative. Fix crash `Exception: java.lang.IllegalStateException: lineHeight can't be negative` Can be due to the rendering when the Activity is animated maybe? --- .../features/lockscreen/impl/unlock/keypad/PinKeypad.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt index dfd6b367b7..c54fdb67f3 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt @@ -37,7 +37,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.coerceAtMost +import androidx.compose.ui.unit.coerceIn import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.times @@ -50,6 +50,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf private val spaceBetweenPinKey = 16.dp +private val minSizePinKey = 16.dp private val maxSizePinKey = 80.dp @Composable @@ -61,8 +62,8 @@ fun PinKeypad( verticalAlignment: Alignment.Vertical = Alignment.Top, horizontalAlignment: Alignment.Horizontal = Alignment.Start, ) { - val pinKeyMaxWidth = ((maxWidth - 2 * spaceBetweenPinKey) / 3).coerceAtMost(maxSizePinKey) - val pinKeyMaxHeight = ((maxHeight - 3 * spaceBetweenPinKey) / 4).coerceAtMost(maxSizePinKey) + val pinKeyMaxWidth = ((maxWidth - 2 * spaceBetweenPinKey) / 3).coerceIn(minSizePinKey, maxSizePinKey) + val pinKeyMaxHeight = ((maxHeight - 3 * spaceBetweenPinKey) / 4).coerceIn(minSizePinKey, maxSizePinKey) val pinKeySize = if (pinKeyMaxWidth < pinKeyMaxHeight) pinKeyMaxWidth else pinKeyMaxHeight val horizontalArrangement = spacedBy(spaceBetweenPinKey, Alignment.CenterHorizontally) From 83a366af2ae00d6dbe4167727fd0f040979218d8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 16:29:33 +0200 Subject: [PATCH 072/115] Fix quality (Konsist failure) --- .../android/features/roomlist/impl/RoomListPresenterTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9528def3e1..b48a603a85 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 @@ -545,7 +545,7 @@ class RoomListPresenterTest { clearMessagesForRoomLambda.assertions().isCalledOnce() .with(value(A_SESSION_ID), value(A_ROOM_ID)) initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID_2)) - assertThat(room2.markAsReadCalls).isEqualTo(emptyList()) + assertThat(room2.markAsReadCalls).isEmpty() assertThat(room2.setUnreadFlagCalls).isEqualTo(listOf(true)) // Test again with private read receipts sessionPreferencesStore.setSendPublicReadReceipts(false) From 530894e8751ac654e1a26415f304c95082c5ec32 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:06:43 +0000 Subject: [PATCH 073/115] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.32 --- gradle/libs.versions.toml | 2 +- .../matrix/impl/RustMatrixClientFactory.kt | 2 -- .../matrix/impl/timeline/RustTimeline.kt | 20 +++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0dd4eb923..2042d82418 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -162,7 +162,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.31" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.32" 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" } 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 03d8680e4e..ad563f39f3 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 @@ -82,8 +82,6 @@ class RustMatrixClientFactory @Inject constructor( .addRootCertificates(userCertificatesProvider.provides()) .autoEnableBackups(true) .autoEnableCrossSigning(true) - // FIXME Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376 - .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) .run { // Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep proxyProvider.provides()?.let { proxy(it) } ?: this diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 9cdb8d1366..23ea2a222d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -328,16 +328,20 @@ class RustTimeline( runCatching { when { originalEventId != null -> { - inner.editByEventId( - newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), - eventId = originalEventId.value, - ) + inner.getEventTimelineItemByEventId(originalEventId.value).use { + inner.edit( + newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), + item = it, + ) + } } transactionId != null -> { - inner.edit( - newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), - item = inner.getEventTimelineItemByTransactionId(transactionId.value), - ) + inner.getEventTimelineItemByTransactionId(transactionId.value).use { + inner.edit( + newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), + item = it, + ) + } } else -> { error("Either originalEventId or transactionId must be non null") From 792667c9a4c6b689669c8214c7dd9f96ebc6df8e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 18:00:25 +0200 Subject: [PATCH 074/115] Iterate on MatrixPatterns. --- .../matrix/api/core/MatrixPatterns.kt | 36 ++++++++----------- .../matrix/api/core/MatrixPatternsTest.kt | 2 +- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index 20e7746e3f..e9ac911330 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -27,32 +27,29 @@ object MatrixPatterns { // Note: TLD is not mandatory (localhost, IP address...) private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?" + // See https://spec.matrix.org/v1.11/appendices/#opaque-identifiers + private const val OPAQUE_ID_REGEX = "[0-9A-Za-z-\\._~]+" + // regex pattern to find matrix user ids in a string. // See https://matrix.org/docs/spec/appendices#historical-user-ids // Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec. - private const val MATRIX_USER_IDENTIFIER_REGEX = "^@.*?$DOMAIN_REGEX$" + private const val MATRIX_USER_IDENTIFIER_REGEX = "^@\\S+?$DOMAIN_REGEX$" private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) - // regex pattern to find room ids in a string. - private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9.-]+$DOMAIN_REGEX" + // regex pattern to match room ids. + private const val MATRIX_ROOM_IDENTIFIER_REGEX = "^!$OPAQUE_ID_REGEX$DOMAIN_REGEX$" private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) - // regex pattern to find room aliases in a string. - private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX" + // regex pattern to match room aliases. + private const val MATRIX_ROOM_ALIAS_REGEX = "^#\\S+$DOMAIN_REGEX$" private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE) - // regex pattern to find message ids in a string. + // regex pattern to match event ids. // Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec. - private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$.+$DOMAIN_REGEX$" + private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$$OPAQUE_ID_REGEX$DOMAIN_REGEX$" private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) - // regex pattern to find message ids in a string. - private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE) - - // Ref: https://matrix.org/docs/spec/rooms/v4#event-ids - private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE) + private const val MAX_IDENTIFIER_LENGTH = 255 /** * Tells if a string is a valid user Id. @@ -61,7 +58,7 @@ object MatrixPatterns { * @return true if the string is a valid user id */ fun isUserId(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER + return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER && str.length <= MAX_IDENTIFIER_LENGTH } /** @@ -79,7 +76,7 @@ object MatrixPatterns { * @return true if the string is a valid room Id */ fun isRoomId(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER + return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER && str.length <= MAX_IDENTIFIER_LENGTH } /** @@ -89,7 +86,7 @@ object MatrixPatterns { * @return true if the string is a valid room alias. */ fun isRoomAlias(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS + return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS && str.length <= MAX_IDENTIFIER_LENGTH } /** @@ -99,10 +96,7 @@ object MatrixPatterns { * @return true if the string is a valid event id. */ fun isEventId(str: String?): Boolean { - return str != null && - (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER || - str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 || - str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) + return str != null && str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER && str.length <= MAX_IDENTIFIER_LENGTH } /** diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt index d195789d14..64e66372ab 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -54,7 +54,7 @@ class MatrixPatternsTest { } @Test - fun `findPatterns - returns raw room event ids`() { + fun `findPatterns - returns raw event ids`() { val text = "A \$event:server.com and \$event2:server.com" val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) assertThat(patterns).containsExactly( From 52900076bd3aa92e590959d31f43c35a8cfc5891 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 18:17:21 +0200 Subject: [PATCH 075/115] Add test on MatrixPatterns functions. --- .../matrix/api/core/MatrixPatternsTest.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt index 64e66372ab..d8399b4823 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import org.junit.Test class MatrixPatternsTest { + private val longLocalPart = "a".repeat(255 - ":server.com".length - 1) + @Test fun `findPatterns - returns raw user ids`() { val text = "A @user:server.com and @user2:server.com" @@ -90,6 +92,70 @@ class MatrixPatternsTest { assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 46)) } + @Test + fun `test isRoomId`() { + assertThat(MatrixPatterns.isRoomId(null)).isFalse() + assertThat(MatrixPatterns.isRoomId("")).isFalse() + assertThat(MatrixPatterns.isRoomId("not a room id")).isFalse() + assertThat(MatrixPatterns.isRoomId(" !room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomId("!room:server.com ")).isFalse() + assertThat(MatrixPatterns.isRoomId("@room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomId("#room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomId("\$room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomId("!${longLocalPart}a:server.com")).isFalse() + + assertThat(MatrixPatterns.isRoomId("!room:server.com")).isTrue() + assertThat(MatrixPatterns.isRoomId("!$longLocalPart:server.com")).isTrue() + } + + @Test + fun `test isRoomAlias`() { + assertThat(MatrixPatterns.isRoomAlias(null)).isFalse() + assertThat(MatrixPatterns.isRoomAlias("")).isFalse() + assertThat(MatrixPatterns.isRoomAlias("not a room alias")).isFalse() + assertThat(MatrixPatterns.isRoomAlias(" #room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomAlias("#room:server.com ")).isFalse() + assertThat(MatrixPatterns.isRoomAlias("@room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomAlias("!room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomAlias("\$room:server.com")).isFalse() + assertThat(MatrixPatterns.isRoomAlias("#${longLocalPart}a:server.com")).isFalse() + + assertThat(MatrixPatterns.isRoomAlias("#room:server.com")).isTrue() + assertThat(MatrixPatterns.isRoomAlias("#$longLocalPart:server.com")).isTrue() + } + + @Test + fun `test isEventId`() { + assertThat(MatrixPatterns.isEventId(null)).isFalse() + assertThat(MatrixPatterns.isEventId("")).isFalse() + assertThat(MatrixPatterns.isEventId("not an event id")).isFalse() + assertThat(MatrixPatterns.isEventId(" \$event:server.com")).isFalse() + assertThat(MatrixPatterns.isEventId("\$event:server.com ")).isFalse() + assertThat(MatrixPatterns.isEventId("@event:server.com")).isFalse() + assertThat(MatrixPatterns.isEventId("!event:server.com")).isFalse() + assertThat(MatrixPatterns.isEventId("#event:server.com")).isFalse() + assertThat(MatrixPatterns.isEventId("$${longLocalPart}a:server.com")).isFalse() + + assertThat(MatrixPatterns.isEventId("\$event:server.com")).isTrue() + assertThat(MatrixPatterns.isEventId("$$longLocalPart:server.com")).isTrue() + } + + @Test + fun `test isUserId`() { + assertThat(MatrixPatterns.isUserId(null)).isFalse() + assertThat(MatrixPatterns.isUserId("")).isFalse() + assertThat(MatrixPatterns.isUserId("not a user id")).isFalse() + assertThat(MatrixPatterns.isUserId(" @user:server.com")).isFalse() + assertThat(MatrixPatterns.isUserId("@user:server.com ")).isFalse() + assertThat(MatrixPatterns.isUserId("!user:server.com")).isFalse() + assertThat(MatrixPatterns.isUserId("#user:server.com")).isFalse() + assertThat(MatrixPatterns.isUserId("\$user:server.com")).isFalse() + assertThat(MatrixPatterns.isUserId("@${longLocalPart}a:server.com")).isFalse() + + assertThat(MatrixPatterns.isUserId("@user:server.com")).isTrue() + assertThat(MatrixPatterns.isUserId("@$longLocalPart:server.com")).isTrue() + } + private fun aPermalinkParser(block: (String) -> PermalinkData = { PermalinkData.FallbackLink(Uri.EMPTY) }) = object : PermalinkParser { override fun parse(uriString: String): PermalinkData { return block(uriString) From 5ae4668da5a6205a3c60d08e8d18a72d0516f888 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 18:23:25 +0200 Subject: [PATCH 076/115] No need to make the block optional. --- .../android/libraries/matrix/api/core/MatrixPatterns.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index e9ac911330..46fe4f7e99 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -112,8 +112,8 @@ object MatrixPatterns { * Note not all cases are implemented. */ fun findPatterns(text: CharSequence, permalinkParser: PermalinkParser): List { - val rawTextMatches = "\\S+?$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text) - val urlMatches = "\\[\\S+?\\]\\((\\S+?)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text) + val rawTextMatches = "\\S+$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text) + val urlMatches = "\\[\\S+\\]\\((\\S+)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text) val atRoomMatches = Regex("@room").findAll(text) return buildList { for (match in rawTextMatches) { From 79d29d680b4a216e7a9198b907b56f99c7b9afa9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 18:25:20 +0200 Subject: [PATCH 077/115] Add extra test for room alias. --- .../android/libraries/matrix/api/core/MatrixPatternsTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt index d8399b4823..399901cf06 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -121,6 +121,7 @@ class MatrixPatternsTest { assertThat(MatrixPatterns.isRoomAlias("#${longLocalPart}a:server.com")).isFalse() assertThat(MatrixPatterns.isRoomAlias("#room:server.com")).isTrue() + assertThat(MatrixPatterns.isRoomAlias("#nico's-stickers:neko.dev")).isTrue() assertThat(MatrixPatterns.isRoomAlias("#$longLocalPart:server.com")).isTrue() } From 67e09295de34fc04744f89961cb270123fdb79a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2024 18:40:03 +0200 Subject: [PATCH 078/115] Still need to support both eventId legacy and v4 --- .../android/libraries/matrix/api/core/MatrixPatterns.kt | 8 +++++++- .../libraries/matrix/api/core/MatrixPatternsTest.kt | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index 46fe4f7e99..d706ff9e3a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -49,6 +49,9 @@ object MatrixPatterns { private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$$OPAQUE_ID_REGEX$DOMAIN_REGEX$" private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) + private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$$OPAQUE_ID_REGEX" + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE) + private const val MAX_IDENTIFIER_LENGTH = 255 /** @@ -96,7 +99,10 @@ object MatrixPatterns { * @return true if the string is a valid event id. */ fun isEventId(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER && str.length <= MAX_IDENTIFIER_LENGTH + return str != null && + (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER || + str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) && + str.length <= MAX_IDENTIFIER_LENGTH } /** diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt index 399901cf06..a29714764f 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -136,9 +136,12 @@ class MatrixPatternsTest { assertThat(MatrixPatterns.isEventId("!event:server.com")).isFalse() assertThat(MatrixPatterns.isEventId("#event:server.com")).isFalse() assertThat(MatrixPatterns.isEventId("$${longLocalPart}a:server.com")).isFalse() + assertThat(MatrixPatterns.isEventId("\$" + "a".repeat(255))).isFalse() assertThat(MatrixPatterns.isEventId("\$event:server.com")).isTrue() assertThat(MatrixPatterns.isEventId("$$longLocalPart:server.com")).isTrue() + assertThat(MatrixPatterns.isEventId("\$9BozuV4TBw6rfRW3rMEgZ5v-jNk1D6FA8Hd1OsWqT9k")).isTrue() + assertThat(MatrixPatterns.isEventId("\$" + "a".repeat(254))).isTrue() } @Test From d6c99870f9084667f928ad699517a55da397cdba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2024 10:06:44 +0200 Subject: [PATCH 079/115] Be more lenient on Matrix pattern to support existing rooms in the wild (will fix crash on debug build). --- .../matrix/api/core/MatrixPatterns.kt | 47 ++++++++++++------- .../matrix/api/core/MatrixPatternsTest.kt | 2 + 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index d706ff9e3a..023fb9322b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -27,18 +27,20 @@ object MatrixPatterns { // Note: TLD is not mandatory (localhost, IP address...) private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?" - // See https://spec.matrix.org/v1.11/appendices/#opaque-identifiers - private const val OPAQUE_ID_REGEX = "[0-9A-Za-z-\\._~]+" + private const val BASE_64_ALPHABET = "[0-9A-Za-z/\\+=]+" + private const val BASE_64_URL_SAFE_ALPHABET = "[0-9A-Za-z/\\-_]+" // regex pattern to find matrix user ids in a string. // See https://matrix.org/docs/spec/appendices#historical-user-ids // Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec. - private const val MATRIX_USER_IDENTIFIER_REGEX = "^@\\S+?$DOMAIN_REGEX$" - private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) + // Note: local part can be empty + private const val MATRIX_USER_IDENTIFIER_REGEX = "^@\\S*?$DOMAIN_REGEX$" + private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex() // regex pattern to match room ids. - private const val MATRIX_ROOM_IDENTIFIER_REGEX = "^!$OPAQUE_ID_REGEX$DOMAIN_REGEX$" - private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) + // Note: roomId can be arbitrary strings, including space and new line char + private const val MATRIX_ROOM_IDENTIFIER_REGEX = "^!.+$DOMAIN_REGEX$" + private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.DOT_MATCHES_ALL) // regex pattern to match room aliases. private const val MATRIX_ROOM_ALIAS_REGEX = "^#\\S+$DOMAIN_REGEX$" @@ -46,11 +48,17 @@ object MatrixPatterns { // regex pattern to match event ids. // Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec. - private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$$OPAQUE_ID_REGEX$DOMAIN_REGEX$" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) + // v1 and v2: arbitrary string + domain + private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$.+$DOMAIN_REGEX$" + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex() - private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$$OPAQUE_ID_REGEX" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE) + // v3: base64 + private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$$BASE_64_ALPHABET" + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex() + + // v4: url-safe base64 + private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$$BASE_64_URL_SAFE_ALPHABET" + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex() private const val MAX_IDENTIFIER_LENGTH = 255 @@ -61,7 +69,9 @@ object MatrixPatterns { * @return true if the string is a valid user id */ fun isUserId(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER && str.length <= MAX_IDENTIFIER_LENGTH + return str != null && + str.length <= MAX_IDENTIFIER_LENGTH && + str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER } /** @@ -79,7 +89,9 @@ object MatrixPatterns { * @return true if the string is a valid room Id */ fun isRoomId(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER && str.length <= MAX_IDENTIFIER_LENGTH + return str != null && + str.length <= MAX_IDENTIFIER_LENGTH && + str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER } /** @@ -89,7 +101,9 @@ object MatrixPatterns { * @return true if the string is a valid room alias. */ fun isRoomAlias(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS && str.length <= MAX_IDENTIFIER_LENGTH + return str != null && + str.length <= MAX_IDENTIFIER_LENGTH && + str matches PATTERN_CONTAIN_MATRIX_ALIAS } /** @@ -100,9 +114,10 @@ object MatrixPatterns { */ fun isEventId(str: String?): Boolean { return str != null && - (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER || - str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) && - str.length <= MAX_IDENTIFIER_LENGTH + str.length <= MAX_IDENTIFIER_LENGTH && + (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 || + str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 || + str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER) } /** diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt index a29714764f..68f938adc9 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -106,6 +106,7 @@ class MatrixPatternsTest { assertThat(MatrixPatterns.isRoomId("!room:server.com")).isTrue() assertThat(MatrixPatterns.isRoomId("!$longLocalPart:server.com")).isTrue() + assertThat(MatrixPatterns.isRoomId("!#test/room\nversion 11, with @🐈️:maunium.net")).isTrue() } @Test @@ -157,6 +158,7 @@ class MatrixPatternsTest { assertThat(MatrixPatterns.isUserId("@${longLocalPart}a:server.com")).isFalse() assertThat(MatrixPatterns.isUserId("@user:server.com")).isTrue() + assertThat(MatrixPatterns.isUserId("@:server.com")).isTrue() assertThat(MatrixPatterns.isUserId("@$longLocalPart:server.com")).isTrue() } From f5e866e18c162fdb3425037c6654db46e2e6470d Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 17 Jul 2024 10:20:47 +0200 Subject: [PATCH 080/115] Fix pillification not working for non formatted message bodies (#3201) * Fix pillification not working for non formatted message bodies Pure Markdown bodies weren't being 'pillified' so their mentions were turned into UI elements in the timeline. A new `pillifiedBody` property was added to `TimelineItemTextBasedContent` to fix this. * Use shorter version of `textWithMentions` computation --- .../components/TimelineItemEventRow.kt | 2 +- ...imelineItemEventRowForDirectRoomPreview.kt | 2 +- .../TimelineItemEventRowTimestampPreview.kt | 1 + ...ineItemEventRowWithManyReactionsPreview.kt | 2 +- .../TimelineItemEventRowWithRRPreview.kt | 12 +++------ .../TimelineItemEventRowWithReplyPreview.kt | 4 +-- .../components/event/TimelineItemTextView.kt | 13 +++++----- .../TimelineItemContentMessageFactory.kt | 5 ++++ .../model/event/TimelineItemEmoteContent.kt | 1 + .../event/TimelineItemEventContentProvider.kt | 8 ++++-- .../model/event/TimelineItemNoticeContent.kt | 1 + .../event/TimelineItemTextBasedContent.kt | 17 +++++++++++++ .../model/event/TimelineItemTextContent.kt | 1 + .../impl/utils/TextPillificationHelper.kt | 13 +++++++--- .../messages/impl/MessagesPresenterTest.kt | 4 +-- .../fixtures/TimelineItemsFactoryFixtures.kt | 2 ++ .../MessageComposerPresenterTest.kt | 8 ++---- .../TimelineItemContentMessageFactoryTest.kt | 2 ++ ... => DefaultTextPillificationHelperTest.kt} | 4 +-- .../impl/utils/FakeTextPillificationHelper.kt | 25 +++++++++++++++++++ 20 files changed, 90 insertions(+), 37 deletions(-) rename features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/{TextPillificationHelperTest.kt => DefaultTextPillificationHelperTest.kt} (98%) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 37febdcf50..a2ac0ac7b1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -571,7 +571,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { event = aTimelineItemEvent( senderDisplayName = "Sender with a super long name that should ellipsize", isMine = isMine, - content = aTimelineItemTextContent().copy( + content = aTimelineItemTextContent( body = "A long text which will be displayed on several lines and" + " hopefully can be manually adjusted to test different behaviors." ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt index 1981bc2c39..48f6fc3a1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt @@ -34,7 +34,7 @@ internal fun TimelineItemEventRowForDirectRoomPreview() = ElementPreview { ATimelineItemEventRow( event = aTimelineItemEvent( isMine = it, - content = aTimelineItemTextContent().copy( + content = aTimelineItemTextContent( body = "A long text which will be displayed on several lines and" + " hopefully can be manually adjusted to test different behaviors." ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt index 8ec1d0e554..d0c8f8ffaf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt @@ -41,6 +41,7 @@ internal fun TimelineItemEventRowTimestampPreview( event = event.copy( content = oldContent.copy( body = str, + pillifiedBody = str, ), reactionsState = aTimelineItemReactions(count = 0), ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt index efb619bcc2..c5095705bc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt @@ -32,7 +32,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { ATimelineItemEventRow( event = aTimelineItemEvent( isMine = isMine, - content = aTimelineItemTextContent().copy( + content = aTimelineItemTextContent( body = "A couple of multi-line messages with many reactions attached." + " One sent by me and another from someone else." ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt index 85ff3a77bf..00298e1ade 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt @@ -41,9 +41,7 @@ internal fun TimelineItemEventRowWithRRPreview( event = aTimelineItemEvent( isMine = false, sendState = null, - content = aTimelineItemTextContent().copy( - body = "A message from someone else" - ), + content = aTimelineItemTextContent(body = "A message from someone else"), timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), @@ -55,9 +53,7 @@ internal fun TimelineItemEventRowWithRRPreview( event = aTimelineItemEvent( isMine = true, sendState = state.sendState, - content = aTimelineItemTextContent().copy( - body = "A message from me" - ), + content = aTimelineItemTextContent(body = "A message from me"), timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), @@ -69,9 +65,7 @@ internal fun TimelineItemEventRowWithRRPreview( event = aTimelineItemEvent( isMine = true, sendState = state.sendState, - content = aTimelineItemTextContent().copy( - body = "A last message from me" - ), + content = aTimelineItemTextContent(body = "A last message from me"), timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt index 7b6bb471c7..294503ecf0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt @@ -48,9 +48,7 @@ internal fun TimelineItemEventRowWithReplyContentToPreview( event = aTimelineItemEvent( isMine = it, timelineItemReactions = aTimelineItemReactions(count = 0), - content = aTimelineItemTextContent().copy( - body = "A reply." - ), + content = aTimelineItemTextContent(body = "A reply."), inReplyTo = inReplyToDetails, displayNameAmbiguous = displayNameAmbiguous, groupPosition = TimelineItemGroupPosition.First, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index a04d524d43..e691c1e549 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -77,14 +77,13 @@ internal fun getTextWithResolvedMentions(content: TimelineItemTextBasedContent): val userProfileCache = LocalRoomMemberProfilesCache.current val lastCacheUpdate by userProfileCache.lastCacheUpdate.collectAsState() val mentionSpanTheme = LocalMentionSpanTheme.current - val formattedBody = remember(content.formattedBody, mentionSpanTheme, lastCacheUpdate) { - content.formattedBody?.let { formattedBody -> - updateMentionSpans(formattedBody, userProfileCache) - mentionSpanTheme.updateMentionStyles(formattedBody) - formattedBody - } + val formattedBody = content.formattedBody ?: content.pillifiedBody + val textWithMentions = remember(formattedBody, mentionSpanTheme, lastCacheUpdate) { + updateMentionSpans(formattedBody, userProfileCache) + mentionSpanTheme.updateMentionStyles(formattedBody) + formattedBody } - return SpannableString(formattedBody ?: content.body) + return SpannableString(textWithMentions) } private fun updateMentionSpans(text: CharSequence, cache: RoomMemberProfilesCache): Boolean { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 5c0623b480..48141ccc03 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -36,6 +36,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent 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.utils.TextPillificationHelper import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -69,6 +70,7 @@ class TimelineItemContentMessageFactory @Inject constructor( private val featureFlagService: FeatureFlagService, private val htmlConverterProvider: HtmlConverterProvider, private val permalinkParser: PermalinkParser, + private val textPillificationHelper: TextPillificationHelper, ) { suspend fun create( content: MessageContent, @@ -126,6 +128,7 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemTextContent( body = body, + pillifiedBody = textPillificationHelper.pillify(body), htmlDocument = null, plainText = body, formattedBody = null, @@ -215,6 +218,7 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemTextContent( body = body, + pillifiedBody = textPillificationHelper.pillify(body), htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser), formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), isEdited = content.isEdited, @@ -224,6 +228,7 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemTextContent( body = body, + pillifiedBody = textPillificationHelper.pillify(body), htmlDocument = null, formattedBody = body.withLinks(), isEdited = content.isEdited, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt index 37c02e4a90..d651c02e4c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt @@ -21,6 +21,7 @@ import org.jsoup.nodes.Document data class TimelineItemEmoteContent( override val body: String, + override val pillifiedBody: CharSequence = body, override val htmlDocument: Document?, override val plainText: String = htmlDocument?.toPlainText() ?: body, override val formattedBody: CharSequence?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt index 9878a28402..29fa048e1f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt @@ -84,8 +84,12 @@ fun aTimelineItemNoticeContent() = TimelineItemNoticeContent( fun aTimelineItemRedactedContent() = TimelineItemRedactedContent -fun aTimelineItemTextContent() = TimelineItemTextContent( - body = "Text", +fun aTimelineItemTextContent( + body: String = "Text", + pillifiedBody: CharSequence = body, +) = TimelineItemTextContent( + body = body, + pillifiedBody = pillifiedBody, htmlDocument = null, formattedBody = null, isEdited = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt index e9358370a3..535a463024 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt @@ -21,6 +21,7 @@ import org.jsoup.nodes.Document data class TimelineItemNoticeContent( override val body: String, + override val pillifiedBody: CharSequence = body, override val htmlDocument: Document?, override val plainText: String = htmlDocument?.toPlainText() ?: body, override val formattedBody: CharSequence?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt index a0ece96855..86ee842b85 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt @@ -19,13 +19,30 @@ package io.element.android.features.messages.impl.timeline.model.event import androidx.compose.runtime.Immutable import org.jsoup.nodes.Document +/** + * Represents a text based content of a timeline item event (a message, a notice, an emote event...). + */ @Immutable sealed interface TimelineItemTextBasedContent : TimelineItemEventContent { + /** The raw body of the event, in Markdown format. */ val body: String + + /** The body of the event, with mentions replaced by their pillified version. */ + val pillifiedBody: CharSequence + + /** The parsed HTML DOM of the formatted event body. */ val htmlDocument: Document? + + /** The formatted body of the event, already parsed and with the DOM translated to Android spans. */ val formattedBody: CharSequence? + + /** The plain text version of the event body. This is the Markdown version without actual Markdown formatting. */ val plainText: String + + /** Whether the event has been edited. */ val isEdited: Boolean + + /** The raw HTML body of the event. */ val htmlBody: String? get() = htmlDocument?.body()?.html() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt index dc4728f300..b70cc7cbd9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt @@ -21,6 +21,7 @@ import org.jsoup.nodes.Document data class TimelineItemTextContent( override val body: String, + override val pillifiedBody: CharSequence = body, override val htmlDocument: Document?, override val plainText: String = htmlDocument?.toPlainText() ?: body, override val formattedBody: CharSequence?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt index ad4e6aa64f..0e403dcb00 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt @@ -19,6 +19,8 @@ package io.element.android.features.messages.impl.utils import android.text.Spannable import android.text.SpannableStringBuilder import androidx.core.text.getSpans +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.MatrixPatternType import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.RoomAlias @@ -30,14 +32,19 @@ import io.element.android.libraries.textcomposer.mentions.MentionSpan import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import javax.inject.Inject -class TextPillificationHelper @Inject constructor( +interface TextPillificationHelper { + fun pillify(text: CharSequence): CharSequence +} + +@ContributesBinding(RoomScope::class) +class DefaultTextPillificationHelper @Inject constructor( private val mentionSpanProvider: MentionSpanProvider, private val permalinkBuilder: PermalinkBuilder, private val permalinkParser: PermalinkParser, private val roomMemberProfilesCache: RoomMemberProfilesCache, -) { +) : TextPillificationHelper { @Suppress("LoopWithTooManyJumpStatements") - fun pillify(text: CharSequence): CharSequence { + override fun pillify(text: CharSequence): CharSequence { val matches = MatrixPatterns.findPatterns(text, permalinkParser).sortedByDescending { it.end } if (matches.isEmpty()) return text 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 906b8d4ac1..2d08b8d4a2 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 @@ -44,7 +44,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.typing.TypingNotificationPresenter -import io.element.android.features.messages.impl.utils.TextPillificationHelper +import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager @@ -787,7 +787,7 @@ class MessagesPresenterTest { timelineController = TimelineController(matrixRoom), draftService = FakeComposerDraftService(), mentionSpanProvider = mentionSpanProvider, - pillificationHelper = TextPillificationHelper(mentionSpanProvider, FakePermalinkBuilder(), FakePermalinkParser(), RoomMemberProfilesCache()), + pillificationHelper = FakeTextPillificationHelper(), roomMemberProfilesCache = RoomMemberProfilesCache(), ).apply { showTextFormatting = true 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 95dffe2bc0..88e2da81c9 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 @@ -33,6 +33,7 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemDaySeparatorFactory import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper +import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper 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 @@ -62,6 +63,7 @@ internal fun TestScope.aTimelineItemsFactory( featureFlagService = FakeFeatureFlagService(), htmlConverterProvider = FakeHtmlConverterProvider(), permalinkParser = FakePermalinkParser(), + textPillificationHelper = FakeTextPillificationHelper(), ), redactedMessageFactory = TimelineItemContentRedactedFactory(), stickerFactory = TimelineItemContentStickerFactory( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index c7ebab4c82..de48ee62b6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.timeline.TimelineController +import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -1362,12 +1363,7 @@ class MessageComposerPresenterTest { permalinkParser: PermalinkParser = FakePermalinkParser(), mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkParser), roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), - textPillificationHelper: TextPillificationHelper = TextPillificationHelper( - mentionSpanProvider = mentionSpanProvider, - permalinkBuilder = permalinkBuilder, - permalinkParser = permalinkParser, - roomMemberProfilesCache = roomMemberProfilesCache, - ), + textPillificationHelper: TextPillificationHelper = FakeTextPillificationHelper(), isRichTextEditorEnabled: Boolean = true, draftService: ComposerDraftService = FakeComposerDraftService(), ) = MessageComposerPresenter( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index ec5571df68..59cd3cf383 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent 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.utils.FakeTextPillificationHelper import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes @@ -697,6 +698,7 @@ class TimelineItemContentMessageFactoryTest { featureFlagService = featureFlagService, htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform), permalinkParser = permalinkParser, + textPillificationHelper = FakeTextPillificationHelper(), ) private fun createStickerContent( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt similarity index 98% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt index 4cc695c1e8..94d109a060 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt @@ -35,7 +35,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class TextPillificationHelperTest { +class DefaultTextPillificationHelperTest { @Test fun `pillify - adds pills for user ids`() { val text = "A @user:server.com" @@ -119,7 +119,7 @@ class TextPillificationHelperTest { permalinkBuilder: FakePermalinkBuilder = FakePermalinkBuilder(), mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkparser), roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), - ) = TextPillificationHelper( + ) = DefaultTextPillificationHelper( mentionSpanProvider = mentionSpanProvider, permalinkBuilder = permalinkBuilder, permalinkParser = permalinkparser, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt new file mode 100644 index 0000000000..1ca8e5ae5a --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.utils + +class FakeTextPillificationHelper( + private val pillifyLambda: (CharSequence) -> CharSequence = { it } +) : TextPillificationHelper { + override fun pillify(text: CharSequence): CharSequence { + return pillifyLambda(text) + } +} From 10717b5e48683e0b39f4d73d52a1e3449515ab0b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2024 10:58:58 +0200 Subject: [PATCH 081/115] Render errors in room member list view. --- .../impl/members/RoomMemberListPresenter.kt | 39 ++++-- .../impl/members/RoomMemberListState.kt | 18 +-- .../members/RoomMemberListStateProvider.kt | 81 ++++++----- .../impl/members/RoomMemberListView.kt | 130 +++++++++++------- 4 files changed, 159 insertions(+), 109 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 305393c822..51f9845a49 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -31,6 +31,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -59,10 +60,10 @@ class RoomMemberListPresenter @AssistedInject constructor( @Composable override fun present(): RoomMemberListState { val coroutineScope = rememberCoroutineScope() - var roomMembers by remember { mutableStateOf(RoomMembers.loading()) } + var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } var searchQuery by rememberSaveable { mutableStateOf("") } var searchResults by remember { - mutableStateOf>(SearchBarResultState.Initial()) + mutableStateOf>>(SearchBarResultState.Initial()) } var isSearchActive by rememberSaveable { mutableStateOf(false) } @@ -82,6 +83,12 @@ class RoomMemberListPresenter @AssistedInject constructor( if (membersState is MatrixRoomMembersState.Unknown) { return@LaunchedEffect } + val _membersState = membersState + if (_membersState is MatrixRoomMembersState.Error && _membersState.roomMembers().orEmpty().isEmpty()) { + // Cannot fetch members and no cached members, display the error + roomMembers = AsyncData.Failure(_membersState.failure) + return@LaunchedEffect + } withContext(coroutineDispatchers.io) { val members = membersState.roomMembers().orEmpty().groupBy { it.membership } val info = room.roomInfoFlow.first() @@ -90,14 +97,18 @@ class RoomMemberListPresenter @AssistedInject constructor( // This result will come from the timeline loading membership events and it'll be wrong. return@withContext } - roomMembers = RoomMembers( + val result = RoomMembers( invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList()) .sortedWith(PowerLevelRoomMemberComparator()) .toImmutableList(), banned = members.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(), - isLoading = membersState is MatrixRoomMembersState.Pending, ) + roomMembers = if (membersState is MatrixRoomMembersState.Pending) { + AsyncData.Loading(result) + } else { + AsyncData.Success(result) + } } } @@ -110,15 +121,19 @@ class RoomMemberListPresenter @AssistedInject constructor( if (results.isEmpty()) { SearchBarResultState.NoResultsFound() } else { + val result = RoomMembers( + invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), + joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()) + .sortedWith(PowerLevelRoomMemberComparator()) + .toImmutableList(), + banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(), + ) SearchBarResultState.Results( - RoomMembers( - invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), - joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()) - .sortedWith(PowerLevelRoomMemberComparator()) - .toImmutableList(), - banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(), - isLoading = membersState is MatrixRoomMembersState.Pending, - ) + if (membersState is MatrixRoomMembersState.Pending) { + AsyncData.Loading(result) + } else { + AsyncData.Success(result) + } ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 67a0802f02..b71da8bfd8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -17,15 +17,15 @@ package io.element.android.features.roomdetails.impl.members import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf data class RoomMemberListState( - val roomMembers: RoomMembers, + val roomMembers: AsyncData, val searchQuery: String, - val searchResults: SearchBarResultState, + val searchResults: SearchBarResultState>, val isSearchActive: Boolean, val canInvite: Boolean, val moderationState: RoomMembersModerationState, @@ -36,14 +36,4 @@ data class RoomMembers( val invited: ImmutableList, val joined: ImmutableList, val banned: ImmutableList, - val isLoading: Boolean, -) { - companion object { - fun loading() = RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf(), - isLoading = true, - ) - } -} +) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index 8be4124e0d..11132040b0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember @@ -29,14 +30,15 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider get() = sequenceOf( aRoomMemberListState( - roomMembers = RoomMembers( - invited = persistentListOf(aVictor(), aWalter()), - joined = persistentListOf(anAlice(), aBob(), aWalter()), - banned = persistentListOf(), - isLoading = false, + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(aVictor(), aWalter()), + joined = persistentListOf(anAlice(), aBob(), aWalter()), + banned = persistentListOf(), + ) ) ), - aRoomMemberListState(roomMembers = RoomMembers.loading()), + aRoomMemberListState(roomMembers = AsyncData.Loading()), aRoomMemberListState().copy(canInvite = true), aRoomMemberListState().copy(isSearchActive = false), aRoomMemberListState().copy(isSearchActive = true), @@ -45,11 +47,12 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider get() = sequenceOf( aRoomMemberListState( - roomMembers = RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"), - ), - isLoading = false, + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(), + joined = persistentListOf(), + banned = persistentListOf( + aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"), + aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"), + aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"), + ), + ) ), moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true), ), aRoomMemberListState( - roomMembers = RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"), - ), - isLoading = true, + roomMembers = AsyncData.Loading( + RoomMembers( + invited = persistentListOf(), + joined = persistentListOf(), + banned = persistentListOf( + aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"), + aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"), + aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"), + ), + ) ), moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true), ), aRoomMemberListState( - roomMembers = RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf(), - isLoading = false, + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(), + joined = persistentListOf(), + banned = persistentListOf(), + ) ), moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true), ) @@ -103,8 +112,8 @@ internal class RoomMemberListStateBannedProvider : PreviewParameterProvider = SearchBarResultState.Initial(), + roomMembers: AsyncData = AsyncData.Loading(), + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), moderationState: RoomMembersModerationState = aRoomMembersModerationState(), ) = RoomMemberListState( roomMembers = roomMembers, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index b12ecf4ec8..b0110b7039 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationView +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview @@ -128,7 +129,6 @@ fun RoomMemberListView( if (!state.isSearchActive) { RoomMemberList( - isLoading = state.roomMembers.isLoading, roomMembers = state.roomMembers, showMembersCount = true, canDisplayBannedUsersControls = state.moderationState.canDisplayBannedUsers, @@ -149,8 +149,7 @@ fun RoomMemberListView( @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable private fun RoomMemberList( - isLoading: Boolean, - roomMembers: RoomMembers, + roomMembers: AsyncData, showMembersCount: Boolean, selectedSection: SelectedSection, onSelectedSectionChange: (SelectedSection) -> Unit, @@ -183,7 +182,7 @@ private fun RoomMemberList( } } AnimatedVisibility( - visible = isLoading, + visible = roomMembers.isLoading(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), ) { @@ -191,47 +190,72 @@ private fun RoomMemberList( } } } - when (selectedSection) { - SelectedSection.MEMBERS -> { - if (roomMembers.invited.isNotEmpty()) { - roomMemberListSection( - headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) }, - members = roomMembers.invited, - onMemberSelected = { onSelectUser(it) } - ) - } - if (roomMembers.joined.isNotEmpty()) { - roomMemberListSection( - headerText = { - if (showMembersCount) { - val memberCount = roomMembers.joined.count() - pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) - } else { - stringResource(id = R.string.screen_room_member_list_room_members_header_title) - } - }, - members = roomMembers.joined, - onMemberSelected = { onSelectUser(it) } - ) - } + when (roomMembers) { + is AsyncData.Failure -> failureItem(roomMembers.error) + is AsyncData.Loading, + is AsyncData.Success -> memberItems( + roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn, + selectedSection = selectedSection, + onSelectUser = onSelectUser, + showMembersCount = showMembersCount, + ) + AsyncData.Uninitialized -> Unit + } + } +} + +private fun LazyListScope.memberItems( + roomMembers: RoomMembers, + selectedSection: SelectedSection, + onSelectUser: (RoomMember) -> Unit, + showMembersCount: Boolean, +) { + when (selectedSection) { + SelectedSection.MEMBERS -> { + if (roomMembers.invited.isNotEmpty()) { + roomMemberListSection( + headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) }, + members = roomMembers.invited, + onMemberSelected = { onSelectUser(it) } + ) } - SelectedSection.BANNED -> { // Banned users - if (roomMembers.banned.isNotEmpty()) { - roomMemberListSection( - headerText = null, - members = roomMembers.banned, - onMemberSelected = { onSelectUser(it) } - ) - } else { - item { - Box(Modifier.fillParentMaxSize().padding(horizontal = 16.dp)) { - Text( - modifier = Modifier.padding(bottom = 56.dp).align(Alignment.Center), - text = stringResource(id = R.string.screen_room_member_list_banned_empty), - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) + if (roomMembers.joined.isNotEmpty()) { + roomMemberListSection( + headerText = { + if (showMembersCount) { + val memberCount = roomMembers.joined.count() + pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) + } else { + stringResource(id = R.string.screen_room_member_list_room_members_header_title) } + }, + members = roomMembers.joined, + onMemberSelected = { onSelectUser(it) } + ) + } + } + SelectedSection.BANNED -> { // Banned users + if (roomMembers.banned.isNotEmpty()) { + roomMemberListSection( + headerText = null, + members = roomMembers.banned, + onMemberSelected = { onSelectUser(it) } + ) + } else { + item { + Box( + Modifier + .fillParentMaxSize() + .padding(horizontal = 16.dp) + ) { + Text( + modifier = Modifier + .padding(bottom = 56.dp) + .align(Alignment.Center), + text = stringResource(id = R.string.screen_room_member_list_banned_empty), + color = ElementTheme.colors.textSecondary, + textAlign = TextAlign.Center, + ) } } } @@ -239,9 +263,22 @@ private fun RoomMemberList( } } +private fun LazyListScope.failureItem(failure: Throwable) { + item { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), + text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage, + color = ElementTheme.colors.textCriticalPrimary, + textAlign = TextAlign.Center, + ) + } +} + private fun LazyListScope.roomMemberListSection( headerText: @Composable (() -> String)?, - members: ImmutableList, + members: ImmutableList?, onMemberSelected: (RoomMember) -> Unit, ) { headerText?.let { @@ -254,7 +291,7 @@ private fun LazyListScope.roomMemberListSection( ) } } - items(members) { matrixUser -> + items(members.orEmpty()) { matrixUser -> RoomMemberListItem( modifier = Modifier.fillMaxWidth(), roomMember = matrixUser, @@ -320,7 +357,7 @@ private fun RoomMemberListTopBar( @Composable private fun RoomMemberSearchBar( query: String, - state: SearchBarResultState, + state: SearchBarResultState>, active: Boolean, placeHolderTitle: String, onActiveChange: (Boolean) -> Unit, @@ -339,7 +376,6 @@ private fun RoomMemberSearchBar( resultState = state, resultHandler = { results -> RoomMemberList( - isLoading = false, roomMembers = results, showMembersCount = false, onSelectUser = { onSelectUser(it) }, From 26aaa9e07018d19083e0b3fe07f09ee15bd720a9 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 17 Jul 2024 09:10:50 +0000 Subject: [PATCH 082/115] Update screenshots --- ...es.roomdetails.impl.members_RoomMemberListView_Day_8_en.png | 3 +++ ....roomdetails.impl.members_RoomMemberListView_Night_8_en.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png new file mode 100644 index 0000000000..3dafa23bb0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd04abc2eec057ef2dbd02e06c3f0ecd8e66fed3f760f293af4bb86c91e031f6 +size 18284 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png new file mode 100644 index 0000000000..516705b7ee --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93cd01b7fe046a90b49a20311941bb3a07e8d2bd71eb9335343f2c15713ce73e +size 17222 From 263c0588437d9cffd0b743765e2bb38d8b9e2c89 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2024 11:31:25 +0200 Subject: [PATCH 083/115] Fix quality and test compilation. --- .../impl/members/RoomMemberListPresenter.kt | 6 +++--- .../roomdetails/members/RoomMemberListPresenterTest.kt | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 51f9845a49..01e1cf99c2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -83,10 +83,10 @@ class RoomMemberListPresenter @AssistedInject constructor( if (membersState is MatrixRoomMembersState.Unknown) { return@LaunchedEffect } - val _membersState = membersState - if (_membersState is MatrixRoomMembersState.Error && _membersState.roomMembers().orEmpty().isEmpty()) { + val finalMembersState = membersState + if (finalMembersState is MatrixRoomMembersState.Error && finalMembersState.roomMembers().orEmpty().isEmpty()) { // Cannot fetch members and no cached members, display the error - roomMembers = AsyncData.Failure(_membersState.failure) + roomMembers = AsyncData.Failure(finalMembersState.failure) return@LaunchedEffect } withContext(coroutineDispatchers.io) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt index c6862f3e2e..a352d8bf27 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt @@ -62,7 +62,7 @@ class RoomMemberListPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.roomMembers.isLoading).isTrue() + assertThat(initialState.roomMembers.isLoading()).isTrue() assertThat(initialState.searchQuery).isEmpty() assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(initialState.isSearchActive).isFalse() @@ -70,9 +70,9 @@ class RoomMemberListPresenterTest { // Skip item while the new members state is processed skipItems(1) val loadedMembersState = awaitItem() - assertThat(loadedMembersState.roomMembers.isLoading).isFalse() - assertThat(loadedMembersState.roomMembers.invited).isEqualTo(listOf(aVictor(), aWalter())) - assertThat(loadedMembersState.roomMembers.joined).isNotEmpty() + assertThat(loadedMembersState.roomMembers.isLoading()).isFalse() + assertThat(loadedMembersState.roomMembers.dataOrNull()?.invited).isEqualTo(listOf(aVictor(), aWalter())) + assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty() } } @@ -126,7 +126,7 @@ class RoomMemberListPresenterTest { assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice") val searchSearchResultDelivered = awaitItem() assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) - assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.joined.first().displayName) + assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.dataOrNull()!!.joined.first().displayName) .isEqualTo("Alice") } } From ef12408b6ea4758a3d87cc8a87c410a4c708650f Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 17 Jul 2024 14:45:14 +0200 Subject: [PATCH 084/115] Performance : add cache on roomListItem and fullRoom --- .../matrix/impl/room/RustMatrixRoom.kt | 5 +- .../matrix/impl/room/RustRoomFactory.kt | 91 +++++++++++++------ .../impl/timeline/MatrixTimelineItemMapper.kt | 4 +- .../impl/timeline/RoomTimelineExtensions.kt | 8 +- .../matrix/impl/timeline/RustTimeline.kt | 23 +++-- .../impl/timeline/TimelineItemsSubscriber.kt | 14 ++- 6 files changed, 96 insertions(+), 49 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 463defb284..d23e5efb2a 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 @@ -196,8 +196,6 @@ class RustMatrixRoom( override fun destroy() { roomCoroutineScope.cancel() liveTimeline.close() - innerRoom.destroy() - roomListItem.destroy() } override val displayName: String @@ -627,12 +625,13 @@ class RustMatrixRoom( isLive: Boolean, onNewSyncedEvent: () -> Unit = {}, ): Timeline { + val timelineCoroutineScope = roomCoroutineScope.childScope(coroutineDispatchers.main, "TimelineScope-$roomId-$timeline") return RustTimeline( isKeyBackupEnabled = isKeyBackupEnabled, isLive = isLive, matrixRoom = this, systemClock = systemClock, - roomCoroutineScope = roomCoroutineScope, + coroutineScope = timelineCoroutineScope, dispatcher = roomDispatcher, lastLoginTimestamp = sessionData.loginTimestamp, onNewSyncedEvent = onNewSyncedEvent, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index 8d3d056aab..a69cbc67d3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.room +import androidx.collection.lruCache import io.element.android.appconfig.TimelineConfig import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.RoomId @@ -41,6 +42,8 @@ import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter import timber.log.Timber import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService +private const val CACHE_SIZE = 16 + class RustRoomFactory( private val sessionId: SessionId, private val notificationSettingsService: NotificationSettingsService, @@ -55,8 +58,23 @@ class RustRoomFactory( private val getSessionData: suspend () -> SessionData, ) { @OptIn(ExperimentalCoroutinesApi::class) - private val createRoomDispatcher = dispatchers.io.limitedParallelism(1) + private val dispatcher = dispatchers.io.limitedParallelism(1) private val mutex = Mutex() + private var isDestroyed: Boolean = false + + private data class RustRoomObjects( + val roomListItem: RoomListItem, + val fullRoom: Room, + ) + + private val cache = lruCache( + maxSize = CACHE_SIZE, + onEntryRemoved = { evicted, roomId, oldRoom, _ -> + Timber.d("On room removed from cache: $roomId, evicted: $evicted") + oldRoom.roomListItem.close() + oldRoom.fullRoom.close() + } + ) private val matrixRoomInfoMapper = MatrixRoomInfoMapper() @@ -70,30 +88,41 @@ class RustRoomFactory( ) } - suspend fun create(roomId: RoomId): MatrixRoom? = withContext(createRoomDispatcher) { - var cachedPairOfRoom: Pair? + suspend fun destroy() { + withContext(dispatcher) { + mutex.withLock { + Timber.d("Destroying room factory") + cache.evictAll() + isDestroyed = true + } + } + } + + suspend fun create(roomId: RoomId): MatrixRoom? = withContext(dispatcher) { mutex.withLock { - // Check if already in memory... - cachedPairOfRoom = pairOfRoom(roomId) - if (cachedPairOfRoom == null) { + if (isDestroyed) { + Timber.d("Room factory is destroyed, returning null for $roomId") + return@withContext null + } + var roomObjects: RustRoomObjects? = getRoomObjects(roomId) + if (roomObjects == null) { // ... otherwise, lets wait for the SS to load all rooms and check again. roomListService.allRooms.awaitLoaded() - cachedPairOfRoom = pairOfRoom(roomId) + roomObjects = getRoomObjects(roomId) } - } - if (cachedPairOfRoom == null) { - Timber.d("No room found for $roomId") - return@withContext null - } - cachedPairOfRoom?.let { (roomListItem, fullRoom) -> + if (roomObjects == null) { + Timber.d("No room found for $roomId, returning null") + return@withContext null + } + val liveTimeline = roomObjects.fullRoom.timeline() RustMatrixRoom( sessionId = sessionId, isKeyBackupEnabled = isKeyBackupEnabled(), - roomListItem = roomListItem, - innerRoom = fullRoom, - innerTimeline = fullRoom.timeline(), - notificationSettingsService = notificationSettingsService, + roomListItem = roomObjects.roomListItem, + innerRoom = roomObjects.fullRoom, + innerTimeline = liveTimeline, sessionCoroutineScope = sessionCoroutineScope, + notificationSettingsService = notificationSettingsService, coroutineDispatchers = dispatchers, systemClock = systemClock, roomContentForwarder = roomContentForwarder, @@ -104,20 +133,28 @@ class RustRoomFactory( } } - private suspend fun pairOfRoom(roomId: RoomId): Pair? { - val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value) + private suspend fun getRoomObjects(roomId: RoomId): RustRoomObjects? { + cache[roomId]?.let { + Timber.d("Room found in cache for $roomId") + return it + } + val roomListItem = innerRoomListService.roomOrNull(roomId.value) + if (roomListItem == null) { + Timber.d("Room not found for $roomId") + return null + } val fullRoom = try { - cachedRoomListItem?.fullRoomWithTimeline(filter = eventFilters) + roomListItem.fullRoomWithTimeline(filter = eventFilters) } catch (e: RoomListException) { Timber.e(e, "Failed to get full room with timeline for $roomId") - null + return null } - return if (cachedRoomListItem == null || fullRoom == null) { - Timber.d("No room cached for $roomId") - null - } else { - Timber.d("Found room cached for $roomId") - Pair(cachedRoomListItem, fullRoom) + Timber.d("Got full room with timeline for $roomId") + return RustRoomObjects( + roomListItem = roomListItem, + fullRoom = fullRoom, + ).also { + cache.put(roomId, it) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt index c8cb4eef1c..d38c86f0f9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -26,7 +26,7 @@ import org.matrix.rustcomponents.sdk.TimelineItem class MatrixTimelineItemMapper( private val fetchDetailsForEvent: suspend (EventId) -> Result, - private val roomCoroutineScope: CoroutineScope, + private val coroutineScope: CoroutineScope, private val virtualTimelineItemMapper: VirtualTimelineItemMapper = VirtualTimelineItemMapper(), private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(), ) { @@ -49,7 +49,7 @@ class MatrixTimelineItemMapper( return MatrixTimelineItem.Other } - private fun fetchEventDetails(eventId: EventId) = roomCoroutineScope.launch { + private fun fetchEventDetails(eventId: EventId) = coroutineScope.launch { fetchDetailsForEvent(eventId) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt index dc012f67b3..2adcab5491 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt @@ -25,13 +25,13 @@ import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.catch import org.matrix.rustcomponents.sdk.PaginationStatusListener -import org.matrix.rustcomponents.sdk.Timeline import org.matrix.rustcomponents.sdk.TimelineDiff +import org.matrix.rustcomponents.sdk.TimelineInterface import org.matrix.rustcomponents.sdk.TimelineListener import timber.log.Timber import uniffi.matrix_sdk_ui.LiveBackPaginationStatus -internal fun Timeline.liveBackPaginationStatus(): Flow = callbackFlow { +internal fun TimelineInterface.liveBackPaginationStatus(): Flow = callbackFlow { val listener = object : PaginationStatusListener { override fun onUpdate(status: LiveBackPaginationStatus) { trySend(status) @@ -45,7 +45,7 @@ internal fun Timeline.liveBackPaginationStatus(): Flow Timber.d(it, "liveBackPaginationStatus() failed") }.buffer(Channel.UNLIMITED) -internal fun Timeline.timelineDiffFlow(): Flow> = +internal fun TimelineInterface.timelineDiffFlow(): Flow> = callbackFlow { val listener = object : TimelineListener { override fun onUpdate(diff: List) { @@ -62,7 +62,7 @@ internal fun Timeline.timelineDiffFlow(): Flow> = Timber.d(it, "timelineDiffFlow() failed") }.buffer(Channel.UNLIMITED) -internal suspend fun Timeline.runWithTimelineListenerRegistered(action: suspend () -> Unit) { +internal suspend fun TimelineInterface.runWithTimelineListenerRegistered(action: suspend () -> Unit) { val result = addListener(NoOpTimelineListener) try { action() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 9f48e59995..fb42f27f62 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -56,6 +56,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -64,6 +65,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch @@ -88,9 +90,9 @@ class RustTimeline( private val inner: InnerTimeline, private val isLive: Boolean, systemClock: SystemClock, - roomCoroutineScope: CoroutineScope, isKeyBackupEnabled: Boolean, private val matrixRoom: MatrixRoom, + private val coroutineScope: CoroutineScope, private val dispatcher: CoroutineDispatcher, lastLoginTimestamp: Date?, private val roomContentForwarder: RoomContentForwarder, @@ -106,7 +108,7 @@ class RustTimeline( private val inReplyToMapper = InReplyToMapper(timelineEventContentMapper) private val timelineItemMapper = MatrixTimelineItemMapper( fetchDetailsForEvent = this::fetchDetailsForEvent, - roomCoroutineScope = roomCoroutineScope, + coroutineScope = coroutineScope, virtualTimelineItemMapper = VirtualTimelineItemMapper(), eventTimelineItemMapper = EventTimelineItemMapper( contentMapper = timelineEventContentMapper @@ -124,7 +126,7 @@ class RustTimeline( ) private val timelineItemsSubscriber = TimelineItemsSubscriber( timeline = inner, - roomCoroutineScope = roomCoroutineScope, + timelineCoroutineScope = coroutineScope, timelineDiffProcessor = timelineDiffProcessor, initLatch = initLatch, isInit = isInit, @@ -145,13 +147,11 @@ class RustTimeline( ) init { - roomCoroutineScope.launch(dispatcher) { - fetchMembers() - if (isLive) { - // When timeline is live, we need to listen to the back pagination status as - // sdk can automatically paginate backwards. - registerBackPaginationStatusListener() - } + coroutineScope.fetchMembers() + if (isLive) { + // When timeline is live, we need to listen to the back pagination status as + // sdk can automatically paginate backwards. + coroutineScope.registerBackPaginationStatusListener() } } @@ -243,9 +243,12 @@ class RustTimeline( } }.onStart { timelineItemsSubscriber.subscribeIfNeeded() + }.onCompletion { + timelineItemsSubscriber.unsubscribeIfNeeded() } override fun close() { + coroutineScope.cancel() inner.close() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt index 0205ac20e5..a4086c2810 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt @@ -40,10 +40,9 @@ private const val INITIAL_MAX_SIZE = 50 * This class is responsible for subscribing to a timeline and post the items/diffs to the timelineDiffProcessor. * It will also trigger a callback when a new synced event is received. * It will also handle the initial items and make sure they are posted before any diff. - * When closing the room subscription, it will also unsubscribe automatically. */ internal class TimelineItemsSubscriber( - roomCoroutineScope: CoroutineScope, + timelineCoroutineScope: CoroutineScope, dispatcher: CoroutineDispatcher, private val timeline: Timeline, private val timelineDiffProcessor: MatrixTimelineDiffProcessor, @@ -54,8 +53,12 @@ internal class TimelineItemsSubscriber( private var subscriptionCount = 0 private val mutex = Mutex() - private val coroutineScope = roomCoroutineScope.childScope(dispatcher, "TimelineItemsSubscriber") + private val coroutineScope = timelineCoroutineScope.childScope(dispatcher, "TimelineItemsSubscriber") + /** + * Add a subscription to the timeline and start posting items/diffs to the timelineDiffProcessor. + * It will also trigger a callback when a new synced event is received. + */ suspend fun subscribeIfNeeded() = mutex.withLock { if (subscriptionCount == 0) { timeline.timelineDiffFlow() @@ -70,6 +73,11 @@ internal class TimelineItemsSubscriber( subscriptionCount++ } + /** + * Remove a subscription to the timeline and unsubscribe if needed. + * The timeline will be unsubscribed when the last subscription is removed. + * If the timelineCoroutineScope is cancelled, the timeline will be unsubscribed automatically. + */ suspend fun unsubscribeIfNeeded() = mutex.withLock { when (subscriptionCount) { 0 -> return@withLock From 5e6bcbd7ac9c95565ec89f8a78ecd02c29ea7855 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 17 Jul 2024 15:49:11 +0200 Subject: [PATCH 085/115] Performance : do not trigger back pagination when opening room. --- .../matrix/impl/timeline/RustTimeline.kt | 31 ++++++++++++++----- .../impl/timeline/TimelineItemsSubscriber.kt | 8 ++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index fb42f27f62..fe2f01925d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -81,7 +81,6 @@ import timber.log.Timber import uniffi.matrix_sdk_ui.LiveBackPaginationStatus import java.io.File import java.util.Date -import java.util.concurrent.atomic.AtomicBoolean import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline private const val PAGINATION_SIZE = 50 @@ -99,7 +98,7 @@ class RustTimeline( onNewSyncedEvent: () -> Unit, ) : Timeline { private val initLatch = CompletableDeferred() - private val isInit = AtomicBoolean(false) + private val isInit = MutableStateFlow(false) private val _timelineItems: MutableStateFlow> = MutableStateFlow(emptyList()) @@ -208,7 +207,7 @@ class RustTimeline( } private fun canPaginate(direction: Timeline.PaginationDirection): Boolean { - if (!isInit.get()) return false + if (!isInit.value) return false return when (direction) { Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus.value.canPaginate Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.value.canPaginate @@ -226,20 +225,25 @@ class RustTimeline( _timelineItems, backPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(), forwardPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(), - ) { timelineItems, hasMoreToLoadBackward, hasMoreToLoadForward -> + isInit, + ) { timelineItems, hasMoreToLoadBackward, hasMoreToLoadForward, isInit -> withContext(dispatcher) { timelineItems - .let { items -> encryptedHistoryPostProcessor.process(items) } - .let { items -> + .process { items -> encryptedHistoryPostProcessor.process(items) } + .process { items -> roomBeginningPostProcessor.process( items = items, isDm = matrixRoom.isDm, hasMoreToLoadBackwards = hasMoreToLoadBackward ) } - .let { items -> loadingIndicatorsPostProcessor.process(items, hasMoreToLoadBackward, hasMoreToLoadForward) } + .process(predicate = isInit) { items -> + loadingIndicatorsPostProcessor.process(items, hasMoreToLoadBackward, hasMoreToLoadForward) + } // Keep lastForwardIndicatorsPostProcessor last - .let { items -> lastForwardIndicatorsPostProcessor.process(items) } + .process(predicate = isInit) { items -> + lastForwardIndicatorsPostProcessor.process(items) + } } }.onStart { timelineItemsSubscriber.subscribeIfNeeded() @@ -545,3 +549,14 @@ class RustTimeline( } } } + +private suspend fun List.process( + predicate: Boolean = true, + processor: suspend (List) -> List +): List { + return if (predicate) { + processor(this) + } else { + this + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt index a4086c2810..d5454ff254 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex @@ -32,7 +33,6 @@ import org.matrix.rustcomponents.sdk.TimelineChange import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineItem import uniffi.matrix_sdk_ui.EventItemOrigin -import java.util.concurrent.atomic.AtomicBoolean private const val INITIAL_MAX_SIZE = 50 @@ -47,7 +47,7 @@ internal class TimelineItemsSubscriber( private val timeline: Timeline, private val timelineDiffProcessor: MatrixTimelineDiffProcessor, private val initLatch: CompletableDeferred, - private val isInit: AtomicBoolean, + private val isInit: MutableStateFlow, private val onNewSyncedEvent: () -> Unit, ) { private var subscriptionCount = 0 @@ -94,13 +94,13 @@ internal class TimelineItemsSubscriber( ensureActive() timelineDiffProcessor.postItems(it) } - isInit.set(true) + isInit.value = true initLatch.complete(Unit) } private suspend fun postDiffs(diffs: List) { val diffsToProcess = diffs.toMutableList() - if (!isInit.get()) { + if (!isInit.value) { val resetDiff = diffsToProcess.firstOrNull { it.change() == TimelineChange.RESET } if (resetDiff != null) { // Keep using the postItems logic so we can post the timelineItems asap. From 766ae389ce3ef0f7d17bb2357e4126ab462524b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2024 15:41:06 +0200 Subject: [PATCH 086/115] Add generated screen to show open source licenses. For Google Play variant only --- app/build.gradle.kts | 4 + .../FdroidOpenSourceLicensesProvider.kt | 32 ++++++ app/src/gplay/AndroidManifest.xml | 12 +++ .../OssOpenSourcesLicensesProvider.kt | 37 +++++++ app/src/gplay/res/values-night-v27/themes.xml | 25 +++++ app/src/gplay/res/values-night/themes.xml | 45 ++++++++ app/src/gplay/res/values-v27/themes.xml | 25 +++++ app/src/gplay/res/values/styles.xml | 23 ++++ app/src/gplay/res/values/themes.xml | 47 ++++++++ build.gradle.kts | 1 + .../api/OpenSourceLicensesProvider.kt | 24 +++++ .../preferences/impl/about/AboutNode.kt | 5 + .../preferences/impl/about/AboutPresenter.kt | 6 +- .../preferences/impl/about/AboutState.kt | 2 +- .../impl/about/AboutStateProvider.kt | 8 +- .../preferences/impl/about/AboutView.kt | 8 ++ .../impl/about/AboutPresenterTest.kt | 15 ++- .../preferences/impl/about/AboutViewTest.kt | 100 ++++++++++++++++++ .../about/FakeOpenSourceLicensesProvider.kt | 26 +++++ gradle/libs.versions.toml | 2 + 20 files changed, 442 insertions(+), 5 deletions(-) create mode 100644 app/src/fdroid/kotlin/io/element/android/x/licenses/FdroidOpenSourceLicensesProvider.kt create mode 100644 app/src/gplay/AndroidManifest.xml create mode 100644 app/src/gplay/kotlin/io/element/android/x/licenses/OssOpenSourcesLicensesProvider.kt create mode 100644 app/src/gplay/res/values-night-v27/themes.xml create mode 100644 app/src/gplay/res/values-night/themes.xml create mode 100644 app/src/gplay/res/values-v27/themes.xml create mode 100644 app/src/gplay/res/values/styles.xml create mode 100644 app/src/gplay/res/values/themes.xml create mode 100644 features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/OpenSourceLicensesProvider.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/FakeOpenSourceLicensesProvider.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3f5618b208..7778a3246e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,6 +36,7 @@ plugins { id(libs.plugins.firebaseAppDistribution.get().pluginId) alias(libs.plugins.knit) id("kotlin-parcelize") + id("com.google.android.gms.oss-licenses-plugin") // To be able to update the firebase.xml files, uncomment and build the project // id("com.google.gms.google-services") } @@ -250,6 +251,7 @@ dependencies { implementation(projects.anvilannotations) implementation(projects.appnav) implementation(projects.appconfig) + implementation(projects.libraries.uiStrings) anvil(projects.anvilcodegen) // Comment to not include firebase in the project @@ -257,6 +259,8 @@ dependencies { // Comment to not include unified push in the project implementation(projects.libraries.pushproviders.unifiedpush) + "gplayImplementation"(libs.play.services.oss.licenses) + implementation(libs.appyx.core) implementation(libs.androidx.splash) implementation(libs.androidx.core) diff --git a/app/src/fdroid/kotlin/io/element/android/x/licenses/FdroidOpenSourceLicensesProvider.kt b/app/src/fdroid/kotlin/io/element/android/x/licenses/FdroidOpenSourceLicensesProvider.kt new file mode 100644 index 0000000000..2e926f487e --- /dev/null +++ b/app/src/fdroid/kotlin/io/element/android/x/licenses/FdroidOpenSourceLicensesProvider.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.x.licenses + +import android.app.Activity +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.preferences.api.OpenSourceLicensesProvider +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class FdroidOpenSourceLicensesProvider @Inject constructor() : OpenSourceLicensesProvider { + override val hasOpenSourceLicenses: Boolean = false + + override fun navigateToOpenSourceLicenses(activity: Activity) { + error("Not supported, please ensure that hasOpenSourcesLicenses is true before calling this method") + } +} diff --git a/app/src/gplay/AndroidManifest.xml b/app/src/gplay/AndroidManifest.xml new file mode 100644 index 0000000000..234003d953 --- /dev/null +++ b/app/src/gplay/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/gplay/kotlin/io/element/android/x/licenses/OssOpenSourcesLicensesProvider.kt b/app/src/gplay/kotlin/io/element/android/x/licenses/OssOpenSourcesLicensesProvider.kt new file mode 100644 index 0000000000..93848c438d --- /dev/null +++ b/app/src/gplay/kotlin/io/element/android/x/licenses/OssOpenSourcesLicensesProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.x.licenses + +import android.app.Activity +import android.content.Intent +import com.google.android.gms.oss.licenses.OssLicensesMenuActivity +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.preferences.api.OpenSourceLicensesProvider +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.ui.strings.CommonStrings +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class OssOpenSourcesLicensesProvider @Inject constructor() : OpenSourceLicensesProvider { + override val hasOpenSourceLicenses: Boolean = true + + override fun navigateToOpenSourceLicenses(activity: Activity) { + val title = activity.getString(CommonStrings.common_open_source_licenses) + OssLicensesMenuActivity.setActivityTitle(title) + activity.startActivity(Intent(activity, OssLicensesMenuActivity::class.java)) + } +} diff --git a/app/src/gplay/res/values-night-v27/themes.xml b/app/src/gplay/res/values-night-v27/themes.xml new file mode 100644 index 0000000000..b1b00d7205 --- /dev/null +++ b/app/src/gplay/res/values-night-v27/themes.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/app/src/gplay/res/values/themes.xml b/app/src/gplay/res/values/themes.xml new file mode 100644 index 0000000000..ab93c3743d --- /dev/null +++ b/app/src/gplay/res/values/themes.xml @@ -0,0 +1,47 @@ + + + + + + + #FFFFFFFF + + #FF1B1D22 + + #FF656D77 + + + + + + + #FF101317 + + #FFEBEEF2 + + #ff808994 + + #FF4187EB - - - -