diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e220ec76e4..dba9677ae3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -38,7 +38,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble debug APK
@@ -47,14 +47,15 @@ jobs:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
- run: ./gradlew :app:assembleDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
+ run: ./gradlew :app:assembleGplayDebug :app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Upload APK APKs
if: ${{ matrix.variant == 'debug' }}
uses: actions/upload-artifact@v4
with:
name: elementx-debug
path: |
- app/build/outputs/apk/debug/*.apk
+ app/build/outputs/apk/gplay/debug/*.apk
+ app/build/outputs/apk/fdroid/debug/*.apk
- uses: rnkdsh/action-upload-diawi@v1.5.4
id: diawi
# Do not fail the whole build if Diawi upload fails
@@ -64,7 +65,7 @@ jobs:
if: ${{ matrix.variant == 'debug' && github.event_name == 'pull_request' && env.token != '' }}
with:
token: ${{ env.token }}
- file: app/build/outputs/apk/debug/app-arm64-v8a-debug.apk
+ file: app/build/outputs/apk/gplay/debug/app-gplay-arm64-v8a-debug.apk
- name: Add or update PR comment with QR Code to download APK.
if: ${{ matrix.variant == 'debug' && github.event_name == 'pull_request' && steps.diawi.conclusion == 'success' }}
uses: NejcZdovc/comment-pr@v2
@@ -82,7 +83,7 @@ jobs:
run: ./gradlew compileReleaseSources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Compile nightly sources
if: ${{ matrix.variant == 'nightly' }}
- run: ./gradlew compileNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
+ run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Compile samples minimal
if: ${{ matrix.variant == 'samples' }}
run: ./gradlew :samples:minimal:assemble $CI_GRADLE_ARG_PROPERTIES
diff --git a/.github/workflows/fork-pr-notice.yml b/.github/workflows/fork-pr-notice.yml
index 42b1e54c3f..b11a81bfb1 100644
--- a/.github/workflows/fork-pr-notice.yml
+++ b/.github/workflows/fork-pr-notice.yml
@@ -11,7 +11,8 @@ jobs:
welcome:
runs-on: ubuntu-latest
name: Welcome comment
- if: github.event.pull_request.fork != null
+ # Only display it if base repo (upstream) is different from HEAD repo (possibly a fork)
+ if: github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name
steps:
- name: Add auto-generated commit warning
uses: actions/github-script@v7
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index 4746aa3885..f2ad3eeee4 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -12,4 +12,4 @@ jobs:
# No concurrency required, this is a prerequisite to other actions and should run every time.
steps:
- uses: actions/checkout@v4
- - uses: gradle/wrapper-validation-action@v1
+ - uses: gradle/wrapper-validation-action@v2
diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml
index 0e2dc74b92..c6d4c44d80 100644
--- a/.github/workflows/maestro.yml
+++ b/.github/workflows/maestro.yml
@@ -53,7 +53,7 @@ jobs:
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
# Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android):
# app-file should point to an x86 compatible APK file, so upload the x86_64 one (much smaller than the universal APK).
- app-file: app/build/outputs/apk/debug/app-x86_64-debug.apk
+ app-file: app/build/outputs/apk/gplay/debug/app-gplay-x86_64-debug.apk
env: |
USERNAME=maestroelement
PASSWORD=${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }}
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 91976f56a0..dc6aaf1249 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -33,7 +33,7 @@ jobs:
yes n | towncrier build --version nightly
- name: Build and upload Nightly application
run: |
- ./gradlew assembleNightly appDistributionUploadNightly $CI_GRADLE_ARG_PROPERTIES
+ ./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
@@ -45,7 +45,7 @@ jobs:
- name: Additionally upload Nightly APK to browserstack for testing
continue-on-error: true # don't block anything by this upload failing (for now)
run: |
- curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_PASSWORD" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@app/build/outputs/apk/nightly/app-universal-nightly.apk" -F "custom_id=element-x-android-nightly"
+ curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_PASSWORD" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@app/build/outputs/apk/gplay/nightly/app-gplay-universal-nightly.apk" -F "custom_id=element-x-android-nightly"
env:
BROWSERSTACK_USERNAME: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_USERNAME }}
BROWSERSTACK_PASSWORD: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_ACCESS_KEY }}
diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml
index 7861425e28..8b429a4d13 100644
--- a/.github/workflows/nightlyReports.yml
+++ b/.github/workflows/nightlyReports.yml
@@ -62,7 +62,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Dependency analysis
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 3a36faf6d3..73bee00499 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -40,7 +40,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Run code quality check suite
diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml
index 11cd8e3352..a9293f006e 100644
--- a/.github/workflows/recordScreenshots.yml
+++ b/.github/workflows/recordScreenshots.yml
@@ -39,7 +39,7 @@ jobs:
java-version: '17'
# Add gradle cache, this should speed up the process
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Record screenshots
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 59b5f60cec..274214a98b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,16 +25,16 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
- name: Create app bundle
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
- run: ./gradlew bundleRelease $CI_GRADLE_ARG_PROPERTIES
+ run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload bundle as artifact
uses: actions/upload-artifact@v4
with:
- name: elementx-app-bundle-unsigned
+ name: elementx-app-gplay-bundle-unsigned
path: |
- app/build/outputs/bundle/release/app-release.aab
+ app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
index 705d937bce..ec1fa5eb8f 100644
--- a/.github/workflows/sonar.yml
+++ b/.github/workflows/sonar.yml
@@ -32,7 +32,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: 🔊 Publish results to Sonar
diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml
index 2f728fdf66..377bd3aa7a 100644
--- a/.github/workflows/sync-localazy.yml
+++ b/.github/workflows/sync-localazy.yml
@@ -24,7 +24,7 @@ jobs:
- name: Run Localazy script
run: ./tools/localazy/downloadStrings.sh --all
- name: Create Pull Request for Strings
- uses: peter-evans/create-pull-request@v5
+ uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
commit-message: Sync Strings from Localazy
diff --git a/.github/workflows/sync-sas-strings.yml b/.github/workflows/sync-sas-strings.yml
index 52aded770e..ba0a1fbdc7 100644
--- a/.github/workflows/sync-sas-strings.yml
+++ b/.github/workflows/sync-sas-strings.yml
@@ -23,7 +23,7 @@ jobs:
- name: Run SAS String script
run: ./tools/sas/import_sas_strings.py
- name: Create Pull Request for SAS Strings
- uses: peter-evans/create-pull-request@v5
+ uses: peter-evans/create-pull-request@v6
with:
commit-message: Sync SAS Strings
title: Sync SAS Strings
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index bf0379f6b6..b7b69bb26f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -44,7 +44,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
- uses: gradle/gradle-build-action@v2.11.1
+ uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
@@ -81,6 +81,8 @@ jobs:
# https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov
if: always()
- uses: codecov/codecov-action@v3
- # with:
- # files: build/reports/kover/xml/report.xml
+ uses: codecov/codecov-action@v4
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+# with:
+# files: build/reports/kover/xml/report.xml
diff --git a/.idea/dictionaries/shared.xml b/.idea/dictionaries/shared.xml
index c1f91b8411..c792687763 100644
--- a/.idea/dictionaries/shared.xml
+++ b/.idea/dictionaries/shared.xml
@@ -16,6 +16,7 @@
snackbar
swipeable
textfields
+ tombstoned
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index ae3f30ae18..8d81632f83 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.maestro/tests/roomList/createAndDeleteDM.yaml b/.maestro/tests/roomList/createAndDeleteDM.yaml
index f79a7418e4..6c2ebed11e 100644
--- a/.maestro/tests/roomList/createAndDeleteDM.yaml
+++ b/.maestro/tests/roomList/createAndDeleteDM.yaml
@@ -9,5 +9,5 @@ appId: ${APP_ID}
index: 1
- takeScreenshot: build/maestro/330-createAndDeleteDM
- tapOn: "maestroelement2"
-- tapOn: "Leave room"
+- tapOn: "Leave conversation"
- tapOn: "Leave"
diff --git a/CHANGES.md b/CHANGES.md
index 494b241823..1688c6c86d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,73 @@
+Changes in Element X v0.4.2 (2024-01-31)
+========================================
+
+Matrix SDK 🦀 v0.1.95
+
+Features ✨
+----------
+ - Add 'send private read receipts' option in advanced settings ([#2204](https://github.com/element-hq/element-x-android/issues/2204))
+ - Send typing notification ([#2240](https://github.com/element-hq/element-x-android/issues/2240)). Disabling the sending of typing notification and rendering typing notification will come soon.
+
+Bugfixes 🐛
+----------
+ - Make the room settings screen update automatically when new room info (name, avatar, topic) is available. ([#921](https://github.com/element-hq/element-x-android/issues/921))
+ - Update timeline items' read receipts when the room members info is loaded. ([#2176](https://github.com/element-hq/element-x-android/issues/2176))
+ - Edited text message bubbles should resize when edited ([#2260](https://github.com/element-hq/element-x-android/issues/2260))
+ - Ensure login and password exclude `\n` ([#2263](https://github.com/element-hq/element-x-android/issues/2263))
+ - Room list Ensure the indicators stay grey if the global setting is set to mention only and a regular message is received. ([#2282](https://github.com/element-hq/element-x-android/issues/2282))
+
+Other changes
+-------------
+ - Add a special logging configuration for nightlies so we can get more detailed info for existing issues. ([#+add-special-tracing-configuration-for-nightlies](https://github.com/element-hq/element-x-android/issues/+add-special-tracing-configuration-for-nightlies))
+ - Try mitigating unexpected logouts by making getting/storing session data use a Mutex for synchronization.
+ Also added some more logs so we can understand exactly where it's failing. ([#+try-mitigating-unexpected-logouts](https://github.com/element-hq/element-x-android/issues/+try-mitigating-unexpected-logouts))
+ - Upgrade Material3 Compose to `1.2.0-beta02`.
+ There is also a constraint on a transitive Compose Foundation dependency version (1.6.0-beta02) that fixes the timeline scrolling issue. ([#0-beta02](https://github.com/element-hq/element-x-android/issues/0-beta02))
+ - Disambiguate display name in the timeline. ([#2215](https://github.com/element-hq/element-x-android/issues/2215))
+- Disambiguate display name in notifications ([#2224](https://github.com/element-hq/element-x-android/issues/2224))
+ - Remove room creation, self-join of room creator and 'this is the beginning of X' timeline items for DMs. ([#2217](https://github.com/element-hq/element-x-android/issues/2217))
+ - Encrypt databases used by the Rust SDK on Nightly and Debug builds. ([#2219](https://github.com/element-hq/element-x-android/issues/2219))
+ - Fallback to UnifiedPush (if available) if the PlayServices are not installed on the device. ([#2248](https://github.com/element-hq/element-x-android/issues/2248))
+ - Add "Report a problem" button to the onboarding screen ([#2275](https://github.com/element-hq/element-x-android/issues/2275))
+ - Add in app logs viewer to the "Report a problem" screen. ([#2276](https://github.com/element-hq/element-x-android/issues/2276))
+
+
+Changes in Element X v0.4.1 (2024-01-17)
+========================================
+
+Features ✨
+----------
+ - Render m.sticker events ([#1949](https://github.com/element-hq/element-x-android/issues/1949))
+ - Add support for sending images from the keyboard ([#1977](https://github.com/element-hq/element-x-android/issues/1977))
+ - Added support for MSC4027 (render custom images in reactions) ([#2159](https://github.com/element-hq/element-x-android/issues/2159))
+
+Bugfixes 🐛
+----------
+ - Fix crash sending image with latest Posthog because of an usage of an internal Android method. ([#+crash-sending-image-with-latest-posthog](https://github.com/element-hq/element-x-android/issues/+crash-sending-image-with-latest-posthog))
+ - Make sure the media viewer tries the main url first (if not empty) then the thumbnail url and then not open if both are missing instead of failing with an error dialog ([#1949](https://github.com/element-hq/element-x-android/issues/1949))
+ - Fix room transition animation happens twice. ([#2084](https://github.com/element-hq/element-x-android/issues/2084))
+ - Disable ability to send reaction if the user does not have the permission to. ([#2093](https://github.com/element-hq/element-x-android/issues/2093))
+ - Trim whitespace at the end of messages to ensure we render the right content. ([#2099](https://github.com/element-hq/element-x-android/issues/2099))
+ - Fix crashes in room list when the last message for a room was an extremely long one (several thousands of characters) with no line breaks. ([#2105](https://github.com/element-hq/element-x-android/issues/2105))
+ - Disable rasterisation of Vector XMLs, which was causing crashes on API 23. ([#2124](https://github.com/element-hq/element-x-android/issues/2124))
+ - Use `SubomposeLayout` for `ContentAvoidingLayout` to prevent wrong measurements in the layout process, leading to cut-off text messages in the timeline. ([#2155](https://github.com/element-hq/element-x-android/issues/2155))
+ - Improve rendering of voice messages in the timeline in large displays ([#2156](https://github.com/element-hq/element-x-android/issues/2156))
+ - Fix no indication that user list is loading when inviting to room. ([#2172](https://github.com/element-hq/element-x-android/issues/2172))
+ - Hide keyboard when tapping on a message in the timeline. ([#2182](https://github.com/element-hq/element-x-android/issues/2182))
+ - Mention selector gets stuck when quickly deleting the prompt. ([#2192](https://github.com/element-hq/element-x-android/issues/2192))
+ - Hide verbose state events from the timeline ([#2216](https://github.com/element-hq/element-x-android/issues/2216))
+
+Other changes
+-------------
+ - Only apply `com.autonomousapps.dependency-analysis` plugin in those modules that need it. ([#+only-apply-dependency-analysis-plugin-where-needed](https://github.com/element-hq/element-x-android/issues/+only-apply-dependency-analysis-plugin-where-needed))
+ - Migrate to Kover 0.7.X ([#1782](https://github.com/element-hq/element-x-android/issues/1782))
+ - Remove extra logout screen. ([#2072](https://github.com/element-hq/element-x-android/issues/2072))
+ - Handle `MembershipChange.NONE` rendering in the timeline. ([#2102](https://github.com/element-hq/element-x-android/issues/2102))
+ - Remove extra previews for timestamp view with 'document' case ([#2127](https://github.com/element-hq/element-x-android/issues/2127))
+ - Bump AGP version to 8.2.0 ([#2142](https://github.com/element-hq/element-x-android/issues/2142))
+ - Replace 'leave room' text with 'leave conversation' for DMs. ([#2218](https://github.com/element-hq/element-x-android/issues/2218))
+
+
Changes in Element X v0.4.0 (2023-12-22)
========================================
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 543a414091..aad1c4eff6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -20,6 +20,10 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
import extension.allFeaturesImpl
import extension.allLibrariesImpl
import extension.allServicesImpl
+import extension.gitBranchName
+import extension.gitRevision
+import extension.koverDependencies
+import extension.setupKover
import org.jetbrains.kotlin.cli.common.toBooleanLenient
plugins {
@@ -36,6 +40,8 @@ plugins {
// id("com.google.gms.google-services")
}
+setupKover()
+
android {
namespace = "io.element.android.x"
@@ -50,6 +56,9 @@ android {
abiFilters += listOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
}
+ buildConfigField("String", "GIT_REVISION", "\"${gitRevision()}\"")
+ buildConfigField("String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\"")
+
// Ref: https://developer.android.com/studio/build/configure-apk-splits.html#configure-abi-split
splits {
// Configures multiple APKs based on ABI.
@@ -69,7 +78,7 @@ android {
}
signingConfigs {
- named("debug") {
+ getByName("debug") {
keyAlias = "androiddebugkey"
keyPassword = "android"
storeFile = file("./signature/debug.keystore")
@@ -87,13 +96,13 @@ android {
}
buildTypes {
- named("debug") {
+ getByName("debug") {
resValue("string", "app_name", "Element X dbg")
applicationIdSuffix = ".debug"
signingConfig = signingConfigs.getByName("debug")
}
- named("release") {
+ getByName("release") {
resValue("string", "app_name", "Element X")
signingConfig = signingConfigs.getByName("debug")
@@ -124,7 +133,7 @@ android {
// We upload the universal APK to fix this error:
// "App Distribution found more than 1 output file for this variant.
// Please contact firebase-support@google.com for help using APK splits with App Distribution."
- artifactPath = "$rootDir/app/build/outputs/apk/nightly/app-universal-nightly.apk"
+ artifactPath = "$rootDir/app/build/outputs/apk/gplay/nightly/app-gplay-universal-nightly.apk"
// artifactType = "AAB"
// artifactPath = "$rootDir/app/build/outputs/bundle/nightly/app-nightly.aab"
// This file will be generated by the GitHub action
@@ -143,6 +152,20 @@ android {
buildFeatures {
buildConfig = true
}
+ flavorDimensions += "store"
+ productFlavors {
+ create("gplay") {
+ dimension = "store"
+ isDefault = true
+ buildConfigField("String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"")
+ buildConfigField("String", "FLAVOR_DESCRIPTION", "\"GooglePlay\"")
+ }
+ create("fdroid") {
+ dimension = "store"
+ buildConfigField("String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"")
+ buildConfigField("String", "FLAVOR_DESCRIPTION", "\"FDroid\"")
+ }
+ }
}
androidComponents {
@@ -222,6 +245,11 @@ dependencies {
implementation(projects.appconfig)
anvil(projects.anvilcodegen)
+ // Comment to not include firebase in the project
+ "gplayImplementation"(projects.libraries.pushproviders.firebase)
+ // Comment to not include unified push in the project
+ implementation(projects.libraries.pushproviders.unifiedpush)
+
implementation(libs.appyx.core)
implementation(libs.androidx.splash)
implementation(libs.androidx.core)
@@ -251,4 +279,5 @@ dependencies {
testImplementation(projects.libraries.matrix.test)
ksp(libs.showkase.processor)
+ koverDependencies()
}
diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
index a6ce26c237..0934771501 100644
--- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
+++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
@@ -18,7 +18,7 @@ package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.features.lockscreen.api.LockScreenService
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.di.AppScope
@@ -34,5 +34,5 @@ interface AppBindings {
fun lockScreenService(): LockScreenService
- fun preferencesStore(): PreferencesStore
+ fun preferencesStore(): AppPreferencesStore
}
diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt
index 995dcf61e5..47a2c373ad 100644
--- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt
+++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt
@@ -85,16 +85,10 @@ object AppModule {
lowPrivacyLoggingEnabled = false,
versionName = BuildConfig.VERSION_NAME,
versionCode = BuildConfig.VERSION_CODE,
- // BuildConfig.GIT_REVISION,
- gitRevision = "TODO",
- // BuildConfig.GIT_REVISION_DATE,
- gitRevisionDate = "TODO",
- // BuildConfig.GIT_BRANCH_NAME,
- gitBranchName = "TODO",
- // BuildConfig.FLAVOR_DESCRIPTION,
- flavorDescription = "TODO",
- // BuildConfig.SHORT_FLAVOR_DESCRIPTION,
- flavorShortDescription = "TODO",
+ gitRevision = BuildConfig.GIT_REVISION,
+ gitBranchName = BuildConfig.GIT_BRANCH_NAME,
+ flavorDescription = BuildConfig.FLAVOR_DESCRIPTION,
+ flavorShortDescription = BuildConfig.SHORT_FLAVOR_DESCRIPTION,
)
@Provides
diff --git a/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt b/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt
index 4ef2840e8e..a4ecc217a4 100644
--- a/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt
+++ b/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt
@@ -33,6 +33,7 @@ interface SessionComponent : NodeFactoriesBindings {
interface Builder {
@BindsInstance
fun client(matrixClient: MatrixClient): Builder
+
fun build(): SessionComponent
}
diff --git a/app/src/main/kotlin/io/element/android/x/info/Logs.kt b/app/src/main/kotlin/io/element/android/x/info/Logs.kt
index 9e96f48e2d..3cabc937a1 100644
--- a/app/src/main/kotlin/io/element/android/x/info/Logs.kt
+++ b/app/src/main/kotlin/io/element/android/x/info/Logs.kt
@@ -29,6 +29,8 @@ fun logApplicationInfo() {
append(BuildConfig.VERSION_CODE)
append(") - ")
append(BuildConfig.BUILD_TYPE)
+ append(" / ")
+ append(BuildConfig.FLAVOR)
}
// TODO Get SDK version somehow
val sdkVersion = "SDK VERSION (TODO)"
@@ -37,6 +39,7 @@ fun logApplicationInfo() {
Timber.d("----------------------------------------------------------------")
Timber.d("----------------------------------------------------------------")
Timber.d(" Application version: $appVersion")
+ Timber.d(" Git SHA: ${BuildConfig.GIT_REVISION}")
Timber.d(" SDK version: $sdkVersion")
Timber.d(" Local time: $date")
Timber.d("----------------------------------------------------------------")
diff --git a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt
index 5a7bf0fb82..7afcc49e6d 100644
--- a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt
+++ b/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt
@@ -46,8 +46,13 @@ class TracingInitializer : Initializer {
writesToFilesConfiguration = WriteToFilesConfiguration.Disabled
)
} else {
+ val config = if (BuildConfig.BUILD_TYPE == "nightly") {
+ TracingFilterConfigurations.nightly
+ } else {
+ TracingFilterConfigurations.release
+ }
TracingConfiguration(
- filterConfiguration = TracingFilterConfigurations.release,
+ filterConfiguration = config,
writesToLogcat = false,
writesToFilesConfiguration = WriteToFilesConfiguration.Enabled(
directory = bugReporter.logDirectory().absolutePath,
diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts
index 9407b16592..4e436ec718 100644
--- a/appnav/build.gradle.kts
+++ b/appnav/build.gradle.kts
@@ -52,6 +52,7 @@ dependencies {
implementation(libs.coil)
implementation(projects.features.ftue.api)
+ implementation(projects.features.viewfolder.api)
implementation(projects.services.apperror.impl)
implementation(projects.services.appnavstate.api)
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt
index 9401cdeac1..fae6d586aa 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt
@@ -24,6 +24,7 @@ import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
+import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push
import dagger.assisted.Assisted
@@ -54,6 +55,10 @@ class NotLoggedInFlowNode @AssistedInject constructor(
buildContext = buildContext,
plugins = plugins,
) {
+ interface Callback : Plugin {
+ fun onOpenBugReport()
+ }
+
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
@@ -91,6 +96,10 @@ class NotLoggedInFlowNode @AssistedInject constructor(
override fun onOpenDeveloperSettings() {
backstack.push(NavTarget.ConfigureTracing)
}
+
+ override fun onReportProblem() {
+ plugins().forEach { it.onOpenBugReport() }
+ }
}
onBoardingEntryPoint
.nodeBuilder(this, buildContext)
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
index 42c9869523..e4ecdd8864 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
@@ -45,6 +45,7 @@ import io.element.android.features.login.api.oidc.OidcAction
import io.element.android.features.login.api.oidc.OidcActionFlow
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.signedout.api.SignedOutEntryPoint
+import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
@@ -70,6 +71,7 @@ class RootFlowNode @AssistedInject constructor(
private val matrixClientsHolder: MatrixClientsHolder,
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
+ private val viewFolderEntryPoint: ViewFolderEntryPoint,
private val signedOutEntryPoint: SignedOutEntryPoint,
private val intentResolver: IntentResolver,
private val oidcActionFlow: OidcActionFlow,
@@ -141,7 +143,7 @@ class RootFlowNode @AssistedInject constructor(
onSuccess(sessionId)
}
.onFailure {
- Timber.v("Failed to restore session $sessionId")
+ Timber.e(it, "Failed to restore session $sessionId")
onFailure()
}
}
@@ -194,6 +196,11 @@ class RootFlowNode @AssistedInject constructor(
@Parcelize
data object BugReport : NavTarget
+
+ @Parcelize
+ data class ViewLogs(
+ val rootPath: String,
+ ) : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@@ -210,7 +217,14 @@ class RootFlowNode @AssistedInject constructor(
}
createNode(buildContext, plugins = listOf(inputs, callback))
}
- NavTarget.NotLoggedInFlow -> createNode(buildContext)
+ NavTarget.NotLoggedInFlow -> {
+ val callback = object : NotLoggedInFlowNode.Callback {
+ override fun onOpenBugReport() {
+ backstack.push(NavTarget.BugReport)
+ }
+ }
+ createNode(buildContext, plugins = listOf(callback))
+ }
is NavTarget.SignedOutFlow -> {
signedOutEntryPoint.nodeBuilder(this, buildContext)
.params(
@@ -226,12 +240,31 @@ class RootFlowNode @AssistedInject constructor(
override fun onBugReportSent() {
backstack.pop()
}
+
+ override fun onViewLogs(basePath: String) {
+ backstack.push(NavTarget.ViewLogs(rootPath = basePath))
+ }
}
bugReportEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
+ is NavTarget.ViewLogs -> {
+ val callback = object : ViewFolderEntryPoint.Callback {
+ override fun onDone() {
+ backstack.pop()
+ }
+ }
+ val params = ViewFolderEntryPoint.Params(
+ rootPath = navTarget.rootPath,
+ )
+ viewFolderEntryPoint
+ .nodeBuilder(this, buildContext)
+ .params(params)
+ .callback(callback)
+ .build()
+ }
}
}
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt
index d6ea4fc533..21e412ef63 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt
@@ -118,14 +118,7 @@ class RoomLoadedFlowNode @AssistedInject constructor(
}
private fun fetchRoomMembers() = lifecycleScope.launch {
- val room = inputs.room
- room.updateMembers()
- .onFailure {
- Timber.e(it, "Fail to fetch members for room ${room.roomId}")
- }
- .onSuccess {
- Timber.v("Success fetching members for room ${room.roomId}")
- }
+ inputs.room.updateMembers()
}
private fun createRoomDetailsNode(buildContext: BuildContext, initialTarget: RoomDetailsEntryPoint.InitialTarget): Node {
diff --git a/build.gradle.kts b/build.gradle.kts
index 051a7a4112..698757f737 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -60,7 +60,7 @@ allprojects {
config.from(files("$rootDir/tools/detekt/detekt.yml"))
}
dependencies {
- detektPlugins("io.nlopez.compose.rules:detekt:0.3.9")
+ detektPlugins("io.nlopez.compose.rules:detekt:0.3.11")
}
// KtLint
diff --git a/changelog.d/+crash-sending-image-with-latest-posthog.bugfix b/changelog.d/+crash-sending-image-with-latest-posthog.bugfix
deleted file mode 100644
index 1b299991bb..0000000000
--- a/changelog.d/+crash-sending-image-with-latest-posthog.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix crash sending image with latest Posthog because of an usage of an internal Android method.
diff --git a/changelog.d/+only-apply-dependency-analysis-plugin-where-needed.misc b/changelog.d/+only-apply-dependency-analysis-plugin-where-needed.misc
deleted file mode 100644
index cfa053dc90..0000000000
--- a/changelog.d/+only-apply-dependency-analysis-plugin-where-needed.misc
+++ /dev/null
@@ -1 +0,0 @@
-Only apply `com.autonomousapps.dependency-analysis` plugin in those modules that need it.
diff --git a/changelog.d/+remove-compose-foundation-workaround.misc b/changelog.d/+remove-compose-foundation-workaround.misc
new file mode 100644
index 0000000000..91bb19be2e
--- /dev/null
+++ b/changelog.d/+remove-compose-foundation-workaround.misc
@@ -0,0 +1 @@
+Remove Compose Foundation version pinning workaround. This was done to avoid a bug introduced in the default foundation version used by the material3 library, but that has already been fixed.
diff --git a/changelog.d/1782.misc b/changelog.d/1782.misc
deleted file mode 100644
index 2e716795d9..0000000000
--- a/changelog.d/1782.misc
+++ /dev/null
@@ -1 +0,0 @@
-Migrate to Kover 0.7.X
diff --git a/changelog.d/1949.bugfix b/changelog.d/1949.bugfix
deleted file mode 100644
index a49aff7545..0000000000
--- a/changelog.d/1949.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Make sure the media viewer tries the main url first (if not empty) then the thumbnail url and then not open if both are missing instead of failing with an error dialog
diff --git a/changelog.d/1949.feature b/changelog.d/1949.feature
deleted file mode 100644
index e5ff7b03a7..0000000000
--- a/changelog.d/1949.feature
+++ /dev/null
@@ -1 +0,0 @@
-Render m.sticker events
diff --git a/changelog.d/1977.feature b/changelog.d/1977.feature
deleted file mode 100644
index 61ae78b082..0000000000
--- a/changelog.d/1977.feature
+++ /dev/null
@@ -1 +0,0 @@
-Add support for sending images from the keyboard
diff --git a/changelog.d/2072.misc b/changelog.d/2072.misc
deleted file mode 100644
index 7ae9d0be44..0000000000
--- a/changelog.d/2072.misc
+++ /dev/null
@@ -1 +0,0 @@
- Remove extra logout screen.
diff --git a/changelog.d/2084.bugfix b/changelog.d/2084.bugfix
deleted file mode 100644
index 8fb66aec73..0000000000
--- a/changelog.d/2084.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix room transition animation happens twice.
diff --git a/changelog.d/2093.bugfix b/changelog.d/2093.bugfix
deleted file mode 100644
index 59a6aa9b2a..0000000000
--- a/changelog.d/2093.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Disable ability to send reaction if the user does not have the permission to.
diff --git a/changelog.d/2099.bugfix b/changelog.d/2099.bugfix
deleted file mode 100644
index f80120ae0d..0000000000
--- a/changelog.d/2099.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Trim whitespace at the end of messages to ensure we render the right content.
diff --git a/changelog.d/2102.misc b/changelog.d/2102.misc
deleted file mode 100644
index 87c258ef70..0000000000
--- a/changelog.d/2102.misc
+++ /dev/null
@@ -1 +0,0 @@
-Handle `MembershipChange.NONE` rendering in the timeline.
diff --git a/changelog.d/2105.bugfix b/changelog.d/2105.bugfix
deleted file mode 100644
index 337b192aad..0000000000
--- a/changelog.d/2105.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix crashes in room list when the last message for a room was an extremely long one (several thousands of characters) with no line breaks.
diff --git a/changelog.d/2124.bugfix b/changelog.d/2124.bugfix
deleted file mode 100644
index 1b81e44093..0000000000
--- a/changelog.d/2124.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Disable rasterisation of Vector XMLs, which was causing crashes on API 23.
diff --git a/changelog.d/2127.misc b/changelog.d/2127.misc
deleted file mode 100644
index dfebf620ac..0000000000
--- a/changelog.d/2127.misc
+++ /dev/null
@@ -1 +0,0 @@
-Remove extra previews for timestamp view with 'document' case
diff --git a/changelog.d/2142.misc b/changelog.d/2142.misc
deleted file mode 100644
index 566fa41040..0000000000
--- a/changelog.d/2142.misc
+++ /dev/null
@@ -1 +0,0 @@
-Bump AGP version to 8.2.0
diff --git a/changelog.d/2155.bugfix b/changelog.d/2155.bugfix
deleted file mode 100644
index cc868cdaee..0000000000
--- a/changelog.d/2155.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Use `SubomposeLayout` for `ContentAvoidingLayout` to prevent wrong measurements in the layout process, leading to cut-off text messages in the timeline.
diff --git a/changelog.d/2156.bugfix b/changelog.d/2156.bugfix
deleted file mode 100644
index 45c7d882c6..0000000000
--- a/changelog.d/2156.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Improve rendering of voice messages in the timeline in large displays
diff --git a/changelog.d/2159.feature b/changelog.d/2159.feature
deleted file mode 100644
index 5adbf5595f..0000000000
--- a/changelog.d/2159.feature
+++ /dev/null
@@ -1 +0,0 @@
-Added support for MSC4027 (render custom images in reactions)
diff --git a/changelog.d/2172.bugfix b/changelog.d/2172.bugfix
deleted file mode 100644
index 14f34666b9..0000000000
--- a/changelog.d/2172.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix no indication that user list is loading when inviting to room.
diff --git a/changelog.d/2182.bugfix b/changelog.d/2182.bugfix
deleted file mode 100644
index 427eb3b402..0000000000
--- a/changelog.d/2182.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Hide keyboard when tapping on a message in the timeline.
diff --git a/changelog.d/2192.bugfix b/changelog.d/2192.bugfix
deleted file mode 100644
index 0138f3508c..0000000000
--- a/changelog.d/2192.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Mention selector gets stuck when quickly deleting the prompt.
diff --git a/changelog.d/2304.bugfix b/changelog.d/2304.bugfix
new file mode 100644
index 0000000000..5f0c485ddd
--- /dev/null
+++ b/changelog.d/2304.bugfix
@@ -0,0 +1 @@
+Fix crash after unregistering UnifiedPush distributor
diff --git a/changelog.d/2316.bugfix b/changelog.d/2316.bugfix
new file mode 100644
index 0000000000..e4c809c8e8
--- /dev/null
+++ b/changelog.d/2316.bugfix
@@ -0,0 +1 @@
+Add missing device id to settings screen.
diff --git a/changelog.d/2333.feature b/changelog.d/2333.feature
new file mode 100644
index 0000000000..5bde0d6734
--- /dev/null
+++ b/changelog.d/2333.feature
@@ -0,0 +1 @@
+Allow joining unencrypted video calls in non encrypted rooms.
diff --git a/docs/install_from_github_release.md b/docs/install_from_github_release.md
index 069e1d97ec..5be1c0c3b0 100644
--- a/docs/install_from_github_release.md
+++ b/docs/install_from_github_release.md
@@ -4,13 +4,43 @@ This document explains how to install Element X Android from a Github Release.
-* [Requirements](#requirements)
-* [Steps](#steps)
-* [I already have the application on my phone](#i-already-have-the-application-on-my-phone)
+* [Installing the universal APK](#installing-the-universal-apk)
+ * [Instructions](#instructions)
+ * [Steps](#steps)
+ * [I already have the application on my phone](#i-already-have-the-application-on-my-phone)
+* [Installing from the App Bundle](#installing-from-the-app-bundle)
+ * [Requirements](#requirements)
+ * [Steps](#steps)
+ * [I already have the application on my phone](#i-already-have-the-application-on-my-phone)
-## Requirements
+## Installing the universal APK
+
+### Instructions
+
+The easiest way to install the application from a GitHub release is to use the universal APK which is attached to the release. This APK is compatible with all Android devices, but it is not optimized for any of them. So it may not be as fast as it could be on your device, and it may not be as small as it could be.
+
+Alternatively, you can generate an APK that is optimized for your device. This is explained in the next section.
+
+### Steps
+
+- Open the GitHub release that you want to install from using the Web browser of your phone.
+- Download the APK
+- Open the APK file from the download notification, or from the file manager
+- Follow the steps to install the application
+
+### I already have the application on my phone
+
+If the application was already installed on your phone, there are several cases:
+
+- it was installed from the PlayStore, you can install the universal APK as long as the version is more recent. The existing data should not be lost.
+- it was installed from a previous GitHub release, this is like an application upgrade.
+- it was installed from a more recent GitHub release, or from the PlayStore with a later version, you will have to uninstall it first.
+
+## Installing from the App Bundle
+
+### Requirements
The Github release will contain an Android App Bundle (with `aab` extension) file, unlike in the Element Android project where releases directly provide the APKs. So there are some steps to perform to generate and sign App Bundle APKs. An APK suitable for the targeted device will then be generated.
@@ -31,7 +61,7 @@ You will also need to install [bundletool](https://developer.android.com/studio/
brew install bundletool
```
-## Steps
+### Steps
1. Open the GitHub release that you want to install from https://github.com/element-hq/element-x-android/releases
2. Download the asset `app-release-signed.aab`
@@ -55,7 +85,7 @@ bundletool install-apks --apks=./tmp/elementx.apks
That's it, the application should be installed on your device, you can start it from the launcher icon.
-## I already have the application on my phone
+### I already have the application on my phone
If the application was already installed on your phone, there are several cases:
diff --git a/fastlane/metadata/android/en-US/changelogs/40004010.txt b/fastlane/metadata/android/en-US/changelogs/40004010.txt
new file mode 100644
index 0000000000..cccc7477c2
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40004010.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Mainly bug fixes.
+Full changelog: https://github.com/element-hq/element-x-android/releases
diff --git a/fastlane/metadata/android/en-US/changelogs/40004020.txt b/fastlane/metadata/android/en-US/changelogs/40004020.txt
new file mode 100644
index 0000000000..b55bf0be0d
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40004020.txt
@@ -0,0 +1,2 @@
+Main changes in this version: be able to send a problem from the first screen, and add an internal log viewer. Be able to send private read receipt, send typing notification, improve performance.
+Full changelog: https://github.com/element-hq/element-x-android/releases
diff --git a/features/analytics/api/src/main/res/values-it/translations.xml b/features/analytics/api/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..02053ccc43
--- /dev/null
+++ b/features/analytics/api/src/main/res/values-it/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Condividi dati statistici"
+ "Condividi dati di utilizzo anonimi per aiutarci a identificare problemi."
+ "Puoi leggere tutti i nostri termini %1$s."
+ "qui"
+
diff --git a/features/analytics/impl/src/main/res/values-it/translations.xml b/features/analytics/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..6e030d564c
--- /dev/null
+++ b/features/analytics/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Non registreremo né profileremo alcun dato personale"
+ "Condividi dati di utilizzo anonimi per aiutarci a identificare problemi."
+ "Puoi leggere tutti i nostri termini %1$s."
+ "qui"
+ "Puoi disattivarlo in qualsiasi momento"
+ "Non condivideremo i tuoi dati con terze parti"
+ "Aiutaci a migliorare %1$s"
+
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt b/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt
index 2729d71a7e..8eea4c814f 100644
--- a/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt
+++ b/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt
@@ -45,7 +45,7 @@ import io.element.android.features.call.CallForegroundService
import io.element.android.features.call.CallType
import io.element.android.features.call.di.CallBindings
import io.element.android.features.call.utils.CallIntentDataParser
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.architecture.bindings
import javax.inject.Inject
@@ -67,7 +67,7 @@ class ElementCallActivity : NodeComponentActivity(), CallScreenNavigator {
@Inject lateinit var callIntentDataParser: CallIntentDataParser
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
- @Inject lateinit var preferencesStore: PreferencesStore
+ @Inject lateinit var appPreferencesStore: AppPreferencesStore
private lateinit var presenter: CallScreenPresenter
@@ -101,7 +101,7 @@ class ElementCallActivity : NodeComponentActivity(), CallScreenNavigator {
setContent {
val theme by remember {
- preferencesStore.getThemeFlow().mapToTheme()
+ appPreferencesStore.getThemeFlow().mapToTheme()
}
.collectAsState(initial = Theme.System)
val state = presenter.present()
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProvider.kt b/features/call/src/main/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProvider.kt
index f3cb9cbcd5..7fb6d3cb48 100644
--- a/features/call/src/main/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProvider.kt
+++ b/features/call/src/main/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProvider.kt
@@ -18,7 +18,7 @@ package io.element.android.features.call.utils
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.ElementCallConfig
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.RoomId
@@ -31,7 +31,7 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultCallWidgetProvider @Inject constructor(
private val matrixClientsProvider: MatrixClientProvider,
- private val preferencesStore: PreferencesStore,
+ private val appPreferencesStore: AppPreferencesStore,
private val callWidgetSettingsProvider: CallWidgetSettingsProvider,
) : CallWidgetProvider {
override suspend fun getWidget(
@@ -42,8 +42,8 @@ class DefaultCallWidgetProvider @Inject constructor(
theme: String?,
): Result> = runCatching {
val room = matrixClientsProvider.getOrRestore(sessionId).getOrThrow().getRoom(roomId) ?: error("Room not found")
- val baseUrl = preferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull() ?: ElementCallConfig.DEFAULT_BASE_URL
- val widgetSettings = callWidgetSettingsProvider.provide(baseUrl)
+ val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull() ?: ElementCallConfig.DEFAULT_BASE_URL
+ val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = room.isEncrypted)
val callUrl = room.generateWidgetWebViewUrl(widgetSettings, clientId, languageTag, theme).getOrThrow()
room.getWidgetDriver(widgetSettings).getOrThrow() to callUrl
}
diff --git a/features/call/src/main/res/values-hu/translations.xml b/features/call/src/main/res/values-hu/translations.xml
index fee5a163bb..c85521c392 100644
--- a/features/call/src/main/res/values-hu/translations.xml
+++ b/features/call/src/main/res/values-hu/translations.xml
@@ -1,6 +1,6 @@
"Folyamatban lévő hívás"
- "Koppintson a híváshoz való visszatéréshez"
+ "Koppints a híváshoz való visszatéréshez"
"☎️ Hívás folyamatban"
diff --git a/features/call/src/main/res/values-it/translations.xml b/features/call/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..38eb65b09c
--- /dev/null
+++ b/features/call/src/main/res/values-it/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Chiamata in corso"
+ "Tocca per tornare alla chiamata"
+ "☎️ Chiamata in corso"
+
diff --git a/features/call/src/main/res/values-ru/translations.xml b/features/call/src/main/res/values-ru/translations.xml
index c77095f3f2..4399785f75 100644
--- a/features/call/src/main/res/values-ru/translations.xml
+++ b/features/call/src/main/res/values-ru/translations.xml
@@ -2,5 +2,5 @@
"Текущий вызов"
"Коснитесь, чтобы вернуться к вызову"
- "Идёт вызов"
+ "☎️ Идёт вызов"
diff --git a/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
index 50d8c54998..31f6327d2f 100644
--- a/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
+++ b/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
@@ -17,8 +17,8 @@
package io.element.android.features.call.utils
import com.google.common.truth.Truth.assertThat
-import io.element.android.features.preferences.api.store.PreferencesStore
-import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.matrix.test.A_ROOM_ID
@@ -94,14 +94,14 @@ class DefaultCallWidgetProviderTest {
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
- val preferencesStore = InMemoryPreferencesStore().apply {
+ val preferencesStore = InMemoryAppPreferencesStore().apply {
setCustomElementCallBaseUrl("https://custom.element.io")
}
val settingsProvider = FakeCallWidgetSettingsProvider()
val provider = createProvider(
matrixClientProvider = FakeMatrixClientProvider { Result.success(client) },
callWidgetSettingsProvider = settingsProvider,
- preferencesStore = preferencesStore,
+ appPreferencesStore = preferencesStore,
)
provider.getWidget(A_SESSION_ID, A_ROOM_ID, "clientId", "languageTag", "theme")
@@ -110,11 +110,11 @@ class DefaultCallWidgetProviderTest {
private fun createProvider(
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(),
- preferencesStore: PreferencesStore = InMemoryPreferencesStore(),
+ appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
callWidgetSettingsProvider: CallWidgetSettingsProvider = FakeCallWidgetSettingsProvider()
) = DefaultCallWidgetProvider(
matrixClientProvider,
- preferencesStore,
+ appPreferencesStore,
callWidgetSettingsProvider,
)
}
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt
index b53c8a7170..37ae6bd2fc 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt
+++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt
@@ -23,10 +23,11 @@ import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
-import io.element.android.libraries.matrix.ui.components.CheckableMatrixUserRow
-import io.element.android.libraries.matrix.ui.components.CheckableUnresolvedUserRow
+import io.element.android.libraries.matrix.ui.components.CheckableUserRow
+import io.element.android.libraries.matrix.ui.components.CheckableUserRowData
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
+import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.usersearch.api.UserSearchResult
@Composable
@@ -36,23 +37,24 @@ fun SearchMultipleUsersResultItem(
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
- if (searchResult.isUnresolved) {
- CheckableUnresolvedUserRow(
- checked = isUserSelected,
- modifier = modifier,
+ val data = if (searchResult.isUnresolved) {
+ CheckableUserRowData.Unresolved(
avatarData = searchResult.matrixUser.getAvatarData(AvatarSize.UserListItem),
id = searchResult.matrixUser.userId.value,
- onCheckedChange = onCheckedChange,
)
} else {
- CheckableMatrixUserRow(
- checked = isUserSelected,
- modifier = modifier,
- matrixUser = searchResult.matrixUser,
- avatarSize = AvatarSize.UserListItem,
- onCheckedChange = onCheckedChange,
+ CheckableUserRowData.Resolved(
+ name = searchResult.matrixUser.getBestName(),
+ subtext = if (searchResult.matrixUser.displayName.isNullOrEmpty()) null else searchResult.matrixUser.userId.value,
+ avatarData = searchResult.matrixUser.getAvatarData(AvatarSize.UserListItem),
)
}
+ CheckableUserRow(
+ checked = isUserSelected,
+ modifier = modifier,
+ data = data,
+ onCheckedChange = onCheckedChange,
+ )
}
@Preview
diff --git a/features/createroom/impl/src/main/res/values-it/translations.xml b/features/createroom/impl/src/main/res/values-it/translations.xml
index ceddb71154..836cff3452 100644
--- a/features/createroom/impl/src/main/res/values-it/translations.xml
+++ b/features/createroom/impl/src/main/res/values-it/translations.xml
@@ -1,8 +1,15 @@
"Nuova stanza"
- "Invita persone"
- "Aggiungi persone"
+ "Invita persone su Element"
+ "Invita persone"
+ "Si è verificato un errore durante la creazione della stanza"
+ "I messaggi in questa stanza sono cifrati. La crittografia non può essere disattivata in seguito."
+ "Stanza privata (solo su invito)"
+ "I messaggi non sono cifrati e chiunque può leggerli. Puoi attivare la crittografia in un secondo momento."
+ "Stanza pubblica (chiunque)"
+ "Nome stanza"
+ "Argomento (facoltativo)"
"Si è verificato un errore durante il tentativo di avviare una chat"
"Crea una stanza"
diff --git a/features/createroom/impl/src/main/res/values-ru/translations.xml b/features/createroom/impl/src/main/res/values-ru/translations.xml
index 7837f9e7de..1557931e85 100644
--- a/features/createroom/impl/src/main/res/values-ru/translations.xml
+++ b/features/createroom/impl/src/main/res/values-ru/translations.xml
@@ -4,9 +4,9 @@
"Пригласите друзей в Element"
"Пригласить людей"
"Произошла ошибка при создании комнаты"
- "Сообщения в этой комнате зашифрованы. Отключить шифрование впоследствии невозможно."
+ "Сообщения в этой комнате зашифрованы. Отключить шифрование позже будет невозможно."
"Приватная комната (только по приглашению)"
- "Сообщения не зашифрованы, и каждый может их прочитать. Вы можете включить шифрование позже."
+ "Сообщения не зашифрованы, каждый может их прочитать. Вы можете включить шифрование позже."
"Публичная комната (любой)"
"Название комнаты"
"Тема (необязательно)"
diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt
index d2674e3f98..54a1b541be 100644
--- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt
+++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt
@@ -18,6 +18,8 @@ package io.element.android.features.ftue.impl.migration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import io.element.android.features.ftue.impl.R
@@ -32,8 +34,9 @@ fun MigrationScreenView(
modifier: Modifier = Modifier,
) {
if (migrationState.isMigrating.not()) {
+ val latestOnMigrationFinished by rememberUpdatedState(onMigrationFinished)
LaunchedEffect(Unit) {
- onMigrationFinished()
+ latestOnMigrationFinished()
}
}
SunsetPage(
diff --git a/features/ftue/impl/src/main/res/values-de/translations.xml b/features/ftue/impl/src/main/res/values-de/translations.xml
index d9a411b04f..d2a71e98e5 100644
--- a/features/ftue/impl/src/main/res/values-de/translations.xml
+++ b/features/ftue/impl/src/main/res/values-de/translations.xml
@@ -1,7 +1,7 @@
"Dies ist ein einmaliger Vorgang, danke fürs Warten."
- "Richte dein Konto ein."
+ "Dein Konto wird eingerichtet."
"Du kannst deine Einstellungen später ändern."
"Erlaube Benachrichtigungen und verpasse keine Nachricht"
"Anrufe, Umfragen, Suchfunktionen und mehr werden im Laufe des Jahres hinzugefügt."
diff --git a/features/ftue/impl/src/main/res/values-it/translations.xml b/features/ftue/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..69013ebdb8
--- /dev/null
+++ b/features/ftue/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,13 @@
+
+
+ "Si tratta di una procedura che si effettua una sola volta, grazie per l\'attesa."
+ "Configurazione del tuo account."
+ "Potrai modificare le tue impostazioni in seguito."
+ "Consenti le notifiche e non perdere mai un messaggio"
+ "Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno."
+ "La cronologia dei messaggi per le stanze crittografate non è ancora disponibile."
+ "Ci piacerebbe sentire il tuo parere, facci sapere cosa ne pensi tramite la pagina delle impostazioni."
+ "Andiamo!"
+ "Ecco cosa c\'è da sapere:"
+ "Benvenuti in %1$s!"
+
diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt
index f2dd87ff05..33ecb36913 100644
--- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt
+++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt
@@ -169,7 +169,7 @@ class InviteListPresenter @Inject constructor(
AvatarData(
id = roomId.value,
name = name,
- url = avatarURLString,
+ url = avatarUrl,
size = AvatarSize.RoomInviteItem,
)
}
diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt
index 28ecf4938d..ff8ba55c0b 100644
--- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt
+++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt
@@ -28,6 +28,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -57,8 +59,9 @@ fun InviteListView(
modifier: Modifier = Modifier,
) {
if (state.acceptedAction is AsyncData.Success) {
+ val latestOnInviteAccepted by rememberUpdatedState(onInviteAccepted)
LaunchedEffect(state.acceptedAction) {
- onInviteAccepted(state.acceptedAction.data)
+ latestOnInviteAccepted(state.acceptedAction.data)
}
}
diff --git a/features/invitelist/impl/src/main/res/values-it/translations.xml b/features/invitelist/impl/src/main/res/values-it/translations.xml
index 5f31ef01ba..e5d25b10cf 100644
--- a/features/invitelist/impl/src/main/res/values-it/translations.xml
+++ b/features/invitelist/impl/src/main/res/values-it/translations.xml
@@ -1,4 +1,9 @@
+ "Vuoi davvero rifiutare l\'invito ad entrare in %1$s?"
+ "Rifiuta l\'invito"
+ "Vuoi davvero rifiutare questa chat privata con %1$s?"
+ "Rifiuta la chat"
+ "Nessun invito"
"%1$s (%2$s) ti ha invitato"
diff --git a/features/invitelist/impl/src/main/res/values-ru/translations.xml b/features/invitelist/impl/src/main/res/values-ru/translations.xml
index 0f1c30cb09..b13ca06a9d 100644
--- a/features/invitelist/impl/src/main/res/values-ru/translations.xml
+++ b/features/invitelist/impl/src/main/res/values-ru/translations.xml
@@ -2,7 +2,7 @@
"Вы уверены, что хотите отклонить приглашение в %1$s?"
"Отклонить приглашение"
- "Вы уверены, что хотите отказаться от приватного общения с %1$s?"
+ "Вы уверены, что хотите отказаться от личного общения с %1$s?"
"Отклонить чат"
"Нет приглашений"
"%1$s (%2$s) пригласил вас"
diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt
index a3bafdb034..0fa899e033 100644
--- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt
+++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt
@@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.api.core.RoomId
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.roomlist.RoomSummary
-import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
@@ -39,6 +38,7 @@ 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
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.roomlist.FakeRoomListService
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
@@ -425,14 +425,12 @@ class InviteListPresenterTests {
postInviteRooms(
listOf(
RoomSummary.Filled(
- RoomSummaryDetails(
+ aRoomSummaryDetails(
roomId = A_ROOM_ID,
name = A_ROOM_NAME,
- avatarURLString = null,
+ avatarUrl = null,
isDirect = false,
lastMessage = null,
- lastMessageTimestamp = null,
- unreadNotificationCount = 0,
inviter = RoomMember(
userId = A_USER_ID,
displayName = A_USER_NAME,
@@ -454,14 +452,12 @@ class InviteListPresenterTests {
postInviteRooms(
listOf(
RoomSummary.Filled(
- RoomSummaryDetails(
+ aRoomSummaryDetails(
roomId = A_ROOM_ID,
name = A_ROOM_NAME,
- avatarURLString = null,
+ avatarUrl = null,
isDirect = true,
lastMessage = null,
- lastMessageTimestamp = null,
- unreadNotificationCount = 0,
inviter = RoomMember(
userId = A_USER_ID,
displayName = A_USER_NAME,
@@ -480,14 +476,12 @@ class InviteListPresenterTests {
}
private fun aRoomSummary(id: RoomId = A_ROOM_ID) = RoomSummary.Filled(
- RoomSummaryDetails(
+ aRoomSummaryDetails(
roomId = id,
name = A_ROOM_NAME,
- avatarURLString = null,
+ avatarUrl = null,
isDirect = false,
lastMessage = null,
- lastMessageTimestamp = null,
- unreadNotificationCount = 0,
)
)
diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt
index df6d44df4d..f069533d1a 100644
--- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt
+++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt
@@ -26,6 +26,7 @@ data class LeaveRoomState(
) {
sealed interface Confirmation {
data object Hidden : Confirmation
+ data class Dm(val roomId: RoomId) : Confirmation
data class Generic(val roomId: RoomId) : Confirmation
data class PrivateRoom(val roomId: RoomId) : Confirmation
data class LastUserInRoom(val roomId: RoomId) : Confirmation
diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt
index f82b58cba3..8c966b1a13 100644
--- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt
+++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt
@@ -28,17 +28,17 @@ class LeaveRoomStateProvider : PreviewParameterProvider {
error = LeaveRoomState.Error.Hidden,
),
aLeaveRoomState(
- confirmation = LeaveRoomState.Confirmation.Generic(A_ROOM_ID),
+ confirmation = LeaveRoomState.Confirmation.Generic(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden,
),
aLeaveRoomState(
- confirmation = LeaveRoomState.Confirmation.PrivateRoom(A_ROOM_ID),
+ confirmation = LeaveRoomState.Confirmation.PrivateRoom(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden,
),
aLeaveRoomState(
- confirmation = LeaveRoomState.Confirmation.LastUserInRoom(A_ROOM_ID),
+ confirmation = LeaveRoomState.Confirmation.LastUserInRoom(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden,
),
@@ -52,6 +52,11 @@ class LeaveRoomStateProvider : PreviewParameterProvider {
progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Shown,
),
+ aLeaveRoomState(
+ confirmation = LeaveRoomState.Confirmation.Dm(roomId = A_ROOM_ID),
+ progress = LeaveRoomState.Progress.Hidden,
+ error = LeaveRoomState.Error.Hidden,
+ ),
)
}
diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt
index 0006ae1251..5168ee9936 100644
--- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt
+++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt
@@ -47,21 +47,32 @@ private fun LeaveRoomConfirmationDialog(
) {
when (state.confirmation) {
is LeaveRoomState.Confirmation.Hidden -> {}
+
+ is LeaveRoomState.Confirmation.Dm -> LeaveRoomConfirmationDialog(
+ text = R.string.leave_conversation_alert_subtitle,
+ roomId = state.confirmation.roomId,
+ isDm = true,
+ eventSink = state.eventSink,
+ )
+
is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog(
text = R.string.leave_room_alert_private_subtitle,
roomId = state.confirmation.roomId,
+ isDm = false,
eventSink = state.eventSink,
)
is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog(
text = R.string.leave_room_alert_empty_subtitle,
roomId = state.confirmation.roomId,
+ isDm = false,
eventSink = state.eventSink,
)
is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog(
text = R.string.leave_room_alert_subtitle,
roomId = state.confirmation.roomId,
+ isDm = false,
eventSink = state.eventSink,
)
}
@@ -71,10 +82,11 @@ private fun LeaveRoomConfirmationDialog(
private fun LeaveRoomConfirmationDialog(
@StringRes text: Int,
roomId: RoomId,
+ isDm: Boolean,
eventSink: (LeaveRoomEvent) -> Unit,
) {
ConfirmationDialog(
- title = stringResource(CommonStrings.action_leave_room),
+ title = stringResource(if (isDm) CommonStrings.action_leave_conversation else CommonStrings.action_leave_room),
content = stringResource(text),
submitText = stringResource(CommonStrings.action_leave),
onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) },
diff --git a/features/leaveroom/api/src/main/res/values-cs/translations.xml b/features/leaveroom/api/src/main/res/values-cs/translations.xml
index 64195325c4..d3163cfbed 100644
--- a/features/leaveroom/api/src/main/res/values-cs/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-cs/translations.xml
@@ -1,5 +1,6 @@
+ "Opravdu chcete opustit tuto konverzaci? Tato konverzace není veřejná a bez pozvánky se k ní nebudete moci znovu připojit."
"Opravdu chcete opustit tuto místnost? Jste tu jediná osoba. Pokud odejdete, nikdo se v budoucnu nebude moci připojit, včetně vás."
"Opravdu chcete opustit tuto místnost? Tato místnost není veřejná a bez pozvánky se nebudete moci znovu připojit."
"Opravdu chcete opustit místnost?"
diff --git a/features/leaveroom/api/src/main/res/values-fr/translations.xml b/features/leaveroom/api/src/main/res/values-fr/translations.xml
index 43ca62a678..bed2bdd365 100644
--- a/features/leaveroom/api/src/main/res/values-fr/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-fr/translations.xml
@@ -1,5 +1,6 @@
+ "Êtes-vous sûr de vouloir quitter cette discussion? Vous ne pourrez pas la rejoindre à nouveau sans y être invité."
"Êtes-vous sûr de vouloir quitter ce salon ? Vous êtes la seule personne ici. Si vous partez, personne ne pourra rejoindre le salon à l’avenir, y compris vous."
"Êtes-vous sûr de vouloir quitter ce salon ? Ce salon n’est pas public et vous ne pourrez pas le rejoindre sans invitation."
"Êtes-vous sûr de vouloir quitter le salon ?"
diff --git a/features/leaveroom/api/src/main/res/values-hu/translations.xml b/features/leaveroom/api/src/main/res/values-hu/translations.xml
index 824c088cdb..0fb52a61aa 100644
--- a/features/leaveroom/api/src/main/res/values-hu/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-hu/translations.xml
@@ -1,6 +1,6 @@
"Biztos, hogy elhagyja ezt a szobát? Ön az egyedüli ember itt. Ha kilép, akkor senki sem fog tudni csatlakozni a jövőben, Önt is beleértve."
- "Biztos, hogy elhagyja ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fog tudni újra belépni."
- "Biztos, hogy elhagyja a szobát?"
+ "Biztos, hogy elhagyod ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fogsz tudni újra belépni."
+ "Biztos, hogy elhagyod a szobát?"
diff --git a/features/leaveroom/api/src/main/res/values-it/translations.xml b/features/leaveroom/api/src/main/res/values-it/translations.xml
index 60d481824a..1b403e8f93 100644
--- a/features/leaveroom/api/src/main/res/values-it/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-it/translations.xml
@@ -1,5 +1,6 @@
+ "Vuoi davvero abbandonare questa conversazione? La conversazione non è pubblica e non potrai rientrare senza un invito."
"Sei sicuro di voler lasciare questa stanza? Sei l\'unica persona presente. Se esci, nessuno potrà unirsi in futuro, te compreso."
"Sei sicuro di voler lasciare questa stanza? Questa stanza non è pubblica e non potrai rientrare senza un invito."
"Sei sicuro di voler lasciare la stanza?"
diff --git a/features/leaveroom/api/src/main/res/values-ru/translations.xml b/features/leaveroom/api/src/main/res/values-ru/translations.xml
index 5915518c80..c6faa299a2 100644
--- a/features/leaveroom/api/src/main/res/values-ru/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-ru/translations.xml
@@ -1,5 +1,6 @@
+ "Вы уверены, что хотите покинуть беседу?"
"Вы уверены, что хотите покинуть эту комнату? Вы здесь единственный человек. Если вы уйдете, никто не сможет присоединиться в будущем, включая вас."
"Вы уверены, что хотите покинуть эту комнату? Эта комната не является публичной, и Вы не сможете присоединиться к ней без приглашения."
"Вы уверены, что хотите покинуть комнату?"
diff --git a/features/leaveroom/api/src/main/res/values-sk/translations.xml b/features/leaveroom/api/src/main/res/values-sk/translations.xml
index c67cb131e7..74ff01b541 100644
--- a/features/leaveroom/api/src/main/res/values-sk/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-sk/translations.xml
@@ -1,5 +1,6 @@
+ "Ste si istí, že chcete opustiť konverzáciu?"
"Ste si istí, že chcete opustiť túto miestnosť? Ste tu jediná osoba. Ak odídete, nikto sa do nej nebude môcť v budúcnosti pripojiť, vrátane vás."
"Ste si istí, že chcete opustiť túto miestnosť? Táto miestnosť nie je verejná a bez pozvania sa do nej nebudete môcť vrátiť."
"Ste si istí, že chcete opustiť miestnosť?"
diff --git a/features/leaveroom/api/src/main/res/values/localazy.xml b/features/leaveroom/api/src/main/res/values/localazy.xml
index 262369c9be..f068e772bb 100644
--- a/features/leaveroom/api/src/main/res/values/localazy.xml
+++ b/features/leaveroom/api/src/main/res/values/localazy.xml
@@ -1,5 +1,6 @@
+ "Are you sure that you want to leave this conversation? This conversation is not public and you won\'t be able to rejoin without an invite."
"Are you sure that you want to leave this room? You\'re the only person here. If you leave, no one will be able to join in the future, including you."
"Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite."
"Are you sure that you want to leave the room?"
diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt
index 2ad35b38c9..56c2f9b8fd 100644
--- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt
+++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.api.LeaveRoomState
+import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Dm
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom
@@ -85,6 +86,7 @@ private suspend fun showLeaveRoomAlert(
) {
matrixClient.getRoom(roomId)?.use { room ->
confirmation.value = when {
+ room.isDm -> Dm(roomId)
!room.isPublic -> PrivateRoom(roomId)
room.joinedMemberCount == 1L -> LastUserInRoom(roomId)
else -> Generic(roomId)
diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt
index 677df53dc8..8bc8157c05 100644
--- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt
+++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt
@@ -114,6 +114,26 @@ class LeaveRoomPresenterImplTest {
}
}
+ @Test
+ fun `present - show DM confirmation`() = runTest {
+ val presenter = createLeaveRoomPresenter(
+ client = FakeMatrixClient().apply {
+ givenGetRoomResult(
+ roomId = A_ROOM_ID,
+ result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true, isOneToOne = true),
+ )
+ }
+ )
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ initialState.eventSink(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID))
+ val confirmationState = awaitItem()
+ assertThat(confirmationState.confirmation).isEqualTo(LeaveRoomState.Confirmation.Dm(A_ROOM_ID))
+ }
+ }
+
@Test
fun `present - leaving a room leaves the room`() = runTest {
val roomMembershipObserver = RoomMembershipObserver()
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt
index e1dd7f5be3..6ecf13f591 100644
--- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt
@@ -18,6 +18,8 @@ package io.element.android.features.lockscreen.impl.unlock
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager
import io.element.android.features.lockscreen.impl.biometric.DefaultBiometricUnlockCallback
import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback
@@ -30,15 +32,16 @@ class PinUnlockHelper @Inject constructor(
) {
@Composable
fun OnUnlockEffect(onUnlock: () -> Unit) {
+ val latestOnUnlock by rememberUpdatedState(onUnlock)
DisposableEffect(Unit) {
val biometricUnlockCallback = object : DefaultBiometricUnlockCallback() {
override fun onBiometricUnlockSuccess() {
- onUnlock()
+ latestOnUnlock()
}
}
val pinCodeVerifiedCallback = object : DefaultPinCodeManagerCallback() {
override fun onPinCodeVerified() {
- onUnlock()
+ latestOnUnlock()
}
}
biometricUnlockManager.addCallback(biometricUnlockCallback)
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 579346ed6e..bce8dc02fc 100644
--- a/features/lockscreen/impl/src/main/res/values-it/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-it/translations.xml
@@ -1,4 +1,37 @@
+
+ - "Hai %1$d tentativo di sblocco"
+ - "Hai %1$d tentativi di sblocco"
+
+
+ - "PIN sbagliato. Hai %1$d altro tentativo"
+ - "PIN sbagliato. Hai altri %1$d tentativi"
+
+ "autenticazione biometrica"
+ "sblocco biometrico"
+ "Sblocca con la biometria"
+ "PIN dimenticato?"
+ "Modifica il codice PIN"
+ "Consenti lo sblocco biometrico"
+ "Rimuovi PIN"
+ "Vuoi davvero rimuovere il PIN?"
+ "Rimuovere il PIN?"
+ "Consenti %1$s"
+ "Preferisco usare il PIN"
+ "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 chat.
+
+Scegli qualcosa che puoi ricordare. Se dimentichi questo PIN, verrai disconnesso dall\'app."
+ "Inserisci lo stesso PIN due volte"
+ "I PIN non corrispondono"
+ "Dovrai effettuare nuovamente l\'accesso e creare un nuovo PIN per procedere"
+ "Stai per essere disconnesso"
+ "Usa la biometria"
+ "Usa il PIN"
"Uscita in corso…"
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 7b2c61176e..3ead0e4604 100644
--- a/features/lockscreen/impl/src/main/res/values-ru/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-ru/translations.xml
@@ -11,11 +11,11 @@
- "Неверный PIN-код. У вас остался %1$d шанса"
"биометрическая идентификация"
- "биометрическая разблокировать"
+ "биометрическая разблокировка"
"Разблокировать с помощью биометрии"
"Забыли PIN-код?"
"Измените PIN-код"
- "Разрешить биометрическую разблокировать"
+ "Разрешить биометрическую разблокировку"
"Удалить PIN-код"
"Вы действительно хотите удалить PIN-код?"
"Удалить PIN-код?"
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt
index 132003eafc..9f71ccc7e5 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt
@@ -18,6 +18,8 @@ package io.element.android.features.login.impl.changeserver
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
@@ -63,8 +65,11 @@ fun ChangeServerView(
}
}
is AsyncData.Loading -> ProgressDialog()
- is AsyncData.Success -> LaunchedEffect(state.changeServerAction) {
- onDone()
+ is AsyncData.Success -> {
+ val latestOnDone by rememberUpdatedState(onDone)
+ LaunchedEffect(state.changeServerAction) {
+ latestOnDone()
+ }
}
AsyncData.Uninitialized -> Unit
}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt
index 2a5fbe8657..6dc1efbbf1 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt
@@ -203,15 +203,17 @@ private fun LoginForm(
.onTabOrEnterKeyFocusNext(focusManager)
.testTag(TestTags.loginEmailUsername)
.autofill(autofillTypes = listOf(AutofillType.Username), onFill = {
- loginFieldState = it
- eventSink(LoginPasswordEvents.SetLogin(it))
+ val sanitized = it.sanitize()
+ loginFieldState = sanitized
+ eventSink(LoginPasswordEvents.SetLogin(sanitized))
}),
placeholder = {
Text(text = stringResource(CommonStrings.common_username))
},
onValueChange = {
- loginFieldState = it
- eventSink(LoginPasswordEvents.SetLogin(it))
+ val sanitized = it.sanitize()
+ loginFieldState = sanitized
+ eventSink(LoginPasswordEvents.SetLogin(sanitized))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
@@ -233,7 +235,6 @@ private fun LoginForm(
null
},
)
-
var passwordVisible by remember { mutableStateOf(false) }
if (state.loginAction is AsyncData.Loading) {
// Ensure password is hidden when user submits the form
@@ -248,12 +249,14 @@ private fun LoginForm(
.onTabOrEnterKeyFocusNext(focusManager)
.testTag(TestTags.loginPassword)
.autofill(autofillTypes = listOf(AutofillType.Password), onFill = {
- passwordFieldState = it
- eventSink(LoginPasswordEvents.SetPassword(it))
+ val sanitized = it.sanitize()
+ passwordFieldState = sanitized
+ eventSink(LoginPasswordEvents.SetPassword(sanitized))
}),
onValueChange = {
- passwordFieldState = it
- eventSink(LoginPasswordEvents.SetPassword(it))
+ val sanitized = it.sanitize()
+ passwordFieldState = sanitized
+ eventSink(LoginPasswordEvents.SetPassword(sanitized))
},
placeholder = {
Text(text = stringResource(CommonStrings.common_password))
@@ -281,6 +284,13 @@ private fun LoginForm(
}
}
+/**
+ * Ensure that the string does not contain any new line characters, which can happen when pasting values.
+ */
+private fun String.sanitize(): String {
+ return replace("\n", "")
+}
+
@Composable
private fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) {
ErrorDialog(
diff --git a/features/login/impl/src/main/res/values-hu/translations.xml b/features/login/impl/src/main/res/values-hu/translations.xml
index defe5e41a4..e41ff77592 100644
--- a/features/login/impl/src/main/res/values-hu/translations.xml
+++ b/features/login/impl/src/main/res/values-hu/translations.xml
@@ -7,7 +7,7 @@
"Fiókszolgáltató keresése"
"Itt lesznek a beszélgetései – ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez."
"Hamarosan bejelentkezik ide: %s"
- "Itt lesznek a beszélgetései – ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez."
+ "Itt lesznek a beszélgetéseid – ahogyan egy e-mail-szolgáltatást is használnál a leveleid kezeléséhez."
"Hamarosan létrehoz egy fiókot itt: %s"
"A Matrix.org egy nagy, ingyenes kiszolgáló a nyilvános Matrix-hálózaton, a biztonságos, decentralizált kommunikáció érdekében, amelyet a Matrix.org Alapítvány üzemeltet."
"Egyéb"
diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml
index 227b85bed7..c3e43e5933 100644
--- a/features/login/impl/src/main/res/values-it/translations.xml
+++ b/features/login/impl/src/main/res/values-it/translations.xml
@@ -1,14 +1,42 @@
+ "Cambia fornitore dell\'account"
+ "Indirizzo dell\'homeserver"
+ "Inserisci un termine di ricerca o un indirizzo di dominio."
+ "Cerca un\' azienda, una comunità o un server privato."
+ "Trova un fornitore di account"
+ "Qui è dove vivranno le tue conversazioni - proprio come useresti un fornitore di posta elettronica per conservare le tue email."
+ "Stai per accedere a %s"
+ "Qui è dove vivranno le tue conversazioni - proprio come useresti un fornitore di posta elettronica per conservare le tue email."
+ "Stai per creare un account su %s"
+ "Matrix.org è un grande server gratuito nella rete pubblica Matrix per una comunicazione sicura e decentralizzata, gestito dalla Fondazione Matrix.org."
+ "Altro"
+ "Utilizza un provider di account diverso, ad esempio il tuo server privato o un account di lavoro."
+ "Cambia fornitore dell\'account"
"Non siamo riusciti a raggiungere questo homserver. Verifica di aver inserito correttamente l\'URL del server domestico. Se l\'URL è corretto, contatta l\'amministratore del tuo server domestico per ulteriore assistenza."
"Questo server attualmente non supporta la sincronizzazione scorrevole."
"URL dell\'homeserver"
"Puoi connetterti solo a un server esistente che supporta la sincronizzazione scorrevole. L\'amministratore del tuo server domestico dovrà configurarlo. %1$s"
"Qual è l\'indirizzo del tuo server?"
+ "Seleziona il tuo server"
"Questo profilo è stato disattivato."
"Nome utente e/o password errati"
"Questo non è un identificatore utente valido. Formato previsto: \'@user:homeserver.org\'"
"L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver."
"Inserisci i tuoi dati"
"Bentornato!"
+ "Accedi a %1$s"
+ "Cambia fornitore dell\'account"
+ "Un server privato per i dipendenti di Element."
+ "Matrix è una rete aperta per comunicazioni sicure e decentralizzate."
+ "Qui è dove vivranno le tue conversazioni — proprio come useresti un fornitore di posta elettronica per conservare le tue email."
+ "Stai per accedere a %1$s"
+ "Stai per creare un account su %1$s"
+ "Al momento c\'è una grande richiesta per %1$s su %2$s. Torna a visitare l\'app tra qualche giorno e riprova.
+
+Grazie per la pazienza!"
+ "Ci sei quasi."
+ "Sei dentro."
+ "Matrix è una rete aperta per comunicazioni sicure e decentralizzate."
+ "Benvenuti in %1$s!"
diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt
index 636dd68058..bf103745b4 100644
--- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt
+++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt
@@ -18,6 +18,8 @@ package io.element.android.features.logout.impl.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.res.stringResource
import io.element.android.features.logout.impl.R
import io.element.android.libraries.architecture.AsyncAction
@@ -52,9 +54,11 @@ fun LogoutActionDialog(
onRetry = onForceLogoutClicked,
onDismiss = onDismissError,
)
- is AsyncAction.Success ->
+ is AsyncAction.Success -> {
+ val latestOnSuccessLogout by rememberUpdatedState(onSuccessLogout)
LaunchedEffect(state) {
- onSuccessLogout(state.data)
+ latestOnSuccessLogout(state.data)
}
+ }
}
}
diff --git a/features/logout/impl/src/main/res/values-it/translations.xml b/features/logout/impl/src/main/res/values-it/translations.xml
index 73c1fbd3a7..50b79fa708 100644
--- a/features/logout/impl/src/main/res/values-it/translations.xml
+++ b/features/logout/impl/src/main/res/values-it/translations.xml
@@ -2,4 +2,17 @@
"Sei sicuro di voler uscire?"
"Uscita in corso…"
+ "Stai per disconnettere la tua ultima sessione. Se esci ora, perderai l\'accesso ai tuoi messaggi cifrati."
+ "Hai disattivato il backup"
+ "Il backup delle chiavi era ancora in corso quando sei andato offline. Riconnettiti per eseguire il backup delle chiavi prima di uscire."
+ "Il backup delle chiavi è ancora in corso"
+ "Attendi il completamento dell\'operazione prima di uscire."
+ "Il backup delle chiavi è ancora in corso"
+ "Stai per disconnettere la tua ultima sessione. Se esci ora, perderai l\'accesso ai tuoi messaggi cifrati."
+ "Recupero non impostato"
+ "Stai per disconnettere la tua ultima sessione. Se esci ora, potresti perdere l\'accesso ai tuoi messaggi cifrati."
+ "Hai salvato la chiave di recupero?"
+ "Disconnetti"
+ "Disconnetti"
+ "Disconnetti"
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 e40d0bbb9b..d378014e2f 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
@@ -63,7 +63,7 @@ import io.element.android.features.messages.impl.utils.messagesummary.MessageSum
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
@@ -81,10 +81,10 @@ 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.user.CurrentSessionIdHolder
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
-import io.element.android.libraries.matrix.ui.room.canRedactAsState
+import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
+import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import kotlinx.coroutines.CoroutineScope
@@ -107,12 +107,11 @@ class MessagesPresenter @AssistedInject constructor(
private val messageSummaryFormatter: MessageSummaryFormatter,
private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper,
- private val preferencesStore: PreferencesStore,
+ private val appPreferencesStore: AppPreferencesStore,
private val featureFlagsService: FeatureFlagService,
private val htmlConverterProvider: HtmlConverterProvider,
@Assisted private val navigator: MessagesNavigator,
private val buildMeta: BuildMeta,
- private val currentSessionIdHolder: CurrentSessionIdHolder,
) : Presenter {
private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator)
@@ -123,7 +122,7 @@ class MessagesPresenter @AssistedInject constructor(
@Composable
override fun present(): MessagesState {
- htmlConverterProvider.Update(currentUserId = currentSessionIdHolder.current)
+ htmlConverterProvider.Update(currentUserId = room.sessionId)
val roomInfo by room.roomInfoFlow.collectAsState(null)
val localCoroutineScope = rememberCoroutineScope()
@@ -138,7 +137,8 @@ class MessagesPresenter @AssistedInject constructor(
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
- val userHasPermissionToRedact by room.canRedactAsState(updateKey = syncUpdateFlow.value)
+ val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = syncUpdateFlow.value)
+ val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value)
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value)
val roomName: AsyncData by remember {
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
@@ -155,15 +155,15 @@ class MessagesPresenter @AssistedInject constructor(
mutableStateOf(false)
}
- LaunchedEffect(currentSessionIdHolder.current) {
+ LaunchedEffect(syncUpdateFlow.value) {
withContext(dispatchers.io) {
- canJoinCall = room.canUserJoinCall(userId = currentSessionIdHolder.current).getOrDefault(false)
+ canJoinCall = room.canUserJoinCall(room.sessionId).getOrDefault(false)
}
}
val inviteProgress = remember { mutableStateOf>(AsyncData.Uninitialized) }
var showReinvitePrompt by remember { mutableStateOf(false) }
- LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow) {
+ LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow.value) {
withContext(dispatchers.io) {
showReinvitePrompt = !hasDismissedInviteDialog && composerState.hasFocus && room.isDirect && room.activeMemberCount == 1L
}
@@ -176,7 +176,7 @@ class MessagesPresenter @AssistedInject constructor(
timelineState.eventSink(TimelineEvents.SetHighlightedEvent(composerState.mode.relatedEventId))
}
- val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true)
+ val enableTextFormatting by appPreferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true)
var enableVoiceMessages by remember { mutableStateOf(false) }
LaunchedEffect(featureFlagsService) {
@@ -219,7 +219,8 @@ class MessagesPresenter @AssistedInject constructor(
roomName = roomName,
roomAvatar = roomAvatar,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
- userHasPermissionToRedact = userHasPermissionToRedact,
+ userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
+ userHasPermissionToRedactOther = userHasPermissionToRedactOther,
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
composerState = composerState,
voiceMessageComposerState = voiceMessageComposerState,
@@ -312,7 +313,7 @@ class MessagesPresenter @AssistedInject constructor(
}
}
- private suspend fun handleActionEdit(
+ private fun handleActionEdit(
targetEvent: TimelineItem.Event,
composerState: MessageComposerState,
enableTextFormatting: Boolean,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt
index c196535607..8e4d1c484b 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt
@@ -36,7 +36,8 @@ data class MessagesState(
val roomName: AsyncData,
val roomAvatar: AsyncData,
val userHasPermissionToSendMessage: Boolean,
- val userHasPermissionToRedact: Boolean,
+ val userHasPermissionToRedactOwn: Boolean,
+ val userHasPermissionToRedactOther: Boolean,
val userHasPermissionToSendReaction: Boolean,
val composerState: MessageComposerState,
val voiceMessageComposerState: VoiceMessageComposerState,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
index 8fc7466f95..1bb500e08f 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
@@ -86,7 +86,8 @@ fun aMessagesState() = MessagesState(
roomName = AsyncData.Success("Room name"),
roomAvatar = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
userHasPermissionToSendMessage = true,
- userHasPermissionToRedact = false,
+ userHasPermissionToRedactOwn = false,
+ userHasPermissionToRedactOther = false,
userHasPermissionToSendReaction = true,
composerState = aMessageComposerState().copy(
richTextEditorState = RichTextEditorState("Hello", initialFocus = true),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
index 96af343a5a..be3afb6660 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
@@ -41,8 +41,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@@ -160,7 +162,8 @@ fun MessagesView(
state.actionListState.eventSink(
ActionListEvents.ComputeForMessage(
event = event,
- canRedact = state.userHasPermissionToRedact,
+ canRedactOwn = state.userHasPermissionToRedactOwn,
+ canRedactOther = state.userHasPermissionToRedactOther,
canSendMessage = state.userHasPermissionToSendMessage,
canSendReaction = state.userHasPermissionToSendReaction,
)
@@ -293,8 +296,11 @@ private fun AttachmentStateView(
) {
when (state) {
AttachmentsState.None -> Unit
- is AttachmentsState.Previewing -> LaunchedEffect(state) {
- onPreviewAttachments(state.attachments)
+ is AttachmentsState.Previewing -> {
+ val latestOnPreviewAttachments by rememberUpdatedState(onPreviewAttachments)
+ LaunchedEffect(state) {
+ latestOnPreviewAttachments(state.attachments)
+ }
}
is AttachmentsState.Sending -> {
ProgressDialog(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt
index 6339716ccf..e486c1ae2b 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt
@@ -22,7 +22,8 @@ sealed interface ActionListEvents {
data object Clear : ActionListEvents
data class ComputeForMessage(
val event: TimelineItem.Event,
- val canRedact: Boolean,
+ val canRedactOwn: Boolean,
+ val canRedactOther: Boolean,
val canSendMessage: Boolean,
val canSendReaction: Boolean,
) : ActionListEvents
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
index 474a0cf022..c5d449b288 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
@@ -31,7 +31,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
import io.element.android.features.messages.impl.timeline.model.event.canReact
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.architecture.Presenter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
@@ -39,7 +39,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
class ActionListPresenter @Inject constructor(
- private val preferencesStore: PreferencesStore,
+ private val appPreferencesStore: AppPreferencesStore,
) : Presenter {
@Composable
override fun present(): ActionListState {
@@ -49,14 +49,15 @@ class ActionListPresenter @Inject constructor(
mutableStateOf(ActionListState.Target.None)
}
- val isDeveloperModeEnabled by preferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false)
+ val isDeveloperModeEnabled by appPreferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false)
fun handleEvents(event: ActionListEvents) {
when (event) {
ActionListEvents.Clear -> target.value = ActionListState.Target.None
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
timelineItem = event.event,
- userCanRedact = event.canRedact,
+ userCanRedactOwn = event.canRedactOwn,
+ userCanRedactOther = event.canRedactOther,
userCanSendMessage = event.canSendMessage,
userCanSendReaction = event.canSendReaction,
isDeveloperModeEnabled = isDeveloperModeEnabled,
@@ -73,13 +74,15 @@ class ActionListPresenter @Inject constructor(
private fun CoroutineScope.computeForMessage(
timelineItem: TimelineItem.Event,
- userCanRedact: Boolean,
+ userCanRedactOwn: Boolean,
+ userCanRedactOther: Boolean,
userCanSendMessage: Boolean,
userCanSendReaction: Boolean,
isDeveloperModeEnabled: Boolean,
target: MutableState
) = launch {
target.value = ActionListState.Target.Loading(timelineItem)
+ val canRedact = timelineItem.isMine && userCanRedactOwn || !timelineItem.isMine && userCanRedactOther
val actions =
when (timelineItem.content) {
is TimelineItemRedactedContent -> {
@@ -98,8 +101,10 @@ class ActionListPresenter @Inject constructor(
}
}
is TimelineItemPollContent -> {
+ val canEndPoll = timelineItem.isRemote &&
+ !timelineItem.content.isEnded &&
+ (timelineItem.isMine || canRedact)
buildList {
- val isMineOrCanRedact = timelineItem.isMine || userCanRedact
if (timelineItem.isRemote) {
// Can only reply or forward messages already uploaded to the server
add(TimelineItemAction.Reply)
@@ -107,7 +112,7 @@ class ActionListPresenter @Inject constructor(
if (timelineItem.isRemote && timelineItem.isEditable) {
add(TimelineItemAction.Edit)
}
- if (timelineItem.isRemote && !timelineItem.content.isEnded && isMineOrCanRedact) {
+ if (canEndPoll) {
add(TimelineItemAction.EndPoll)
}
if (timelineItem.content.canBeCopied()) {
@@ -119,7 +124,7 @@ class ActionListPresenter @Inject constructor(
if (!timelineItem.isMine) {
add(TimelineItemAction.ReportContent)
}
- if (isMineOrCanRedact) {
+ if (canRedact) {
add(TimelineItemAction.Redact)
}
}
@@ -136,7 +141,7 @@ class ActionListPresenter @Inject constructor(
if (!timelineItem.isMine) {
add(TimelineItemAction.ReportContent)
}
- if (timelineItem.isMine || userCanRedact) {
+ if (canRedact) {
add(TimelineItemAction.Redact)
}
}
@@ -169,7 +174,7 @@ class ActionListPresenter @Inject constructor(
if (!timelineItem.isMine) {
add(TimelineItemAction.ReportContent)
}
- if (timelineItem.isMine || userCanRedact) {
+ if (canRedact) {
add(TimelineItemAction.Redact)
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt
index 5b624bd254..b18d36d61d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt
@@ -24,6 +24,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -57,8 +59,9 @@ fun AttachmentsPreviewView(
}
if (state.sendActionState is SendActionState.Done) {
+ val latestOnDismiss by rememberUpdatedState(onDismiss)
LaunchedEffect(state.sendActionState) {
- onDismiss()
+ latestOnDismiss()
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt
index 3029a174fc..aa33b1cbd9 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt
@@ -18,9 +18,6 @@ package io.element.android.features.messages.impl.forward
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.core.RoomId
-import io.element.android.libraries.matrix.api.room.RoomMember
-import io.element.android.libraries.matrix.api.room.message.RoomMessage
-import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -51,30 +48,3 @@ fun aForwardMessagesState(
forwardingSucceeded = forwardingSucceeded,
eventSink = {}
)
-
-internal fun aForwardMessagesRoomList() = persistentListOf(
- aRoomDetailsState(),
- aRoomDetailsState(roomId = RoomId("!room2:domain"), canonicalAlias = "#element-x-room:matrix.org"),
-)
-
-fun aRoomDetailsState(
- roomId: RoomId = RoomId("!room:domain"),
- name: String = "roomName",
- canonicalAlias: String? = null,
- isDirect: Boolean = true,
- avatarURLString: String? = null,
- lastMessage: RoomMessage? = null,
- lastMessageTimestamp: Long? = null,
- unreadNotificationCount: Int = 0,
- inviter: RoomMember? = null,
-) = RoomSummaryDetails(
- roomId = roomId,
- name = name,
- canonicalAlias = canonicalAlias,
- isDirect = isDirect,
- avatarURLString = avatarURLString,
- lastMessage = lastMessage,
- lastMessageTimestamp = lastMessageTimestamp,
- unreadNotificationCount = unreadNotificationCount,
- inviter = inviter,
-)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt
index 4f5c2aba10..9ddf1f7aae 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt
@@ -43,6 +43,7 @@ sealed interface MessageComposerEvents {
data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvents
data object CancelSendAttachment : MessageComposerEvents
data class Error(val error: Throwable) : MessageComposerEvents
+ data class TypingNotice(val isTyping: Boolean) : MessageComposerEvents
data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents
data class InsertMention(val mention: MentionSuggestion) : MessageComposerEvents
}
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 7a3e1baf13..4fea2ee046 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
@@ -20,6 +20,7 @@ import android.Manifest
import android.annotation.SuppressLint
import android.net.Uri
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
@@ -183,9 +184,8 @@ class MessageComposerPresenter @Inject constructor(
val currentUserId = currentSessionIdHolder.current
suspend fun canSendRoomMention(): Boolean {
- val roomIsDm = room.isDirect && room.isOneToOne
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
- return !roomIsDm && userCanSendAtRoom
+ return !room.isDm && userCanSendAtRoom
}
// This will trigger a search immediately when `@` is typed
@@ -208,6 +208,15 @@ class MessageComposerPresenter @Inject constructor(
.collect()
}
+ DisposableEffect(Unit) {
+ // Declare that the user is not typing anymore when the composer is disposed
+ onDispose {
+ appCoroutineScope.launch {
+ room.typingNotice(false)
+ }
+ }
+ }
+
fun handleEvents(event: MessageComposerEvents) {
when (event) {
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
@@ -300,6 +309,11 @@ class MessageComposerPresenter @Inject constructor(
is MessageComposerEvents.Error -> {
analyticsService.trackError(event.error)
}
+ is MessageComposerEvents.TypingNotice -> {
+ localCoroutineScope.launch {
+ room.typingNotice(event.isTyping)
+ }
+ }
is MessageComposerEvents.SuggestionReceived -> {
suggestionSearchTrigger.value = event.suggestion
}
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 47b5adea5e..6cb2900a20 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
@@ -78,6 +78,10 @@ internal fun MessageComposerView(
state.eventSink(MessageComposerEvents.Error(error))
}
+ fun onTyping(typing: Boolean) {
+ state.eventSink(MessageComposerEvents.TypingNotice(typing))
+ }
+
val coroutineScope = rememberCoroutineScope()
fun onRequestFocus() {
coroutineScope.launch {
@@ -121,6 +125,7 @@ internal fun MessageComposerView(
onDeleteVoiceMessage = onDeleteVoiceMessage,
onSuggestionReceived = ::onSuggestionReceived,
onError = ::onError,
+ onTyping = ::onTyping,
currentUserId = state.currentUserId,
onRichContentSelected = ::sendUri,
)
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 85b555fa7c..a0d7ebf771 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
@@ -38,6 +38,7 @@ import io.element.android.features.messages.impl.timeline.session.SessionState
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
import io.element.android.features.poll.api.actions.EndPollAction
import io.element.android.features.poll.api.actions.SendPollResponseAction
+import io.element.android.features.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.EventId
@@ -53,6 +54,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -72,6 +74,7 @@ class TimelinePresenter @AssistedInject constructor(
private val redactedVoiceMessageManager: RedactedVoiceMessageManager,
private val sendPollResponseAction: SendPollResponseAction,
private val endPollAction: EndPollAction,
+ private val sessionPreferencesStore: SessionPreferencesStore,
) : Presenter {
@AssistedFactory
interface Factory {
@@ -102,6 +105,8 @@ class TimelinePresenter @AssistedInject constructor(
val sessionVerifiedStatus by verificationService.sessionVerifiedStatus.collectAsState()
val keyBackupState by encryptionService.backupStateStateFlow.collectAsState()
+ val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true)
+
val sessionState by remember {
derivedStateOf {
SessionState(
@@ -111,8 +116,6 @@ class TimelinePresenter @AssistedInject constructor(
}
}
- val membersState by room.membersStateFlow.collectAsState()
-
fun handleEvents(event: TimelineEvents) {
when (event) {
TimelineEvents.LoadMore -> localScope.paginateBackwards()
@@ -125,7 +128,8 @@ class TimelinePresenter @AssistedInject constructor(
firstVisibleIndex = event.firstIndex,
timelineItems = timelineItems,
lastReadReceiptIndex = lastReadReceiptIndex,
- lastReadReceiptId = lastReadReceiptId
+ lastReadReceiptId = lastReadReceiptId,
+ readReceiptType = if (isSendPublicReadReceiptsEnabled) ReceiptType.READ else ReceiptType.READ_PRIVATE,
)
}
is TimelineEvents.PollAnswerSelected -> appScope.launch {
@@ -149,13 +153,12 @@ class TimelinePresenter @AssistedInject constructor(
}
LaunchedEffect(Unit) {
- timeline
- .timelineItems
- .onEach {
+ combine(timeline.timelineItems, room.membersStateFlow) { items, membersState ->
timelineItemsFactory.replaceWith(
- timelineItems = it,
+ timelineItems = items,
roomMembers = membersState.roomMembers().orEmpty()
)
+ items
}
.onEach { timelineItems ->
if (timelineItems.isEmpty()) {
@@ -225,13 +228,14 @@ class TimelinePresenter @AssistedInject constructor(
timelineItems: ImmutableList,
lastReadReceiptIndex: MutableState,
lastReadReceiptId: MutableState,
+ readReceiptType: ReceiptType,
) = launch(dispatchers.computation) {
// Get last valid EventId seen by the user, as the first index might refer to a Virtual item
val eventId = getLastEventIdBeforeOrAt(firstVisibleIndex, timelineItems)
if (eventId != null && firstVisibleIndex <= lastReadReceiptIndex.value && eventId != lastReadReceiptId.value) {
lastReadReceiptIndex.value = firstVisibleIndex
lastReadReceiptId.value = eventId
- timeline.sendReadReceipt(eventId = eventId, receiptType = ReceiptType.READ)
+ timeline.sendReadReceipt(eventId = eventId, receiptType = readReceiptType)
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
index 4f686e594b..563eb78d78 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
@@ -42,6 +42,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -145,7 +146,7 @@ fun TimelineView(
}
}
}
- if (state.paginationState.beginningOfRoomReached) {
+ if (state.paginationState.beginningOfRoomReached && !state.timelineRoomInfo.isDirect) {
item(contentType = "BeginningOfRoomReached") {
TimelineItemRoomBeginningView(roomName = roomName)
}
@@ -193,10 +194,11 @@ private fun BoxScope.TimelineScrollHelper(
}
}
+ val latestOnScrollFinishedAt by rememberUpdatedState(onScrollFinishedAt)
LaunchedEffect(isScrollFinished, isTimelineEmpty) {
if (isScrollFinished && !isTimelineEmpty) {
// Notify the parent composable about the first visible item index when scrolling finishes
- onScrollFinishedAt(lazyListState.firstVisibleItemIndex)
+ latestOnScrollFinishedAt(lazyListState.firstVisibleItemIndex)
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt
index 5df38c1845..334899288d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt
@@ -18,10 +18,11 @@ package io.element.android.features.messages.impl.timeline.components.layout
import android.text.Layout
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.Constraints
@@ -59,23 +60,27 @@ fun ContentAvoidingLayout(
) {
val scope = remember { ContentAvoidingLayoutScopeInstance() }
- SubcomposeLayout(
+ Layout(
modifier = modifier,
- ) { constraints ->
+ content = {
+ scope.content()
+ overlay()
+ }
+ ) { measurables, constraints ->
// Measure the `overlay` view first, in case we need to shrink the `content`
- val overlayPlaceable = subcompose(0, overlay).first().measure(Constraints(minWidth = 0, maxWidth = constraints.maxWidth))
+ val overlayPlaceable = measurables.last().measure(Constraints(minWidth = 0, maxWidth = constraints.maxWidth))
val contentConstraints = if (shrinkContent) {
Constraints(minWidth = 0, maxWidth = constraints.maxWidth - overlayPlaceable.width)
} else {
Constraints(minWidth = 0, maxWidth = constraints.maxWidth)
}
- val contentPlaceable = subcompose(1) { scope.content() }.first().measure(contentConstraints)
+ val contentPlaceable = measurables.first().measure(contentConstraints)
var layoutWidth = contentPlaceable.width
var layoutHeight = contentPlaceable.height
- val data = scope.data
+ val data = scope.data.value
// Free space = width of the whole component - width of its non overlapping contents
val freeSpace = max(contentPlaceable.width - data.nonOverlappingContentWidth, 0)
@@ -135,13 +140,10 @@ interface ContentAvoidingLayoutScope {
}
private class ContentAvoidingLayoutScopeInstance(
- val data: ContentAvoidingLayoutData = ContentAvoidingLayoutData(),
+ val data: MutableState = mutableStateOf(ContentAvoidingLayoutData()),
) : ContentAvoidingLayoutScope {
override fun onContentLayoutChanged(data: ContentAvoidingLayoutData) {
- this.data.contentWidth = data.contentWidth
- this.data.contentHeight = data.contentHeight
- this.data.nonOverlappingContentWidth = data.nonOverlappingContentWidth
- this.data.nonOverlappingContentHeight = data.nonOverlappingContentHeight
+ this.data.value = data
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt
index ffc1a1b3f1..29ea3abcfc 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt
@@ -87,7 +87,16 @@ class TimelineItemsFactory @Inject constructor(
newTimelineItemStates.add(timelineItemState)
}
} else {
- newTimelineItemStates.add(cacheItem)
+ val updatedItem = if (cacheItem is TimelineItem.Event && roomMembers.isNotEmpty()) {
+ eventItemFactory.update(
+ timelineItem = cacheItem,
+ receivedMatrixTimelineItem = timelineItems[index] as MatrixTimelineItem.Event,
+ roomMembers = roomMembers
+ )
+ } else {
+ cacheItem
+ }
+ newTimelineItemStates.add(updatedItem)
}
}
val result = timelineItemGrouper.group(newTimelineItemStates).toPersistentList()
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt
index abd4dce536..4a15a47f95 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt
@@ -24,13 +24,13 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
-import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
+import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import javax.inject.Inject
class TimelineItemContentFactory @Inject constructor(
@@ -50,7 +50,7 @@ class TimelineItemContentFactory @Inject constructor(
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)
is MessageContent -> {
- val senderDisplayName = (eventTimelineItem.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: eventTimelineItem.sender.value
+ val senderDisplayName = eventTimelineItem.senderProfile.getDisambiguatedDisplayName(eventTimelineItem.sender)
messageFactory.create(itemContent, senderDisplayName, eventTimelineItem.eventId)
}
is ProfileChangeContent -> profileChangeFactory.create(eventTimelineItem)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
index 0f204921d2..38125dcef7 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
@@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
+import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import kotlinx.collections.immutable.toImmutableList
import java.text.DateFormat
import java.util.Date
@@ -52,21 +53,7 @@ class TimelineItemEventFactory @Inject constructor(
val currentSender = currentTimelineItem.event.sender
val groupPosition =
computeGroupPosition(currentTimelineItem, timelineItems, index)
- val senderDisplayName: String?
- val senderAvatarUrl: String?
-
- when (val senderProfile = currentTimelineItem.event.senderProfile) {
- ProfileTimelineDetails.Unavailable,
- ProfileTimelineDetails.Pending,
- is ProfileTimelineDetails.Error -> {
- senderDisplayName = null
- senderAvatarUrl = null
- }
- is ProfileTimelineDetails.Ready -> {
- senderDisplayName = senderProfile.displayName
- senderAvatarUrl = senderProfile.avatarUrl
- }
- }
+ val (senderDisplayName, senderAvatarUrl) = currentTimelineItem.getSenderInfo()
val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp))
@@ -100,6 +87,36 @@ class TimelineItemEventFactory @Inject constructor(
)
}
+ fun update(
+ timelineItem: TimelineItem.Event,
+ receivedMatrixTimelineItem: MatrixTimelineItem.Event,
+ roomMembers: List,
+ ): TimelineItem.Event {
+ return timelineItem.copy(
+ readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers)
+ )
+ }
+
+ private fun MatrixTimelineItem.Event.getSenderInfo(): Pair {
+ val senderDisplayName: String?
+ val senderAvatarUrl: String?
+
+ when (val senderProfile = event.senderProfile) {
+ ProfileTimelineDetails.Unavailable,
+ ProfileTimelineDetails.Pending,
+ is ProfileTimelineDetails.Error -> {
+ senderDisplayName = null
+ senderAvatarUrl = null
+ }
+ is ProfileTimelineDetails.Ready -> {
+ senderDisplayName = senderProfile.getDisambiguatedDisplayName(event.sender)
+ senderAvatarUrl = senderProfile.avatarUrl
+ }
+ }
+
+ return senderDisplayName to senderAvatarUrl
+ }
+
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
var aggregatedReactions = event.reactions.map { reaction ->
diff --git a/features/messages/impl/src/main/res/values-cs/translations.xml b/features/messages/impl/src/main/res/values-cs/translations.xml
index 62a5ceb29b..1cb2a3f57f 100644
--- a/features/messages/impl/src/main/res/values-cs/translations.xml
+++ b/features/messages/impl/src/main/res/values-cs/translations.xml
@@ -13,6 +13,16 @@
- "%1$d změny místnosti"
- "%1$d změn místnosti"
+
+ - "%1$s, %2$s a %3$d další"
+ - "%1$s, %2$s a %3$d další"
+ - "%1$s, %2$s a %3$d dalších"
+
+
+ - "%1$s píše"
+ - "%1$s píší"
+ - "%1$s píše"
+
"Tato zpráva bude nahlášena správci vašeho domovského serveru. Nebude si moci přečíst žádné šifrované zprávy."
"Důvod nahlášení tohoto obsahu"
"Toto je začátek %1$s."
@@ -54,6 +64,7 @@
"Vaši zprávu se nepodařilo odeslat"
"Přidat emoji"
"Zobrazit méně"
+ "%1$s a %2$s"
"Držte pro nahrávání"
"Všichni"
"Zablokovat uživatele"
diff --git a/features/messages/impl/src/main/res/values-de/translations.xml b/features/messages/impl/src/main/res/values-de/translations.xml
index c7e7ec276b..2ecb2a31a8 100644
--- a/features/messages/impl/src/main/res/values-de/translations.xml
+++ b/features/messages/impl/src/main/res/values-de/translations.xml
@@ -20,7 +20,7 @@
"Den ganzen Raum benachrichtigen"
"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest"
"Kamera"
- "Foto machen"
+ "Foto aufnehmen"
"Video aufnehmen"
"Anhang"
"Foto- und Videobibliothek"
diff --git a/features/messages/impl/src/main/res/values-fr/translations.xml b/features/messages/impl/src/main/res/values-fr/translations.xml
index 061cd73f41..51ea5ab07f 100644
--- a/features/messages/impl/src/main/res/values-fr/translations.xml
+++ b/features/messages/impl/src/main/res/values-fr/translations.xml
@@ -12,6 +12,14 @@
- "%1$d changement dans le salon"
- "%1$d changements dans le salon"
+
+ - "%1$s, %2$s et %3$d autre"
+ - "%1$s, %2$s et %3$d autres"
+
+
+ - "%1$s écrit"
+ - "%1$s écrivent"
+
"Ce message sera signalé à l’administrateur de votre serveur d’accueil. Il ne pourra lire aucun message chiffré."
"Raison du signalement de ce contenu"
"Ceci est le début de %1$s."
@@ -23,7 +31,7 @@
"Prendre une photo"
"Enregistrer une vidéo"
"Pièce jointe"
- "Gallerie Photo et Vidéo"
+ "Galerie Photo et Vidéo"
"Position"
"Sondage"
"Formatage du texte"
@@ -53,6 +61,7 @@
"Votre message n’a pas pu être envoyé"
"Ajouter un émoji"
"Afficher moins"
+ "%1$s et %2$s"
"Maintenir pour enregistrer"
"Tout le monde"
"Bloquer l’utilisateur"
diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml
index c36e8c515c..0dd03afae2 100644
--- a/features/messages/impl/src/main/res/values-it/translations.xml
+++ b/features/messages/impl/src/main/res/values-it/translations.xml
@@ -17,6 +17,45 @@
"Questo è l\'inizio di %1$s."
"Questo è l\'inizio della conversazione."
"Nuovo"
+ "Avvisa l\'intera stanza"
"Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente"
+ "Fotocamera"
+ "Scatta foto"
+ "Registra video"
+ "Allegato"
+ "Libreria di foto e video"
+ "Posizione"
+ "Sondaggio"
+ "Formattazione del testo"
+ "La cronologia dei messaggi non è attualmente disponibile."
+ "La cronologia dei messaggi non è disponibile in questa stanza. Verifica questo dispositivo per vedere la cronologia dei messaggi."
+ "Impossibile recuperare i dettagli dell\'utente"
+ "Vorresti invitarli di nuovo?"
+ "Ci sei solo tu in questa chat"
+ "Messaggio copiato"
+ "Non sei autorizzato a postare in questa stanza"
+ "Consenti impostazione personalizzata"
+ "L\'attivazione di questa opzione sovrascriverà l\'impostazione predefinita"
+ "Avvisami in questa chat per"
+ "Puoi cambiarlo nelle tue %1$s."
+ "impostazioni globali"
+ "Impostazione predefinita"
+ "Rimuovi l\'impostazione personalizzata"
+ "Si è verificato un errore durante il caricamento delle impostazioni di notifica."
+ "Ripristino della modalità predefinita fallito, riprova."
+ "Impossibile impostare la modalità, riprova."
+ "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi non riceverai notifiche in questa stanza."
+ "Tutti i messaggi"
+ "In questa stanza, avvisami per"
+ "Mostra meno"
+ "Mostra di più"
+ "Invia di nuovo"
+ "Il tuo messaggio non è stato inviato"
+ "Aggiungi emoji"
+ "Mostra meno"
+ "Tieni premuto per registrare"
+ "Tutti"
"Blocca utente"
+ "Elaborazione del file multimediale da caricare fallita, riprova."
+ "Solo menzioni e parole chiave"
diff --git a/features/messages/impl/src/main/res/values-ru/translations.xml b/features/messages/impl/src/main/res/values-ru/translations.xml
index 77f7e1d0e5..a537ef01ac 100644
--- a/features/messages/impl/src/main/res/values-ru/translations.xml
+++ b/features/messages/impl/src/main/res/values-ru/translations.xml
@@ -5,7 +5,7 @@
"Еда и напитки"
"Животные и природа"
"Объекты"
- "Смайлы и люди"
+ "Улыбки и люди"
"Путешествия и места"
"Символы"
@@ -13,6 +13,16 @@
- "%1$d изменения в комнате"
- "%1$d изменений в комнате"
+
+ - "%1$s, %2$s и %3$d"
+ - "%1$s, %2$s и другие %3$d"
+ - "%1$s, %2$s и другие %3$d"
+
+
+ - "%1$s набирает сообщение"
+ - "%1$s набирают сообщения"
+ - "%1$s набирают сообщения"
+
"Это сообщение будет передано администратору вашего домашнего сервера. Они не смогут прочитать зашифрованные сообщения."
"Причина, по которой вы пожаловались на этот контент"
"Это начало %1$s."
@@ -54,6 +64,7 @@
"Не удалось отправить ваше сообщение"
"Добавить эмодзи"
"Показать меньше"
+ "%1$s и %2$s"
"Удерживайте для записи"
"Для всех"
"Заблокировать пользователя"
diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml
index d9c5fce030..3d781ab8cb 100644
--- a/features/messages/impl/src/main/res/values/localazy.xml
+++ b/features/messages/impl/src/main/res/values/localazy.xml
@@ -12,6 +12,14 @@
- "%1$d room change"
- "%1$d room changes"
+
+ - "%1$s, %2$s and %3$d other"
+ - "%1$s, %2$s and %3$d others"
+
+
+ - "%1$s is typing"
+ - "%1$s are typing"
+
"This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages."
"Reason for reporting this content"
"This is the beginning of %1$s."
@@ -53,6 +61,7 @@
"Your message failed to send"
"Add emoji"
"Show less"
+ "%1$s and %2$s"
"Hold to record"
"Everyone"
"Block user"
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 1ad6a693ab..1c10cb97e8 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
@@ -59,7 +59,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
-import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
@@ -120,7 +121,7 @@ class MessagesPresenterTest {
assertThat(initialState.roomAvatar)
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
- assertThat(initialState.userHasPermissionToRedact).isFalse()
+ assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
assertThat(initialState.hasNetworkConnection).isTrue()
assertThat(initialState.snackbarMessage).isNull()
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
@@ -601,14 +602,29 @@ class MessagesPresenterTest {
}
@Test
- fun `present - permission to redact`() = runTest {
- val matrixRoom = FakeMatrixRoom(canRedact = true)
+ fun `present - permission to redact own`() = runTest {
+ val matrixRoom = FakeMatrixRoom(canRedactOwn = true)
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedact }.last()
- assertThat(initialState.userHasPermissionToRedact).isTrue()
+ val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOwn }.last()
+ assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
+ assertThat(initialState.userHasPermissionToRedactOther).isFalse()
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - permission to redact other`() = runTest {
+ val matrixRoom = FakeMatrixRoom(canRedactOther = true)
+ val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOther }.last()
+ assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
+ assertThat(initialState.userHasPermissionToRedactOther).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@@ -649,10 +665,11 @@ class MessagesPresenterTest {
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
- currentSessionIdHolder: CurrentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)),
): MessagesPresenter {
val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom)
val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter)
+ val appPreferencesStore = InMemoryAppPreferencesStore(isRichTextEditorEnabled = true)
+ val sessionPreferencesStore = InMemorySessionPreferencesStore()
val messageComposerPresenter = MessageComposerPresenter(
appCoroutineScope = this,
room = matrixRoom,
@@ -687,14 +704,14 @@ class MessagesPresenterTest {
redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
endPollAction = FakeEndPollAction(),
sendPollResponseAction = FakeSendPollResponseAction(),
+ sessionPreferencesStore = sessionPreferencesStore,
)
val timelinePresenterFactory = object : TimelinePresenter.Factory {
override fun create(navigator: MessagesNavigator): TimelinePresenter {
return timelinePresenter
}
}
- val preferencesStore = InMemoryPreferencesStore(isRichTextEditorEnabled = true)
- val actionListPresenter = ActionListPresenter(preferencesStore = preferencesStore)
+ val actionListPresenter = ActionListPresenter(appPreferencesStore = appPreferencesStore)
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
@@ -714,11 +731,10 @@ class MessagesPresenterTest {
messageSummaryFormatter = FakeMessageSummaryFormatter(),
navigator = navigator,
clipboardHelper = clipboardHelper,
- preferencesStore = preferencesStore,
+ appPreferencesStore = appPreferencesStore,
featureFlagsService = FakeFeatureFlagService(),
buildMeta = aBuildMeta(),
dispatchers = coroutineDispatchers,
- currentSessionIdHolder = currentSessionIdHolder,
htmlConverterProvider = FakeHtmlConverterProvider(),
)
}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt
index 0dbdc7194b..981e8fc8ac 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt
@@ -30,7 +30,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
-import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.collections.immutable.persistentListOf
@@ -38,6 +38,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
+@Suppress("LargeClass")
class ActionListPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@@ -61,7 +62,15 @@ class ActionListPresenterTest {
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -87,7 +96,15 @@ class ActionListPresenterTest {
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -116,7 +133,15 @@ class ActionListPresenterTest {
isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -149,7 +174,15 @@ class ActionListPresenterTest {
isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = false,
+ canSendReaction = true
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -181,7 +214,15 @@ class ActionListPresenterTest {
isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = true,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -213,7 +254,15 @@ class ActionListPresenterTest {
isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = false))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = true,
+ canSendMessage = true,
+ canSendReaction = false
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -245,7 +294,15 @@ class ActionListPresenterTest {
isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -268,6 +325,47 @@ class ActionListPresenterTest {
}
}
+ @Test
+ fun `present - compute for my message cannot redact`() = runTest {
+ val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ val messageEvent = aMessageEvent(
+ isMine = true,
+ content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
+ )
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
+ // val loadingState = awaitItem()
+ // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
+ val successState = awaitItem()
+ assertThat(successState.target).isEqualTo(
+ ActionListState.Target.Success(
+ event = messageEvent,
+ displayEmojiReactions = true,
+ actions = persistentListOf(
+ TimelineItemAction.Reply,
+ TimelineItemAction.Forward,
+ TimelineItemAction.Edit,
+ TimelineItemAction.Copy,
+ TimelineItemAction.ViewSource,
+ )
+ )
+ )
+ initialState.eventSink.invoke(ActionListEvents.Clear)
+ assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
+ }
+ }
+
@Test
fun `present - compute for a media item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
@@ -279,7 +377,15 @@ class ActionListPresenterTest {
isMine = true,
content = aTimelineItemImageContent(),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -311,7 +417,15 @@ class ActionListPresenterTest {
isMine = true,
content = aTimelineItemStateEventContent(),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = stateEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -341,7 +455,15 @@ class ActionListPresenterTest {
isMine = true,
content = aTimelineItemStateEventContent(),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = stateEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -370,7 +492,15 @@ class ActionListPresenterTest {
isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
@@ -408,10 +538,26 @@ class ActionListPresenterTest {
content = TimelineItemRedactedContent,
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = redactedEvent,
+ canRedactOwn = false,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
awaitItem().run {
assertThat(target).isEqualTo(ActionListState.Target.None)
}
@@ -432,7 +578,15 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -460,7 +614,15 @@ class ActionListPresenterTest {
isEditable = true,
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -489,7 +651,15 @@ class ActionListPresenterTest {
isEditable = false,
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -517,7 +687,15 @@ class ActionListPresenterTest {
isEditable = false,
content = aTimelineItemPollContent(isEnded = true),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -543,7 +721,15 @@ class ActionListPresenterTest {
isMine = true,
content = aTimelineItemVoiceContent(),
)
- initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
+ initialState.eventSink.invoke(
+ ActionListEvents.ComputeForMessage(
+ event = messageEvent,
+ canRedactOwn = true,
+ canRedactOther = false,
+ canSendMessage = true,
+ canSendReaction = true,
+ )
+ )
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
@@ -561,6 +747,6 @@ class ActionListPresenterTest {
}
private fun createActionListPresenter(isDeveloperModeEnabled: Boolean): ActionListPresenter {
- val preferencesStore = InMemoryPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
- return ActionListPresenter(preferencesStore = preferencesStore)
+ val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
+ return ActionListPresenter(appPreferencesStore = preferencesStore)
}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt
index cf084700ca..c9acdba508 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt
@@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.EventId
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.aRoomSummaryDetail
+import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.runTest
@@ -54,7 +54,7 @@ class ForwardMessagesPresenterTests {
presenter.present()
}.test {
skipItems(1)
- val summary = aRoomSummaryDetail()
+ val summary = aRoomSummaryDetails()
presenter.onRoomSelected(listOf(summary.roomId))
val forwardingState = awaitItem()
assertThat(forwardingState.isForwarding).isTrue()
@@ -74,7 +74,7 @@ class ForwardMessagesPresenterTests {
// Test failed forwarding
room.givenForwardEventResult(Result.failure(Throwable("error")))
skipItems(1)
- val summary = aRoomSummaryDetail()
+ val summary = aRoomSummaryDetails()
presenter.onRoomSelected(listOf(summary.roomId))
skipItems(1)
val failedForwardState = awaitItem()
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 8f9842e309..d4b3c33cb1 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
@@ -873,6 +873,21 @@ class MessageComposerPresenterTest {
}
}
+ @Test
+ fun `present - handle typing notice event`() = runTest {
+ val room = FakeMatrixRoom()
+ val presenter = createPresenter(room = room, coroutineScope = this)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitFirstItem()
+ assertThat(room.typingRecord).isEmpty()
+ initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
+ initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
+ assertThat(room.typingRecord).isEqualTo(listOf(true, false))
+ }
+ }
+
private suspend fun ReceiveTurbine.backToNormalMode(state: MessageComposerState, skipCount: Int = 0): MessageComposerState {
state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode)
skipItems(skipCount)
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
index 5f68622c4d..c01135fb5f 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
@@ -35,14 +35,20 @@ import io.element.android.features.poll.api.actions.SendPollResponseAction
import io.element.android.features.poll.test.actions.FakeEndPollAction
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
import io.element.android.libraries.featureflag.api.FeatureFlags
+import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
+import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
+import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender
+import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import io.element.android.libraries.matrix.test.AN_EVENT_ID
+import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
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.FakeMatrixTimeline
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
@@ -60,6 +66,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import java.util.Date
+import kotlin.time.Duration.Companion.seconds
private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID"
@@ -129,13 +136,41 @@ class TimelinePresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
+ assertThat(timeline.sentReadReceipts).isEmpty()
val initialState = awaitFirstItem()
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
}
- assertThat(timeline.sendReadReceiptCount).isEqualTo(1)
+ assertThat(timeline.sentReadReceipts).isNotEmpty()
+ assertThat(timeline.sentReadReceipts.first().second).isEqualTo(ReceiptType.READ)
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - on scroll finished send a private read receipt if an event is before the index and public read receipts are disabled`() = runTest {
+ val timeline = FakeMatrixTimeline(
+ initialTimelineItems = listOf(
+ MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
+ )
+ )
+ val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
+ val presenter = createTimelinePresenter(
+ timeline = timeline,
+ sessionPreferencesStore = sessionPreferencesStore,
+ )
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ assertThat(timeline.sentReadReceipts).isEmpty()
+ val initialState = awaitFirstItem()
+ awaitWithLatch { latch ->
+ timeline.sendReadReceiptLatch = latch
+ initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
+ }
+ assertThat(timeline.sentReadReceipts).isNotEmpty()
+ assertThat(timeline.sentReadReceipts.first().second).isEqualTo(ReceiptType.READ_PRIVATE)
cancelAndIgnoreRemainingEvents()
}
}
@@ -151,13 +186,13 @@ class TimelinePresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
+ assertThat(timeline.sentReadReceipts).isEmpty()
val initialState = awaitFirstItem()
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
}
- assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
+ assertThat(timeline.sentReadReceipts).isEmpty()
cancelAndIgnoreRemainingEvents()
}
}
@@ -173,13 +208,13 @@ class TimelinePresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
+ assertThat(timeline.sentReadReceipts).isEmpty()
val initialState = awaitFirstItem()
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
}
- assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
+ assertThat(timeline.sentReadReceipts).isEmpty()
cancelAndIgnoreRemainingEvents()
}
}
@@ -353,6 +388,50 @@ class TimelinePresenterTest {
}
}
+ @Test
+ fun `present - when room member info is loaded, read receipts info should be updated`() = runTest {
+ val timeline = FakeMatrixTimeline(
+ listOf(
+ MatrixTimelineItem.Event(
+ FAKE_UNIQUE_ID,
+ anEventTimelineItem(
+ sender = A_USER_ID,
+ receipts = persistentListOf(
+ Receipt(
+ userId = A_USER_ID,
+ timestamp = 0L,
+ )
+ )
+ )
+ )
+ )
+ )
+ val room = FakeMatrixRoom(matrixTimeline = timeline).apply {
+ givenRoomMembersState(MatrixRoomMembersState.Unknown)
+ }
+
+ val avatarUrl = "https://domain.com/avatar.jpg"
+
+ val presenter = createTimelinePresenter(timeline, room)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = consumeItemsUntilPredicate(30.seconds) { it.timelineItems.isNotEmpty() }.last()
+ val event = initialState.timelineItems.first() as TimelineItem.Event
+ assertThat(event.senderAvatar.url).isNull()
+ assertThat(event.readReceiptState.receipts.first().avatarData.url).isNull()
+
+ room.givenRoomMembersState(
+ MatrixRoomMembersState.Ready(
+ persistentListOf(aRoomMember(userId = A_USER_ID, avatarUrl = avatarUrl))
+ )
+ )
+
+ val updatedEvent = awaitItem().timelineItems.first() as TimelineItem.Event
+ assertThat(updatedEvent.readReceiptState.receipts.first().avatarData.url).isEqualTo(avatarUrl)
+ }
+ }
+
private suspend fun ReceiveTurbine.awaitFirstItem(): T {
// Skip 1 item if Mentions feature is enabled
if (FeatureFlags.Mentions.defaultValue) {
@@ -363,15 +442,17 @@ class TimelinePresenterTest {
private fun TestScope.createTimelinePresenter(
timeline: MatrixTimeline = FakeMatrixTimeline(),
+ room: FakeMatrixRoom = FakeMatrixRoom(matrixTimeline = timeline),
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(),
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
endPollAction: EndPollAction = FakeEndPollAction(),
sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(),
+ sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
): TimelinePresenter {
return TimelinePresenter(
timelineItemsFactory = timelineItemsFactory,
- room = FakeMatrixRoom(matrixTimeline = timeline),
+ room = room,
dispatchers = testCoroutineDispatchers(),
appScope = this,
navigator = messagesNavigator,
@@ -380,6 +461,7 @@ class TimelinePresenterTest {
redactedVoiceMessageManager = redactedVoiceMessageManager,
endPollAction = endPollAction,
sendPollResponseAction = sendPollResponseAction,
+ sessionPreferencesStore = sessionPreferencesStore,
)
}
}
diff --git a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt
index 77898ca91f..a70f14dc43 100644
--- a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt
+++ b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt
@@ -33,5 +33,6 @@ interface OnBoardingEntryPoint : FeatureEntryPoint {
fun onSignUp()
fun onSignIn()
fun onOpenDeveloperSettings()
+ fun onReportProblem()
}
}
diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt
index f8653cc921..f7d829d5fe 100644
--- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt
+++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt
@@ -49,6 +49,10 @@ class OnBoardingNode @AssistedInject constructor(
plugins().forEach { it.onOpenDeveloperSettings() }
}
+ private fun onReportProblem() {
+ plugins().forEach { it.onReportProblem() }
+ }
+
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
@@ -59,6 +63,7 @@ class OnBoardingNode @AssistedInject constructor(
onCreateAccount = ::onSignUp,
onSignInWithQrCode = { /* Not supported yet */ },
onOpenDeveloperSettings = ::onOpenDeveloperSettings,
+ onReportProblem = ::onReportProblem,
)
}
}
diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt
index 8ecef02b0e..c4b4e2d6b4 100644
--- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt
+++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt
@@ -16,6 +16,7 @@
package io.element.android.features.onboarding.impl
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -65,6 +66,7 @@ fun OnBoardingView(
onSignIn: () -> Unit,
onCreateAccount: () -> Unit,
onOpenDeveloperSettings: () -> Unit,
+ onReportProblem: () -> Unit,
modifier: Modifier = Modifier,
) {
OnBoardingPage(
@@ -81,6 +83,7 @@ fun OnBoardingView(
onSignInWithQrCode = onSignInWithQrCode,
onSignIn = onSignIn,
onCreateAccount = onCreateAccount,
+ onReportProblem = onReportProblem,
)
}
)
@@ -154,6 +157,7 @@ private fun OnBoardingButtons(
onSignInWithQrCode: () -> Unit,
onSignIn: () -> Unit,
onCreateAccount: () -> Unit,
+ onReportProblem: () -> Unit,
modifier: Modifier = Modifier,
) {
ButtonColumnMolecule(modifier = modifier) {
@@ -186,7 +190,16 @@ private fun OnBoardingButtons(
.fillMaxWidth()
)
}
- Spacer(modifier = Modifier.height(48.dp))
+ Spacer(modifier = Modifier.height(16.dp))
+ // Add a report problem text button. Use a Text since we need a special theme here.
+ Text(
+ modifier = Modifier
+ .padding(8.dp)
+ .clickable(onClick = onReportProblem),
+ text = stringResource(id = CommonStrings.common_report_a_problem),
+ style = ElementTheme.typography.fontBodySmRegular,
+ color = ElementTheme.colors.textSecondary,
+ )
}
}
@@ -200,6 +213,7 @@ internal fun OnBoardingScreenPreview(
onSignInWithQrCode = {},
onSignIn = {},
onCreateAccount = {},
- onOpenDeveloperSettings = {}
+ onOpenDeveloperSettings = {},
+ onReportProblem = {},
)
}
diff --git a/features/onboarding/impl/src/main/res/values-it/translations.xml b/features/onboarding/impl/src/main/res/values-it/translations.xml
index 652d9e6c22..738c4c8947 100644
--- a/features/onboarding/impl/src/main/res/values-it/translations.xml
+++ b/features/onboarding/impl/src/main/res/values-it/translations.xml
@@ -1,5 +1,9 @@
+ "Accedi manualmente"
+ "Accedi con codice QR"
+ "Crea account"
+ "Benvenuti nell\'Element più veloce di sempre. Potenziato per velocità e semplicità."
"Benvenuto su %1$s. Potenziato in velocità e semplicità."
"Sii nel tuo elemento"
diff --git a/features/onboarding/impl/src/main/res/values-ru/translations.xml b/features/onboarding/impl/src/main/res/values-ru/translations.xml
index 31d09cd396..2a0ab37e1d 100644
--- a/features/onboarding/impl/src/main/res/values-ru/translations.xml
+++ b/features/onboarding/impl/src/main/res/values-ru/translations.xml
@@ -3,7 +3,7 @@
"Вход в систему вручную"
"Войти с помощью QR-кода"
"Создать учетную запись"
- "Добро пожаловать в самый быстрый Element. Преимущество в скорости и простоте."
- "Добро пожаловать в %1$s. Supercharged — это скорость и простота."
- "Будь c element"
+ "Добро пожаловать в самый быстрый Element. Сверхзаряженность на скорость и простоту."
+ "Добро пожаловать в %1$s. Сверхзаряжен для скорости и простоты."
+ "Будьте в своем element"
diff --git a/features/poll/impl/src/main/res/values-it/translations.xml b/features/poll/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..bf7190db23
--- /dev/null
+++ b/features/poll/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,19 @@
+
+
+ "Aggiungi opzione"
+ "Mostra i risultati solo al termine del sondaggio"
+ "Nascondi voti"
+ "Opzione %1$d"
+ "Le modifiche non sono state salvate. Vuoi davvero tornare indietro?"
+ "Domanda o argomento"
+ "Di cosa parla il sondaggio?"
+ "Crea sondaggio"
+ "Vuoi davvero eliminare questo sondaggio?"
+ "Elimina sondaggio"
+ "Modifica sondaggio"
+ "Impossibile trovare sondaggi in corso."
+ "Impossibile trovare sondaggi passati."
+ "In corso"
+ "Passato"
+ "Sondaggi"
+
diff --git a/features/poll/impl/src/main/res/values-ru/translations.xml b/features/poll/impl/src/main/res/values-ru/translations.xml
index 65318d3830..2ed319bbeb 100644
--- a/features/poll/impl/src/main/res/values-ru/translations.xml
+++ b/features/poll/impl/src/main/res/values-ru/translations.xml
@@ -1,6 +1,6 @@
- "Добавить опцию"
+ "Добавить вариант"
"Показывать результаты только после окончания опроса"
"Анонимный опрос"
"Настройка %1$d"
@@ -14,6 +14,6 @@
"Не найдено текущих опросов."
"Не найдено предыдущих опросов."
"Текущие"
- "Прошедшие"
+ "Прошлые"
"Опросы"
diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts
index 406d6a2ff4..e6a605f5e4 100644
--- a/features/preferences/impl/build.gradle.kts
+++ b/features/preferences/impl/build.gradle.kts
@@ -82,6 +82,7 @@ dependencies {
testImplementation(projects.libraries.indicator.impl)
testImplementation(projects.features.logout.impl)
testImplementation(projects.services.analytics.test)
+ testImplementation(projects.services.toolbox.test)
testImplementation(projects.features.analytics.impl)
testImplementation(projects.tests.testutils)
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt
index f22a653f6d..d67a594dd2 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt
@@ -21,6 +21,7 @@ import io.element.android.compound.theme.Theme
sealed interface AdvancedSettingsEvents {
data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
+ data class SetSendPublicReadReceiptsEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data object ChangeTheme : AdvancedSettingsEvents
data object CancelChangeTheme : AdvancedSettingsEvents
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt
index ce5de0b8e8..2ac5b664b5 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt
@@ -25,40 +25,48 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.mapToTheme
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
+import io.element.android.features.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.architecture.Presenter
import kotlinx.coroutines.launch
import javax.inject.Inject
class AdvancedSettingsPresenter @Inject constructor(
- private val preferencesStore: PreferencesStore,
+ private val appPreferencesStore: AppPreferencesStore,
+ private val sessionPreferencesStore: SessionPreferencesStore,
) : Presenter {
@Composable
override fun present(): AdvancedSettingsState {
val localCoroutineScope = rememberCoroutineScope()
- val isRichTextEditorEnabled by preferencesStore
+ val isRichTextEditorEnabled by appPreferencesStore
.isRichTextEditorEnabledFlow()
.collectAsState(initial = false)
- val isDeveloperModeEnabled by preferencesStore
+ val isDeveloperModeEnabled by appPreferencesStore
.isDeveloperModeEnabledFlow()
.collectAsState(initial = false)
+ val isSendPublicReadReceiptsEnabled by sessionPreferencesStore
+ .isSendPublicReadReceiptsEnabled()
+ .collectAsState(initial = true)
val theme by remember {
- preferencesStore.getThemeFlow().mapToTheme()
+ appPreferencesStore.getThemeFlow().mapToTheme()
}
.collectAsState(initial = Theme.System)
var showChangeThemeDialog by remember { mutableStateOf(false) }
fun handleEvents(event: AdvancedSettingsEvents) {
when (event) {
is AdvancedSettingsEvents.SetRichTextEditorEnabled -> localCoroutineScope.launch {
- preferencesStore.setRichTextEditorEnabled(event.enabled)
+ appPreferencesStore.setRichTextEditorEnabled(event.enabled)
}
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
- preferencesStore.setDeveloperModeEnabled(event.enabled)
+ appPreferencesStore.setDeveloperModeEnabled(event.enabled)
+ }
+ is AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled -> localCoroutineScope.launch {
+ sessionPreferencesStore.setSendPublicReadReceipts(event.enabled)
}
AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false
AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true
is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch {
- preferencesStore.setTheme(event.theme.name)
+ appPreferencesStore.setTheme(event.theme.name)
showChangeThemeDialog = false
}
}
@@ -67,6 +75,7 @@ class AdvancedSettingsPresenter @Inject constructor(
return AdvancedSettingsState(
isRichTextEditorEnabled = isRichTextEditorEnabled,
isDeveloperModeEnabled = isDeveloperModeEnabled,
+ isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled,
theme = theme,
showChangeThemeDialog = showChangeThemeDialog,
eventSink = { handleEvents(it) }
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt
index 01d702224f..0ea04185f7 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt
@@ -21,6 +21,7 @@ import io.element.android.compound.theme.Theme
data class AdvancedSettingsState(
val isRichTextEditorEnabled: Boolean,
val isDeveloperModeEnabled: Boolean,
+ val isSendPublicReadReceiptsEnabled: Boolean,
val theme: Theme,
val showChangeThemeDialog: Boolean,
val eventSink: (AdvancedSettingsEvents) -> Unit
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt
index aadf27dd20..acfd9bb026 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt
@@ -26,16 +26,19 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider {
@Composable
override fun present(): DeveloperSettingsState {
@@ -69,7 +69,7 @@ class DeveloperSettingsPresenter @Inject constructor(
val clearCacheAction = remember {
mutableStateOf>(AsyncData.Uninitialized)
}
- val customElementCallBaseUrl by preferencesStore
+ val customElementCallBaseUrl by appPreferencesStore
.getCustomElementCallBaseUrlFlow()
.collectAsState(initial = null)
@@ -100,7 +100,7 @@ class DeveloperSettingsPresenter @Inject constructor(
is DeveloperSettingsEvents.SetCustomElementCallBaseUrl -> coroutineScope.launch {
// If the URL is either empty or the default one, we want to save 'null' to remove the custom URL
val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() && it != ElementCallConfig.DEFAULT_BASE_URL }
- preferencesStore.setCustomElementCallBaseUrl(urlToSave)
+ appPreferencesStore.setCustomElementCallBaseUrl(urlToSave)
}
DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
}
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 0c4ef95c6c..1eb7c0389e 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
@@ -21,7 +21,7 @@ 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.api.roomlist.RoomSummaryDetails
+import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
import kotlinx.collections.immutable.persistentListOf
open class EditDefaultNotificationSettingStateProvider : PreviewParameterProvider {
@@ -49,14 +49,12 @@ private fun anEditDefaultNotificationSettingsState(
)
private fun aRoomSummary() = RoomSummary.Filled(
- RoomSummaryDetails(
+ aRoomSummaryDetails(
roomId = RoomId("!roomId:domain"),
name = "Room",
- avatarURLString = null,
+ avatarUrl = null,
isDirect = false,
lastMessage = null,
- lastMessageTimestamp = null,
- unreadNotificationCount = 0,
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 e67eb280af..9eca90fa54 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
@@ -84,7 +84,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.notificationMode) {
+ val subtitle = when (summary.details.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)
@@ -95,7 +95,7 @@ fun EditDefaultNotificationSettingView(
val avatarData = AvatarData(
id = summary.identifier(),
name = summary.details.name,
- url = summary.details.avatarURLString,
+ url = summary.details.avatarUrl,
size = AvatarSize.CustomRoomNotificationSetting,
)
ListItem(
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt
index 4ad2a95df4..067bd2a26f 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt
@@ -99,6 +99,7 @@ class PreferencesRootPresenter @Inject constructor(
return PreferencesRootState(
myUser = matrixUser.value,
version = versionFormatter.get(),
+ deviceId = matrixClient.deviceId,
showCompleteVerification = showCompleteVerification,
showSecureBackup = !showCompleteVerification && secureStorageFlag == true,
showSecureBackupBadge = showSecureBackupIndicator,
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt
index 0da537f26a..cda7b3e768 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt
@@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
data class PreferencesRootState(
val myUser: MatrixUser?,
val version: String,
+ val deviceId: String?,
val showCompleteVerification: Boolean,
val showSecureBackup: Boolean,
val showSecureBackupBadge: Boolean,
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt
index b99c197d69..d1a60af615 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt
@@ -24,6 +24,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun aPreferencesRootState() = PreferencesRootState(
myUser = null,
version = "Version 1.1 (1)",
+ deviceId = "ILAKNDNASDLK",
showCompleteVerification = true,
showSecureBackup = true,
showSecureBackupBadge = true,
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt
index f25cb515e9..85e841decf 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.InsertChart
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -164,18 +165,38 @@ fun PreferencesRootView(
style = ListItemStyle.Destructive,
onClick = onSignOutClicked,
)
- Text(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 40.dp, bottom = 24.dp),
- textAlign = TextAlign.Center,
- text = state.version,
- style = ElementTheme.typography.fontBodySmRegular,
- color = ElementTheme.materialColors.secondary,
+ Footer(
+ version = state.version,
+ deviceId = state.deviceId,
)
}
}
+@Composable
+private fun Footer(
+ version: String,
+ deviceId: String?
+) {
+ val text = remember(version, deviceId) {
+ buildString {
+ append(version)
+ if (deviceId != null) {
+ append("\n")
+ append(deviceId)
+ }
+ }
+ }
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 40.dp, bottom = 24.dp),
+ textAlign = TextAlign.Center,
+ text = text,
+ style = ElementTheme.typography.fontBodySmRegular,
+ color = ElementTheme.materialColors.secondary,
+ )
+}
+
@Composable
private fun DeveloperPreferencesView(onOpenDeveloperSettings: () -> Unit) {
ListItem(
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt
index 7eff670951..12e7d9f5b9 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt
@@ -33,16 +33,16 @@ class DefaultVersionFormatter @Inject constructor(
private val buildMeta: BuildMeta,
) : VersionFormatter {
override fun get(): String {
- return stringProvider.getString(
+ val base = stringProvider.getString(
CommonStrings.settings_version_number,
buildMeta.versionName,
buildMeta.versionCode.toString()
)
- }
-}
-
-class FakeVersionFormatter : VersionFormatter {
- override fun get(): String {
- return "A Version"
+ return if (buildMeta.gitBranchName == "main") {
+ base
+ } else {
+ // In case of a build not from main, we display the branch name and the revision
+ "$base\n${buildMeta.gitBranchName} (${buildMeta.gitRevision})"
+ }
}
}
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 76d764c131..96aa762e00 100644
--- a/features/preferences/impl/src/main/res/values-cs/translations.xml
+++ b/features/preferences/impl/src/main/res/values-cs/translations.xml
@@ -6,6 +6,8 @@
"Vývojářský režim"
"Povolením získáte přístup k funkcím a funkcím pro vývojáře."
"Vypněte editor formátovaného textu pro ruční zadání Markdown."
+ "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ů."
"Povolit možnost zobrazení zdroje zprávy na časové ose."
"Zobrazované jméno"
"Vaše zobrazované jméno"
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 dc5e6ff790..cb9ebdf2af 100644
--- a/features/preferences/impl/src/main/res/values-fr/translations.xml
+++ b/features/preferences/impl/src/main/res/values-fr/translations.xml
@@ -6,6 +6,8 @@
"Mode développeur"
"Activer pour pouvoir accéder aux fonctionnalités destinées aux développeurs."
"Désactivez l’éditeur de texte enrichi pour saisir manuellement du Markdown."
+ "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."
"Activer cette option pour pouvoir voir la source des messages dans la discussion."
"Pseudonyme"
"Votre pseudonyme"
diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml
index d5286dc323..598c61da3d 100644
--- a/features/preferences/impl/src/main/res/values-hu/translations.xml
+++ b/features/preferences/impl/src/main/res/values-hu/translations.xml
@@ -2,11 +2,11 @@
"Egyéni Element Call alapwebcím"
"Egyéni alapwebcím beállítása az Element Callhoz."
- "Érvénytelen webcím, győződjön meg arról, hogy szerepel benne a protokoll (http/https), és hogy helyes a cím."
+ "Érvénytelen webcím, győződj meg arról, hogy szerepel-e benne a protokoll (http/https), és hogy helyes-e a cím."
"Fejlesztői mód"
- "Engedélyezze, hogy elérje a fejlesztőknek szánt funkciókat."
- "A formázott szöveges szerkesztő letiltása, hogy kézzel írhasson Markdownt."
- "Engedélyezze a beállítást az üzenet forrásának megjelenítéséhez az idővonalon."
+ "Engedélyezd, hogy elérd a fejlesztőknek szánt funkciókat."
+ "A formázott szöveges szerkesztő letiltása, hogy kézzel írhass Markdownt."
+ "Engedélyezd a beállítást az üzenet forrásának megjelenítéséhez az idővonalon."
"Megjelenítendő név"
"Saját megjelenítendő név"
"Ismeretlen hiba történt, és az információ módosítása nem sikerült."
diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..a77e35738a
--- /dev/null
+++ b/features/preferences/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,43 @@
+
+
+ "URL base di Element Call personalizzato"
+ "Imposta un URL di base personalizzato per Element Call."
+ "URL non valido, assicurati di includere il protocollo (http/https) e l\'indirizzo corretto."
+ "Modalità sviluppatore"
+ "Attiva per avere accesso a caratteristiche e funzionalità per sviluppatori."
+ "Disattiva l\'editor in rich text per digitare Markdown manualmente."
+ "Attiva l\'opzione per visualizzare il sorgente del messaggio nella linea temporale."
+ "Nome da mostrare"
+ "Il tuo nome da mostrare"
+ "Si è verificato un errore sconosciuto e non è stato possibile modificare le informazioni."
+ "Impossibile aggiornare il profilo"
+ "Modifica profilo"
+ "Aggiornamento del profilo…"
+ "Impostazioni aggiuntive"
+ "Chiamate audio e video"
+ "Mancata corrispondenza della configurazione"
+ "Abbiamo semplificato le impostazioni di notifica per rendere le opzioni più facili da trovare. Alcune impostazioni personalizzate che hai scelto in passato non sono mostrate qui, ma sono ancora attive.
+
+Se procedi, alcune delle tue impostazioni potrebbero cambiare."
+ "Chat dirette"
+ "Impostazione personalizzata per chat"
+ "Si è verificato un errore durante l\'aggiornamento delle impostazioni di notifica."
+ "Tutti i messaggi"
+ "Solo menzioni e parole chiave"
+ "Nelle chat dirette, avvisami per"
+ "Nelle chat di gruppo, avvisami per"
+ "Attiva le notifiche su questo dispositivo"
+ "La configurazione non è stata corretta, riprova."
+ "Chat di gruppo"
+ "Inviti"
+ "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi potresti non ricevere notifiche in alcune stanze."
+ "Menzioni"
+ "Tutto"
+ "Menzioni"
+ "Avvisami per"
+ "Avvisami su @room"
+ "Per ricevere notifiche, modifica le tue %1$s."
+ "impostazioni di sistema"
+ "Notifiche di sistema disattivate"
+ "Notifiche"
+
diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml
index bbf5f426ea..8c79b71df5 100644
--- a/features/preferences/impl/src/main/res/values-ru/translations.xml
+++ b/features/preferences/impl/src/main/res/values-ru/translations.xml
@@ -6,7 +6,9 @@
"Режим разработчика"
"Предоставьте разработчикам доступ к функциям и функциональным возможностям."
"Отключить редактор форматированного текста и включить Markdown."
- "Включить опцию просмотра источника сообщения на временной шкале."
+ "Уведомления о прочтении"
+ "Если этот параметр выключен, ваш статус о прочтении не будет отображаться. Вы по-прежнему будете видеть статус о прочтении от других пользователей."
+ "Включить опцию просмотра источника сообщения в ленте."
"Отображаемое имя"
"Ваше отображаемое имя"
"Произошла неизвестная ошибка, изменить информацию не удалось."
@@ -20,7 +22,7 @@
Если вы продолжите, некоторые настройки могут быть изменены."
"Прямые чаты"
- "Индивидуальные настройки для каждого чата"
+ "Персональные настройки для каждого чата"
"При обновлении настроек уведомления произошла ошибка."
"Все сообщения"
"Только упоминания и ключевые слова"
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 8171748e1d..ad0a77b61e 100644
--- a/features/preferences/impl/src/main/res/values-sk/translations.xml
+++ b/features/preferences/impl/src/main/res/values-sk/translations.xml
@@ -6,6 +6,8 @@
"Vývojársky režim"
"Umožniť prístup k možnostiam a funkciám pre vývojárov."
"Vypnite rozšírený textový editor na ručné písanie Markdown."
+ "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."
"Povoliť možnosť zobrazenia zdroja správy na časovej osi."
"Zobrazované meno"
"Vaše zobrazované meno"
diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml
index 55c43f05f4..607329332f 100644
--- a/features/preferences/impl/src/main/res/values/localazy.xml
+++ b/features/preferences/impl/src/main/res/values/localazy.xml
@@ -6,6 +6,8 @@
"Developer mode"
"Enable to have access to features and functionality for developers."
"Disable the rich text editor to type Markdown manually."
+ "Read receipts"
+ "If turned off, your read receipts won\'t be sent to anyone. You will still receive read receipts from other users."
"Enable option to view message source in the timeline."
"Display name"
"Your display name"
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt
index 6d7877de32..a042d792b8 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt
@@ -21,7 +21,8 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.compound.theme.Theme
-import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem
import kotlinx.coroutines.test.runTest
@@ -34,8 +35,7 @@ class AdvancedSettingsPresenterTest {
@Test
fun `present - initial state`() = runTest {
- val store = InMemoryPreferencesStore()
- val presenter = AdvancedSettingsPresenter(store)
+ val presenter = createAdvancedSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -43,14 +43,14 @@ class AdvancedSettingsPresenterTest {
assertThat(initialState.isDeveloperModeEnabled).isFalse()
assertThat(initialState.isRichTextEditorEnabled).isFalse()
assertThat(initialState.showChangeThemeDialog).isFalse()
+ assertThat(initialState.isSendPublicReadReceiptsEnabled).isTrue()
assertThat(initialState.theme).isEqualTo(Theme.System)
}
}
@Test
fun `present - developer mode on off`() = runTest {
- val store = InMemoryPreferencesStore()
- val presenter = AdvancedSettingsPresenter(store)
+ val presenter = createAdvancedSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -65,8 +65,7 @@ class AdvancedSettingsPresenterTest {
@Test
fun `present - rich text editor on off`() = runTest {
- val store = InMemoryPreferencesStore()
- val presenter = AdvancedSettingsPresenter(store)
+ val presenter = createAdvancedSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -79,10 +78,24 @@ class AdvancedSettingsPresenterTest {
}
}
+ @Test
+ fun `present - send public read receipts off on`() = runTest {
+ val presenter = createAdvancedSettingsPresenter()
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitLastSequentialItem()
+ assertThat(initialState.isSendPublicReadReceiptsEnabled).isTrue()
+ initialState.eventSink.invoke(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(false))
+ assertThat(awaitItem().isSendPublicReadReceiptsEnabled).isFalse()
+ initialState.eventSink.invoke(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(true))
+ assertThat(awaitItem().isSendPublicReadReceiptsEnabled).isTrue()
+ }
+ }
+
@Test
fun `present - change theme`() = runTest {
- val store = InMemoryPreferencesStore()
- val presenter = AdvancedSettingsPresenter(store)
+ val presenter = createAdvancedSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -102,4 +115,12 @@ class AdvancedSettingsPresenterTest {
assertThat(withNewTheme.theme).isEqualTo(Theme.Light)
}
}
+
+ private fun createAdvancedSettingsPresenter(
+ appPreferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
+ sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
+ ) = AdvancedSettingsPresenter(
+ appPreferencesStore = appPreferencesStore,
+ sessionPreferencesStore = sessionPreferencesStore,
+ )
}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
index 50bf1ab426..19b1614500 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
@@ -29,7 +29,7 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
-import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
+import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem
import kotlinx.coroutines.test.runTest
@@ -114,7 +114,7 @@ class DeveloperSettingsPresenterTest {
@Test
fun `present - custom element call base url`() = runTest {
- val preferencesStore = InMemoryPreferencesStore()
+ val preferencesStore = InMemoryAppPreferencesStore()
val presenter = createDeveloperSettingsPresenter(preferencesStore = preferencesStore)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -149,14 +149,14 @@ class DeveloperSettingsPresenterTest {
cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(),
clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(),
rageshakePresenter: DefaultRageshakePreferencesPresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()),
- preferencesStore: InMemoryPreferencesStore = InMemoryPreferencesStore(),
+ preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
): DeveloperSettingsPresenter {
return DeveloperSettingsPresenter(
featureFlagService = featureFlagService,
computeCacheSizeUseCase = cacheSizeUseCase,
clearCacheUseCase = clearCacheUseCase,
rageshakePresenter = rageshakePresenter,
- preferencesStore = preferencesStore,
+ appPreferencesStore = preferencesStore,
)
}
}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt
index 25b5aa4532..c8655d6a26 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt
@@ -29,7 +29,7 @@ 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.aRoomSummaryDetail
+import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails
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 +72,11 @@ class EditDefaultNotificationSettingsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- roomListService.postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail(notificationMode = RoomNotificationMode.ALL_MESSAGES))))
+ roomListService.postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails(notificationMode = RoomNotificationMode.ALL_MESSAGES))))
val loadedState = consumeItemsUntilPredicate { state ->
- state.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES }
+ state.roomsWithUserDefinedMode.any { it.details.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES }
}.last()
- assertThat(loadedState.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue()
+ assertThat(loadedState.roomsWithUserDefinedMode.any { it.details.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue()
}
}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.kt
new file mode 100644
index 0000000000..e9321c3dd4
--- /dev/null
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.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
+ *
+ * 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.features.preferences.impl.root
+
+class FakeVersionFormatter : VersionFormatter {
+ override fun get(): String {
+ return "A Version"
+ }
+}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt
index 983a820509..2dea13eac3 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt
@@ -70,7 +70,7 @@ class PreferencesRootPresenterTest {
directLogoutPresenter = object : DirectLogoutPresenter {
@Composable
override fun present() = aDirectLogoutState
- }
+ },
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt
new file mode 100644
index 0000000000..ebab2960e9
--- /dev/null
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.features.preferences.impl.root
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.test.core.aBuildMeta
+import io.element.android.services.toolbox.test.strings.FakeStringProvider
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class VersionFormatterTest {
+ @Test
+ fun `version formatter should return simplified version for other branch`() = runTest {
+ val sut = DefaultVersionFormatter(
+ stringProvider = FakeStringProvider(defaultResult = VERSION),
+ buildMeta = aBuildMeta(gitBranchName = "main")
+ )
+ assertThat(sut.get()).isEqualTo(VERSION)
+ }
+
+ @Test
+ fun `version formatter should return simplified version for main branch`() = runTest {
+ val sut = DefaultVersionFormatter(
+ stringProvider = FakeStringProvider(defaultResult = VERSION),
+ buildMeta = aBuildMeta(
+ gitBranchName = "branch",
+ gitRevision = "1234567890",
+ )
+ )
+ assertThat(sut.get()).isEqualTo("$VERSION\nbranch (1234567890)")
+ }
+
+ companion object {
+ const val VERSION = "version"
+ }
+}
diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt
index cebc94f31d..758c153671 100644
--- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt
+++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt
@@ -31,5 +31,6 @@ interface BugReportEntryPoint : FeatureEntryPoint {
interface Callback : Plugin {
fun onBugReportSent()
+ fun onViewLogs(basePath: String)
}
}
diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt
index 996392d879..e076ceb1a6 100644
--- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt
+++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt
@@ -18,6 +18,8 @@ package io.element.android.features.rageshake.api.detection
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
@@ -73,9 +75,10 @@ private fun TakeScreenshot(
onScreenshotTaken: (ImageResult) -> Unit
) {
val view = LocalView.current
+ val latestOnScreenshotTaken by rememberUpdatedState(onScreenshotTaken)
LaunchedEffect(Unit) {
view.screenshot {
- onScreenshotTaken(it)
+ latestOnScreenshotTaken(it)
}
}
}
diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt
index d50ce28778..d8e05f947b 100644
--- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt
+++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt
@@ -52,4 +52,9 @@ interface BugReporter {
* Set the current tracing filter.
*/
fun setCurrentTracingFilter(tracingFilter: String)
+
+ /**
+ * Save the logcat.
+ */
+ fun saveLogCat()
}
diff --git a/features/rageshake/api/src/main/res/values-hu/translations.xml b/features/rageshake/api/src/main/res/values-hu/translations.xml
index 559594ed05..5b228df7a0 100644
--- a/features/rageshake/api/src/main/res/values-hu/translations.xml
+++ b/features/rageshake/api/src/main/res/values-hu/translations.xml
@@ -1,7 +1,7 @@
- "Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?"
- "Úgy tűnik, mintha mérgében a telefont rázná. Megnyitja a hibajelentési képernyőt?"
+ "Az %1$s összeomlott a legutóbbi használata óta. Megosztod velünk az összeomlás-jelentést?"
+ "Úgy tűnik, mintha dühösen ráznád a telefont. Megnyitod a hibajelentési képernyőt?"
"Ideges rázás"
"Észlelési küszöb"
diff --git a/features/rageshake/api/src/main/res/values-ru/translations.xml b/features/rageshake/api/src/main/res/values-ru/translations.xml
index 24ba792de6..f8fd569add 100644
--- a/features/rageshake/api/src/main/res/values-ru/translations.xml
+++ b/features/rageshake/api/src/main/res/values-ru/translations.xml
@@ -2,6 +2,6 @@
"При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом о сбое?"
"Похоже, что вы трясете телефон. Хотите открыть экран сообщения об ошибке?"
- "Rageshake"
+ "Встряхните"
"Порог обнаружения"
diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt
index 90a81e279d..caed077228 100644
--- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt
+++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt
@@ -28,6 +28,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
+import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.androidutils.system.toast
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.ui.strings.CommonStrings
@@ -37,7 +38,12 @@ class BugReportNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List,
private val presenter: BugReportPresenter,
+ private val bugReporter: BugReporter,
) : Node(buildContext, plugins = plugins) {
+ private fun onViewLogs(basePath: String) {
+ plugins().forEach { it.onViewLogs(basePath) }
+ }
+
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
@@ -50,6 +56,11 @@ class BugReportNode @AssistedInject constructor(
activity?.toast(CommonStrings.common_report_submitted)
onDone()
},
+ onViewLogs = {
+ // Force a logcat dump
+ bugReporter.saveLogCat()
+ onViewLogs(bugReporter.logDirectory().absolutePath)
+ }
)
}
diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt
index 11843553e8..e10e9250e3 100644
--- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt
+++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt
@@ -40,9 +40,11 @@ import io.element.android.features.rageshake.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.form.textFieldState
+import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
+import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
@@ -55,6 +57,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun BugReportView(
state: BugReportState,
+ onViewLogs: () -> Unit,
onDone: () -> Unit,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
@@ -97,6 +100,13 @@ fun BugReportView(
)
}
Spacer(modifier = Modifier.height(16.dp))
+ PreferenceDivider()
+ PreferenceText(
+ title = stringResource(id = R.string.screen_bug_report_view_logs),
+ enabled = isFormEnabled,
+ onClick = onViewLogs,
+ )
+ PreferenceDivider()
PreferenceSwitch(
isChecked = state.formState.sendLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
@@ -169,5 +179,6 @@ internal fun BugReportViewPreview(@PreviewParameter(BugReportStateProvider::clas
state = state,
onDone = {},
onBackPressed = {},
+ onViewLogs = {},
)
}
diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt
index 2fc834086a..bd76aa9650 100755
--- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt
+++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt
@@ -94,6 +94,8 @@ class DefaultBugReporter @Inject constructor(
private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*")
private var currentTracingFilter: String? = null
+ private val logCatErrFile = File(logDirectory().absolutePath, LOG_CAT_FILENAME)
+
override suspend fun sendBugReport(
withDevicesLogs: Boolean,
withCrashLogs: Boolean,
@@ -130,8 +132,8 @@ class DefaultBugReporter @Inject constructor(
}
if (!isCancelled && (withCrashLogs || withDevicesLogs)) {
- val gzippedLogcat = saveLogCat()
-
+ saveLogCat()
+ val gzippedLogcat = compressFile(logCatErrFile)
if (null != gzippedLogcat) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(gzippedLogcat)
@@ -199,6 +201,9 @@ class DefaultBugReporter @Inject constructor(
// add some github labels
builder.addFormDataPart("label", buildMeta.versionName)
+ builder.addFormDataPart("label", buildMeta.flavorDescription)
+ builder.addFormDataPart("branch_name", buildMeta.gitBranchName)
+
if (crashCallStack.isNotEmpty() && withCrashLogs) {
builder.addFormDataPart("label", "crash")
}
@@ -320,7 +325,9 @@ class DefaultBugReporter @Inject constructor(
}
override fun logDirectory(): File {
- return File(context.cacheDir, LOG_DIRECTORY_NAME)
+ return File(context.cacheDir, LOG_DIRECTORY_NAME).apply {
+ mkdirs()
+ }
}
override fun cleanLogDirectoryIfNeeded() {
@@ -380,30 +387,19 @@ class DefaultBugReporter @Inject constructor(
*
* @return the file if the operation succeeds
*/
- private fun saveLogCat(): File? {
- val logCatErrFile = File(context.cacheDir.absolutePath, LOG_CAT_FILENAME)
-
+ override fun saveLogCat() {
if (logCatErrFile.exists()) {
logCatErrFile.safeDelete()
}
-
try {
logCatErrFile.writer().use {
getLogCatError(it)
}
-
- return compressFile(logCatErrFile)
} catch (error: OutOfMemoryError) {
Timber.e(error, "## saveLogCat() : fail to write logcat OOM")
} catch (e: Exception) {
Timber.e(e, "## saveLogCat() : fail to write logcat")
- } finally {
- if (logCatErrFile.exists()) {
- logCatErrFile.safeDelete()
- }
}
-
- return null
}
/**
diff --git a/features/rageshake/impl/src/main/res/values-cs/translations.xml b/features/rageshake/impl/src/main/res/values-cs/translations.xml
index bfea484240..824819e5ee 100644
--- a/features/rageshake/impl/src/main/res/values-cs/translations.xml
+++ b/features/rageshake/impl/src/main/res/values-cs/translations.xml
@@ -11,5 +11,6 @@
"Povolit protokoly"
"Odeslat snímek obrazovky"
"Protokoly budou součástí vaší zprávy, aby se zajistilo že vše funguje správně. Chcete-li odeslat zprávu bez protokolů, vypněte toto nastavení."
+ "Zobrazit protokoly"
"%1$s havaroval při posledním použití. Chcete se s námi podělit o zprávu o selhání?"
diff --git a/features/rageshake/impl/src/main/res/values-hu/translations.xml b/features/rageshake/impl/src/main/res/values-hu/translations.xml
index bc09e541ab..85f22f8449 100644
--- a/features/rageshake/impl/src/main/res/values-hu/translations.xml
+++ b/features/rageshake/impl/src/main/res/values-hu/translations.xml
@@ -11,5 +11,5 @@
"Naplók engedélyezése"
"Képernyőkép küldése"
"A naplók szerepelni fognak az üzenetben, hogy megbizonyosodhassunk arról, hogy minden megfelelően működik-e. Ha naplók nélkül szeretné elküldeni az üzenetet, akkor kapcsolja ki ezt a beállítást."
- "Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?"
+ "Az %1$s összeomlott a legutóbbi használata óta. Megosztod velünk az összeomlás-jelentést?"
diff --git a/features/rageshake/impl/src/main/res/values-it/translations.xml b/features/rageshake/impl/src/main/res/values-it/translations.xml
index 2c95849db0..852a8f68ff 100644
--- a/features/rageshake/impl/src/main/res/values-it/translations.xml
+++ b/features/rageshake/impl/src/main/res/values-it/translations.xml
@@ -1,14 +1,15 @@
"Allega istantanea schermo"
- "Potete contattarmi per qualsiasi altra domanda"
+ "Potete contattarmi per qualsiasi altra domanda."
+ "Contattami"
"Modifica istantanea schermo"
- "Descrivi il bug. Che cosa hai fatto? Cosa ti aspettavi che accadesse? Cosa è effettivamente accaduto. Si prega di inserire il maggior numero di dettagli possibile."
+ "Descrivi il problema. Che cosa hai fatto? Cosa ti aspettavi che accadesse? Cosa è effettivamente accaduto. Si prega di inserire il maggior numero di dettagli possibile."
"Descrivi il problema…"
"Se possibile, scrivere la descrizione in inglese."
"Invia i log degli arresti anomali"
- "Invia i log per aiutarci"
+ "Consenti i log"
"Invia istantanea schermo"
- "Per verificare che le cose funzionino come previsto, i log verranno inviati con il tuo messaggio. Questi saranno privati. Per inviare solo il tuo messaggio, disattiva questa impostazione."
+ "Per verificare che le cose funzionino come previsto, i log verranno inviati con il tuo messaggio. Per inviare solo il tuo messaggio, disattiva questa impostazione."
"%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?"
diff --git a/features/rageshake/impl/src/main/res/values-ru/translations.xml b/features/rageshake/impl/src/main/res/values-ru/translations.xml
index c22a34bf30..7c0ea079a3 100644
--- a/features/rageshake/impl/src/main/res/values-ru/translations.xml
+++ b/features/rageshake/impl/src/main/res/values-ru/translations.xml
@@ -11,5 +11,6 @@
"Разрешить ведение журналов"
"Отправить снимок экрана"
"Чтобы убедиться, что все работает правильно, в сообщение будут включены журналы. Чтобы отправить сообщение без журналов, отключите эту настройку."
+ "Просмотр журналов"
"При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом о сбое?"
diff --git a/features/rageshake/impl/src/main/res/values-sk/translations.xml b/features/rageshake/impl/src/main/res/values-sk/translations.xml
index 51222367a6..8e94848650 100644
--- a/features/rageshake/impl/src/main/res/values-sk/translations.xml
+++ b/features/rageshake/impl/src/main/res/values-sk/translations.xml
@@ -11,5 +11,6 @@
"Povoliť záznamy"
"Odoslať snímku obrazovky"
"K vašej správe budú priložené záznamy o chybe, aby sme sa uistili, že všetko funguje správne. Ak chcete odoslať správu bez záznamov o chybe, vypnite toto nastavenie."
+ "Zobraziť záznamy"
"%1$s zlyhal pri poslednom použití. Chcete zdieľať správu o páde s našim tímom?"
diff --git a/features/rageshake/impl/src/main/res/values/localazy.xml b/features/rageshake/impl/src/main/res/values/localazy.xml
index 34ba8b5b30..83413c3919 100644
--- a/features/rageshake/impl/src/main/res/values/localazy.xml
+++ b/features/rageshake/impl/src/main/res/values/localazy.xml
@@ -11,5 +11,6 @@
"Allow logs"
"Send screenshot"
"Logs will be included with your message to make sure that everything is working properly. To send your message without logs, turn off this setting."
+ "View logs"
"%1$s crashed the last time it was used. Would you like to share a crash report with us?"
diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt
index 2cd9e61398..0a67a79f57 100644
--- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt
+++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt
@@ -63,6 +63,10 @@ class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Succes
override fun setCurrentTracingFilter(tracingFilter: String) {
// No op
}
+
+ override fun saveLogCat() {
+ // No op
+ }
}
enum class FakeBugReporterMode {
diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt
index 44e085c569..91b5c016da 100755
--- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt
+++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt
@@ -48,7 +48,7 @@ class DefaultBugReporterTest {
val sut = createDefaultBugReporter(server)
var onUploadCancelledCalled = false
var onUploadFailedCalled = false
- var progressValues = mutableListOf()
+ val progressValues = mutableListOf()
var onUploadSucceedCalled = false
sut.sendBugReport(
withDevicesLogs = true,
@@ -80,7 +80,7 @@ class DefaultBugReporterTest {
server.shutdown()
assertThat(onUploadCancelledCalled).isFalse()
assertThat(onUploadFailedCalled).isFalse()
- assertThat(progressValues.size).isEqualTo(10)
+ assertThat(progressValues.size).isEqualTo(EXPECTED_NUMBER_OF_PROGRESS_VALUE)
assertThat(onUploadSucceedCalled).isTrue()
}
@@ -97,7 +97,7 @@ class DefaultBugReporterTest {
var onUploadCancelledCalled = false
var onUploadFailedCalled = false
var onUploadFailedReason: String? = null
- var progressValues = mutableListOf()
+ val progressValues = mutableListOf()
var onUploadSucceedCalled = false
sut.sendBugReport(
withDevicesLogs = true,
@@ -131,7 +131,7 @@ class DefaultBugReporterTest {
assertThat(onUploadCancelledCalled).isFalse()
assertThat(onUploadFailedCalled).isTrue()
assertThat(onUploadFailedReason).isEqualTo("An error body")
- assertThat(progressValues.size).isEqualTo(10)
+ assertThat(progressValues.size).isEqualTo(EXPECTED_NUMBER_OF_PROGRESS_VALUE)
assertThat(onUploadSucceedCalled).isFalse()
}
@@ -153,4 +153,8 @@ class DefaultBugReporterTest {
bugReporterUrlProvider = { server.url("/") }
)
}
+
+ companion object {
+ private const val EXPECTED_NUMBER_OF_PROGRESS_VALUE = 12
+ }
}
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 e9aebe104b..a232d4e87e 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
@@ -26,11 +26,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.lifecycle.Lifecycle
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClient
@@ -63,6 +65,12 @@ class RoomDetailsPresenter @Inject constructor(
val scope = rememberCoroutineScope()
val leaveRoomState = leaveRoomPresenter.present()
val canShowNotificationSettings = remember { mutableStateOf(false) }
+ val roomInfo = room.roomInfoFlow.collectAsState(initial = null).value
+
+ val roomAvatar by remember { derivedStateOf { roomInfo?.avatarUrl ?: room.avatarUrl } }
+
+ val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.name ?: room.displayName).trim() } }
+ val roomTopic by remember { derivedStateOf { roomInfo?.topic ?: room.topic } }
LaunchedEffect(Unit) {
canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
@@ -70,7 +78,13 @@ class RoomDetailsPresenter @Inject constructor(
room.updateRoomNotificationSettings()
observeNotificationSettings()
}
- room.updateMembers()
+ }
+
+ // Update room members only when first presenting the node
+ OnLifecycleEvent { _, event ->
+ if (event == Lifecycle.Event.ON_CREATE) {
+ scope.launch { room.updateMembers() }
+ }
}
val membersState by room.membersStateFlow.collectAsState()
@@ -82,8 +96,8 @@ class RoomDetailsPresenter @Inject constructor(
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType by getRoomType(dmMember)
- val topicState = remember(canEditTopic, room.topic, roomType) {
- val topic = room.topic
+ val topicState = remember(canEditTopic, roomTopic, roomType) {
+ val topic = roomTopic
when {
!topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic)
@@ -115,9 +129,9 @@ class RoomDetailsPresenter @Inject constructor(
return RoomDetailsState(
roomId = room.roomId.value,
- roomName = room.displayName,
+ roomName = roomName,
roomAlias = room.alias,
- roomAvatarUrl = room.avatarUrl,
+ roomAvatarUrl = roomAvatar,
roomTopic = topicState,
memberCount = room.joinedMemberCount,
isEncrypted = room.isEncrypted,
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
index 2879995d82..bbdccbc302 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
@@ -190,9 +190,10 @@ fun RoomDetailsView(
BlockUserDialogs(roomMemberState)
}
- OtherActionsSection(onLeaveRoom = {
- state.eventSink(RoomDetailsEvent.LeaveRoom)
- })
+ OtherActionsSection(
+ isDm = state.roomType is RoomDetailsType.Dm,
+ onLeaveRoom = { state.eventSink(RoomDetailsEvent.LeaveRoom) }
+ )
}
}
}
@@ -410,10 +411,17 @@ private fun SecuritySection(modifier: Modifier = Modifier) {
}
@Composable
-private fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
+private fun OtherActionsSection(isDm: Boolean, onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(showDivider = false, modifier = modifier) {
ListItem(
- headlineContent = { Text(stringResource(R.string.screen_room_details_leave_room_title)) },
+ headlineContent = {
+ val leaveText = stringResource(id = if (isDm) {
+ R.string.screen_room_details_leave_conversation_title
+ } else {
+ R.string.screen_room_details_leave_room_title
+ })
+ Text(leaveText)
+ },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave)),
style = ListItemStyle.Destructive,
onClick = onLeaveRoom,
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt
index a66813a699..d51e7c0a47 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt
@@ -46,8 +46,8 @@ 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.user.MatrixUser
-import io.element.android.libraries.matrix.ui.components.CheckableUnresolvedUserRow
import io.element.android.libraries.matrix.ui.components.CheckableUserRow
+import io.element.android.libraries.matrix.ui.components.CheckableUserRowData
import io.element.android.libraries.matrix.ui.components.SelectedUsersList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
@@ -186,18 +186,16 @@ private fun RoomInviteMembersSearchBar(
LazyColumn {
itemsIndexed(results) { index, invitableUser ->
- if (invitableUser.isUnresolved && !invitableUser.isAlreadyInvited && !invitableUser.isAlreadyJoined) {
- CheckableUnresolvedUserRow(
- checked = invitableUser.isSelected,
+ val notInvitedOrJoined = !(invitableUser.isAlreadyInvited || invitableUser.isAlreadyJoined)
+ val isUnresolved = invitableUser.isUnresolved && notInvitedOrJoined
+ val enabled = isUnresolved || notInvitedOrJoined
+ val data = if (isUnresolved) {
+ CheckableUserRowData.Unresolved(
avatarData = invitableUser.matrixUser.getAvatarData(AvatarSize.UserListItem),
id = invitableUser.matrixUser.userId.value,
- onCheckedChange = { onUserToggled(invitableUser.matrixUser) },
- modifier = Modifier.fillMaxWidth()
)
} else {
- CheckableUserRow(
- checked = invitableUser.isSelected,
- enabled = !invitableUser.isAlreadyInvited && !invitableUser.isAlreadyJoined,
+ CheckableUserRowData.Resolved(
avatarData = invitableUser.matrixUser.getAvatarData(AvatarSize.UserListItem),
name = invitableUser.matrixUser.getBestName(),
subtext = when {
@@ -207,11 +205,16 @@ private fun RoomInviteMembersSearchBar(
// Otherwise show the ID, unless that's already used for their name
invitableUser.matrixUser.displayName.isNullOrEmpty().not() -> invitableUser.matrixUser.userId.value
else -> null
- },
- onCheckedChange = { onUserToggled(invitableUser.matrixUser) },
- modifier = Modifier.fillMaxWidth()
+ }
)
}
+ CheckableUserRow(
+ checked = invitableUser.isSelected,
+ enabled = enabled,
+ data = data,
+ onCheckedChange = { onUserToggled(invitableUser.matrixUser) },
+ modifier = Modifier.fillMaxWidth()
+ )
if (index < results.lastIndex) {
HorizontalDivider()
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt
index 867ecd991f..faf1f9fc78 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt
@@ -19,11 +19,8 @@ package io.element.android.features.roomdetails.impl.members
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
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.roomMembers
-import kotlinx.coroutines.flow.dropWhile
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -32,11 +29,8 @@ class RoomMemberListDataSource @Inject constructor(
private val coroutineDispatchers: CoroutineDispatchers,
) {
suspend fun search(query: String): List = withContext(coroutineDispatchers.io) {
- val roomMembers = room.membersStateFlow
- .dropWhile { it !is MatrixRoomMembersState.Ready }
- .first()
- .roomMembers()
- .orEmpty()
+ val roomMembersState = room.membersStateFlow.value
+ val roomMembers = roomMembersState.roomMembers().orEmpty()
val filteredMembers = if (query.isBlank()) {
roomMembers
} else {
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 6fc5b6ece7..399a2ad1e1 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
@@ -30,8 +30,10 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
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.RoomMembershipState
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
+import io.element.android.libraries.matrix.api.room.roomMembers
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -55,9 +57,12 @@ class RoomMemberListPresenter @Inject constructor(
value = room.canInvite().getOrElse { false }
}
- LaunchedEffect(Unit) {
+ LaunchedEffect(membersState) {
+ if (membersState is MatrixRoomMembersState.Unknown) {
+ return@LaunchedEffect
+ }
withContext(coroutineDispatchers.io) {
- val members = roomMemberListDataSource.search("").groupBy { it.membership }
+ val members = membersState.roomMembers().orEmpty().groupBy { it.membership }
roomMembers = AsyncData.Success(
RoomMembers(
invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml
index eddec59585..e6316cfec1 100644
--- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml
@@ -48,6 +48,7 @@
"Odblokovat"
"Znovu uvidíte všechny zprávy od nich."
"Odblokovat uživatele"
+ "Opustit konverzaci"
"Opustit místnost"
"Zabezpečení"
"Téma"
diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml
index 1611f89115..76e3ea81ec 100644
--- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml
@@ -47,6 +47,7 @@
"Débloquer"
"Vous pourrez à nouveau voir tous ses messages."
"Débloquer l’utilisateur"
+ "Quitter la discussion"
"Quitter le salon"
"Sécurité"
"Sujet"
diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml
index 3217efe862..664c2dbd85 100644
--- a/features/roomdetails/impl/src/main/res/values-it/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml
@@ -4,18 +4,52 @@
- "1 persona"
- "%1$d persone"
+ "Si è verificato un errore durante l\'aggiornamento delle impostazioni di notifica."
+ "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi potresti non ricevere notifiche in alcune stanze."
+ "Sondaggi"
+ "Aggiungi argomento"
+ "Già membro"
+ "Già invitato"
+ "Modifica stanza"
+ "Si è verificato un errore sconosciuto e non è stato possibile modificare le informazioni."
+ "Impossibile aggiornare la stanza"
"I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli."
"Crittografia messaggi abilitata"
+ "Si è verificato un errore durante il caricamento delle impostazioni di notifica."
+ "Impostazione del silenzioso fallita per questa stanza, riprova."
+ "Disattivazione del silenzioso di questa stanza fallita, riprova."
"Invita persone"
+ "Personalizzato"
+ "Predefinito"
+ "Notifiche"
+ "Nome stanza"
"Condividi stanza"
+ "Aggiornamento della stanza…"
+ "In attesa"
+ "Membri della stanza"
+ "Consenti impostazione personalizzata"
+ "L\'attivazione di questa opzione sovrascriverà l\'impostazione predefinita"
+ "Avvisami in questa chat per"
+ "Puoi cambiarlo nelle tue %1$s."
+ "impostazioni globali"
+ "Impostazione predefinita"
+ "Rimuovi l\'impostazione personalizzata"
+ "Si è verificato un errore durante il caricamento delle impostazioni di notifica."
+ "Ripristino della modalità predefinita fallito, riprova."
+ "Impossibile impostare la modalità, riprova."
+ "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi non riceverai notifiche in questa stanza."
+ "Tutti i messaggi"
+ "In questa stanza, avvisami per"
"Si è verificato un errore durante il tentativo di avviare una chat"
"Blocca"
- "Gli utenti bloccati non saranno in grado di inviarti nuovi messaggi e tutti quelli già esistenti saranno nascosti. Potrai annullare questa azione in qualsiasi momento."
+ "Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti quelli già ricevuti saranno nascosti. Puoi sbloccarli in qualsiasi momento."
"Blocca utente"
"Sblocca"
- "Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."
+ "Potrai vedere di nuovo tutti i suoi messaggi."
"Sblocca utente"
+ "Abbandona la conversazione"
"Esci dalla stanza"
"Sicurezza"
"Oggetto"
+ "Solo menzioni e parole chiave"
diff --git a/features/roomdetails/impl/src/main/res/values-ru/translations.xml b/features/roomdetails/impl/src/main/res/values-ru/translations.xml
index d83820dbbf..4412d7be4d 100644
--- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml
@@ -12,7 +12,7 @@
"Уже зарегистрирован"
"Уже приглашены"
"Редактировать комнату"
- "Произошла неизвестная ошибка, и информацию нельзя было изменить."
+ "Произошла неизвестная ошибка и информацию не удалось изменить."
"Не удалось обновить комнату"
"Сообщения зашифрованы. Только у вас и у получателей есть уникальные ключи для их разблокировки."
"Шифрование сообщений включено"
@@ -48,6 +48,7 @@
"Разблокировать"
"Вы снова сможете увидеть все сообщения."
"Разблокировать пользователя"
+ "Покинуть беседу"
"Покинуть комнату"
"Безопасность"
"Тема"
diff --git a/features/roomdetails/impl/src/main/res/values-sk/translations.xml b/features/roomdetails/impl/src/main/res/values-sk/translations.xml
index 74a489c720..c9afe72b80 100644
--- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml
@@ -48,6 +48,7 @@
"Odblokovať"
"Všetky správy od nich budete môcť opäť vidieť."
"Odblokovať používateľa"
+ "Opustiť konverzáciu"
"Opustiť miestnosť"
"Bezpečnosť"
"Téma"
diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml
index 13219abaf8..2693b40edb 100644
--- a/features/roomdetails/impl/src/main/res/values/localazy.xml
+++ b/features/roomdetails/impl/src/main/res/values/localazy.xml
@@ -47,6 +47,7 @@
"Unblock"
"You\'ll be able to see all messages from them again."
"Unblock user"
+ "Leave conversation"
"Leave room"
"Security"
"Topic"
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
index 0a25434339..299a8120a5 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
@@ -16,6 +16,7 @@
package io.element.android.features.roomdetails
+import androidx.lifecycle.Lifecycle
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
@@ -46,9 +47,12 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
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.aRoomInfo
+import io.element.android.tests.testutils.FakeLifecycleOwner
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.testCoroutineDispatchers
+import io.element.android.tests.testutils.withFakeLifecycleOwner
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -62,6 +66,10 @@ class RoomDetailsPresenterTests {
@get:Rule
val warmUpRule = WarmUpRule()
+ private val fakeLifecycleOwner = FakeLifecycleOwner().apply {
+ givenState(Lifecycle.State.RESUMED)
+ }
+
private fun TestScope.createRoomDetailsPresenter(
room: MatrixRoom,
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
@@ -89,15 +97,17 @@ class RoomDetailsPresenterTests {
}
@Test
- fun `present - initial state is created from room info`() = runTest {
+ fun `present - initial state is created from room if roomInfo is null`() = runTest {
val room = aMatrixRoom()
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
val initialState = awaitItem()
assertThat(initialState.roomId).isEqualTo(room.roomId.value)
- assertThat(initialState.roomName).isEqualTo(room.displayName)
+ assertThat(initialState.roomName).isEqualTo(room.name)
assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl)
assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!))
assertThat(initialState.memberCount).isEqualTo(room.joinedMemberCount)
@@ -107,12 +117,36 @@ class RoomDetailsPresenterTests {
}
}
+ @Test
+ fun `present - initial state is updated with roomInfo if it exists`() = runTest {
+ val roomInfo = aRoomInfo(name = "A room name", topic = "A topic", avatarUrl = "https://matrix.org/avatar.jpg")
+ val room = aMatrixRoom().apply {
+ givenRoomInfo(roomInfo)
+ }
+ val presenter = createRoomDetailsPresenter(room)
+ moleculeFlow(RecompositionMode.Immediate) {
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
+ }.test {
+ skipItems(1)
+ val updatedState = awaitItem()
+ assertThat(updatedState.roomName).isEqualTo(roomInfo.name)
+ assertThat(updatedState.roomAvatarUrl).isEqualTo(roomInfo.avatarUrl)
+ assertThat(updatedState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(roomInfo.topic!!))
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
@Test
fun `present - initial state with no room name`() = runTest {
val room = aMatrixRoom(name = null)
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
val initialState = awaitItem()
assertThat(initialState.roomName).isEqualTo(room.displayName)
@@ -134,7 +168,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
val initialState = awaitItem()
assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
@@ -150,7 +186,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// Initially false
assertThat(awaitItem().canInvite).isFalse()
@@ -168,7 +206,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
assertThat(awaitItem().canInvite).isFalse()
@@ -183,7 +223,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
assertThat(awaitItem().canInvite).isFalse()
@@ -201,7 +243,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// Initially false
assertThat(awaitItem().canEdit).isFalse()
@@ -230,7 +274,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// Initially false
assertThat(awaitItem().canEdit).isFalse()
@@ -260,7 +306,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
skipItems(1)
@@ -281,7 +329,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// Initially false
assertThat(awaitItem().canEdit).isFalse()
@@ -302,7 +352,9 @@ class RoomDetailsPresenterTests {
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// Initially false, and no further events
assertThat(awaitItem().canEdit).isFalse()
@@ -320,7 +372,9 @@ class RoomDetailsPresenterTests {
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// The initial state is "hidden" and no further state changes happen
assertThat(awaitItem().roomTopic).isEqualTo(RoomTopicState.Hidden)
@@ -334,11 +388,14 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom(topic = null).apply {
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
givenCanInviteResult(Result.success(false))
+ givenRoomInfo(aRoomInfo(topic = null))
}
val presenter = createRoomDetailsPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
// Ignore the initial state
skipItems(1)
@@ -360,7 +417,9 @@ class RoomDetailsPresenterTests {
dispatchers = testCoroutineDispatchers()
)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
@@ -381,7 +440,9 @@ class RoomDetailsPresenterTests {
notificationSettingsService = notificationSettingsService,
)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
val updatedState = consumeItemsUntilPredicate {
@@ -398,7 +459,9 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
awaitItem().eventSink(RoomDetailsEvent.MuteNotification)
val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) {
@@ -418,7 +481,9 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
+ withFakeLifecycleOwner(fakeLifecycleOwner) {
+ presenter.present()
+ }
}.test {
awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification)
val updatedState = consumeItemsUntilPredicate {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt
index a869bb5e04..88e9c7df86 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt
@@ -47,15 +47,20 @@ class RoomMemberListPresenterTests {
@Test
fun `search is done automatically on start, but is async`() = runTest {
- val presenter = createPresenter()
+ val room = FakeMatrixRoom()
+ val presenter = createPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
+ skipItems(1)
val initialState = awaitItem()
assertThat(initialState.roomMembers).isInstanceOf(AsyncData.Loading::class.java)
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(initialState.isSearchActive).isFalse()
+ room.givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
+ // Skip item while the new members state is processed
+ skipItems(1)
val loadedState = awaitItem()
assertThat(loadedState.roomMembers).isInstanceOf(AsyncData.Success::class.java)
assertThat((loadedState.roomMembers as AsyncData.Success).data.invited).isEqualTo(listOf(aVictor(), aWalter()))
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 04eb6164a4..1d2cf2c910 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
@@ -95,7 +95,14 @@ private fun RoomListModalBottomSheetContent(
style = ListItemStyle.Primary,
)
ListItem(
- headlineContent = { Text(text = stringResource(id = CommonStrings.action_leave_room)) },
+ headlineContent = {
+ val leaveText = stringResource(id = if (contextMenu.isDm) {
+ CommonStrings.action_leave_conversation
+ } else {
+ CommonStrings.action_leave_room
+ })
+ Text(text = leaveText)
+ },
modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) },
leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector(
@@ -117,7 +124,22 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
RoomListModalBottomSheetContent(
contextMenu = RoomListState.ContextMenu.Shown(
roomId = RoomId(value = "!aRoom:aDomain"),
- roomName = "aRoom"
+ roomName = "aRoom",
+ isDm = false,
+ ),
+ onRoomSettingsClicked = {},
+ onLeaveRoomClicked = {}
+ )
+}
+
+@PreviewsDayNight
+@Composable
+internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview {
+ RoomListModalBottomSheetContent(
+ contextMenu = RoomListState.ContextMenu.Shown(
+ roomId = RoomId(value = "!aRoom:aDomain"),
+ roomName = "aRoom",
+ isDm = true,
),
onRoomSettingsClicked = {},
onLeaveRoomClicked = {}
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 05d4d93146..8ab1bde3f4 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
@@ -119,7 +119,8 @@ class RoomListPresenter @Inject constructor(
is RoomListEvents.ShowContextMenu -> {
contextMenu = RoomListState.ContextMenu.Shown(
roomId = event.roomListRoomSummary.roomId,
- roomName = event.roomListRoomSummary.name
+ roomName = event.roomListRoomSummary.name,
+ isDm = event.roomListRoomSummary.isDm,
)
}
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
index b0c87b88aa..7003da36e5 100644
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
+++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
@@ -46,6 +46,7 @@ data class RoomListState(
data class Shown(
val roomId: RoomId,
val roomName: String,
+ val isDm: Boolean,
) : ContextMenu
}
}
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
index 406c28592a..b1e43f147e 100644
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
+++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
@@ -19,7 +19,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
-import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
+import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
@@ -44,7 +44,8 @@ open class RoomListStateProvider : PreviewParameterProvider {
aRoomListState().copy(
contextMenu = RoomListState.ContextMenu.Shown(
roomId = RoomId("!aRoom:aDomain"),
- roomName = "A nice room name"
+ roomName = "A nice room name",
+ isDm = false,
)
),
aRoomListState().copy(displayRecoveryKeyPrompt = true),
@@ -70,25 +71,29 @@ internal fun aRoomListState() = RoomListState(
internal fun aRoomListRoomSummaryList(): ImmutableList {
return persistentListOf(
- RoomListRoomSummary(
+ aRoomListRoomSummary(
name = "Room",
- hasUnread = true,
+ numberOfUnreadMessages = 1,
timestamp = "14:18",
lastMessage = "A very very very very long message which suites on two lines",
avatarData = AvatarData("!id", "R", size = AvatarSize.RoomListItem),
id = "!roomId:domain",
- roomId = RoomId("!roomId:domain")
),
- RoomListRoomSummary(
+ aRoomListRoomSummary(
name = "Room#2",
- hasUnread = false,
+ numberOfUnreadMessages = 0,
timestamp = "14:16",
lastMessage = "A short message",
avatarData = AvatarData("!id", "Z", size = AvatarSize.RoomListItem),
id = "!roomId2:domain",
- roomId = RoomId("!roomId2:domain")
),
- RoomListRoomSummaryPlaceholders.create("!roomId2:domain"),
- RoomListRoomSummaryPlaceholders.create("!roomId3:domain"),
+ aRoomListRoomSummary(
+ id = "!roomId3:domain",
+ isPlaceholder = true,
+ ),
+ aRoomListRoomSummary(
+ id = "!roomId4:domain",
+ isPlaceholder = true,
+ ),
)
}
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 effea847d0..7002d451ef 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
@@ -35,6 +35,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -141,7 +142,7 @@ private fun RowScope.NameAndTimestampRow(room: RoomListRoomSummary) {
Text(
text = room.timestamp ?: "",
style = ElementTheme.typography.fontBodySmMedium,
- color = if (room.hasUnread) {
+ color = if (room.isHighlighted) {
ElementTheme.colors.unreadIndicator
} else {
MaterialTheme.roomListRoomMessageDate()
@@ -165,49 +166,61 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) {
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
-
- // Unread
+ // Call and unread
Row(
modifier = Modifier.height(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- // Video call
- if (room.hasOngoingCall) {
- Icon(
- modifier = Modifier.size(16.dp),
- imageVector = CompoundIcons.VideoCallSolid,
- contentDescription = null,
- tint = ElementTheme.colors.unreadIndicator,
+ val tint = if (room.isHighlighted) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
+ if (room.hasRoomCall) {
+ OnGoingCallIcon(
+ color = tint,
)
}
- NotificationIcon(room)
- if (room.hasUnread) {
- UnreadIndicatorAtom()
+ if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
+ NotificationOffIndicatorAtom()
+ } else if (room.numberOfUnreadMentions > 0) {
+ MentionIndicatorAtom()
+ }
+ if (room.hasNewContent) {
+ UnreadIndicatorAtom(
+ color = tint
+ )
}
}
}
@Composable
-private fun NotificationIcon(room: RoomListRoomSummary) {
- val tint = if (room.hasUnread) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
- when (room.notificationMode) {
- null, RoomNotificationMode.ALL_MESSAGES -> return
- RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY ->
- Icon(
- modifier = Modifier.size(16.dp),
- contentDescription = null,
- imageVector = CompoundIcons.Mention,
- tint = tint,
- )
- RoomNotificationMode.MUTE ->
- Icon(
- modifier = Modifier.size(16.dp),
- contentDescription = null,
- imageVector = CompoundIcons.NotificationsSolidOff,
- tint = tint,
- )
- }
+private fun OnGoingCallIcon(
+ color: Color,
+) {
+ Icon(
+ modifier = Modifier.size(16.dp),
+ imageVector = CompoundIcons.VideoCallSolid,
+ contentDescription = null,
+ tint = color,
+ )
+}
+
+@Composable
+private fun NotificationOffIndicatorAtom() {
+ Icon(
+ modifier = Modifier.size(16.dp),
+ contentDescription = null,
+ imageVector = CompoundIcons.NotificationsSolidOff,
+ tint = ElementTheme.colors.iconQuaternary,
+ )
+}
+
+@Composable
+private fun MentionIndicatorAtom() {
+ Icon(
+ modifier = Modifier.size(16.dp),
+ contentDescription = null,
+ imageVector = CompoundIcons.Mention,
+ tint = ElementTheme.colors.unreadIndicator,
+ )
}
@PreviewsDayNight
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 e337fe5ad3..a255767d7e 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
@@ -17,16 +17,9 @@
package io.element.android.features.roomlist.impl.datasource
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
-import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
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.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.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
@@ -49,8 +42,7 @@ import kotlin.time.Duration.Companion.seconds
class RoomListDataSource @Inject constructor(
private val roomListService: RoomListService,
- private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
- private val roomLastMessageFormatter: RoomLastMessageFormatter,
+ private val roomListRoomSummaryFactory: RoomListRoomSummaryFactory,
private val coroutineDispatchers: CoroutineDispatchers,
private val notificationSettingsService: NotificationSettingsService,
private val appScope: CoroutineScope,
@@ -121,7 +113,7 @@ class RoomListDataSource @Inject constructor(
private suspend fun buildAndEmitAllRooms(roomSummaries: List) {
if (diffCache.isEmpty()) {
_allRooms.emit(
- RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList()
+ roomListRoomSummaryFactory.createFakeList()
)
} else {
val roomListRoomSummaries = ArrayList()
@@ -141,32 +133,10 @@ class RoomListDataSource @Inject constructor(
private fun buildAndCacheItem(roomSummaries: List, index: Int): RoomListRoomSummary? {
val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) {
- is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier)
- is RoomSummary.Filled -> {
- val avatarData = AvatarData(
- id = roomSummary.identifier(),
- name = roomSummary.details.name,
- url = roomSummary.details.avatarURLString,
- size = AvatarSize.RoomListItem,
- )
- val roomIdentifier = roomSummary.identifier()
- RoomListRoomSummary(
- id = roomSummary.identifier(),
- roomId = RoomId(roomIdentifier),
- name = roomSummary.details.name,
- hasUnread = roomSummary.details.unreadNotificationCount > 0,
- timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
- lastMessage = roomSummary.details.lastMessage?.let { message ->
- roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
- }.orEmpty(),
- avatarData = avatarData,
- notificationMode = roomSummary.details.notificationMode,
- hasOngoingCall = roomSummary.details.hasOngoingCall,
- )
- }
+ is RoomSummary.Empty -> roomListRoomSummaryFactory.createPlaceholder(roomSummary.identifier)
+ is RoomSummary.Filled -> roomListRoomSummaryFactory.create(roomSummary)
null -> null
}
-
diffCache[index] = roomListRoomSummary
return roomListRoomSummary
}
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
new file mode 100644
index 0000000000..9f1416c701
--- /dev/null
+++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.features.roomlist.impl.datasource
+
+import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
+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.roomlist.RoomSummary
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toImmutableList
+import javax.inject.Inject
+
+class RoomListRoomSummaryFactory @Inject constructor(
+ private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
+ private val roomLastMessageFormatter: RoomLastMessageFormatter,
+) {
+ fun createPlaceholder(id: String): RoomListRoomSummary {
+ return RoomListRoomSummary(
+ id = id,
+ roomId = RoomId("!aRoom:domain"),
+ isPlaceholder = true,
+ name = "Short name",
+ timestamp = "hh:mm",
+ lastMessage = "Last message for placeholder",
+ avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem),
+ numberOfUnreadMessages = 0,
+ numberOfUnreadMentions = 0,
+ numberOfUnreadNotifications = 0,
+ userDefinedNotificationMode = null,
+ hasRoomCall = false,
+ isDm = false,
+ )
+ }
+
+ fun createFakeList(): ImmutableList {
+ return List(16) {
+ createPlaceholder("!fakeRoom$it:domain")
+ }.toImmutableList()
+ }
+
+ fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary {
+ val roomIdentifier = roomSummary.identifier()
+ val avatarData = AvatarData(
+ id = roomIdentifier,
+ name = roomSummary.details.name,
+ url = roomSummary.details.avatarUrl,
+ size = AvatarSize.RoomListItem,
+ )
+ return RoomListRoomSummary(
+ id = roomIdentifier,
+ roomId = RoomId(roomIdentifier),
+ name = roomSummary.details.name,
+ numberOfUnreadMessages = roomSummary.details.numUnreadMessages,
+ numberOfUnreadMentions = roomSummary.details.numUnreadMentions,
+ numberOfUnreadNotifications = roomSummary.details.numUnreadNotifications,
+ timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
+ lastMessage = roomSummary.details.lastMessage?.let { message ->
+ roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
+ }.orEmpty(),
+ avatarData = avatarData,
+ isPlaceholder = false,
+ userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
+ hasRoomCall = roomSummary.details.hasRoomCall,
+ isDm = roomSummary.details.isDm,
+ )
+ }
+}
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt
index cfaf9b8321..26cd9e2d32 100644
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt
+++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt
@@ -18,7 +18,6 @@ package io.element.android.features.roomlist.impl.model
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData
-import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@@ -26,12 +25,22 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
data class RoomListRoomSummary(
val id: String,
val roomId: RoomId,
- val name: String = "",
- val hasUnread: Boolean = false,
- val timestamp: String? = null,
- val lastMessage: CharSequence? = null,
- val avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
- val isPlaceholder: Boolean = false,
- val notificationMode: RoomNotificationMode? = null,
- val hasOngoingCall: Boolean = false,
-)
+ val name: String,
+ val numberOfUnreadMessages: Int,
+ val numberOfUnreadMentions: Int,
+ val numberOfUnreadNotifications: Int,
+ val timestamp: String?,
+ val lastMessage: CharSequence?,
+ val avatarData: AvatarData,
+ val isPlaceholder: Boolean,
+ val userDefinedNotificationMode: RoomNotificationMode?,
+ val hasRoomCall: Boolean,
+ val isDm: Boolean,
+) {
+ val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
+ (numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0)
+
+ val hasNewContent = numberOfUnreadMessages > 0 ||
+ numberOfUnreadMentions > 0 ||
+ numberOfUnreadNotifications > 0
+}
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt
deleted file mode 100644
index f5976d7d1c..0000000000
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt
+++ /dev/null
@@ -1,43 +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.features.roomlist.impl.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.core.RoomId
-
-object RoomListRoomSummaryPlaceholders {
- fun create(id: String): RoomListRoomSummary {
- return RoomListRoomSummary(
- id = id,
- roomId = RoomId("!aRoom:domain"),
- isPlaceholder = true,
- name = "Short name",
- timestamp = "hh:mm",
- lastMessage = "Last message for placeholder",
- avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem)
- )
- }
-
- fun createFakeList(size: Int): List {
- return mutableListOf().apply {
- repeat(size) {
- add(create("!fakeRoom$it:domain"))
- }
- }
- }
-}
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 2649704427..e53e1f9072 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
@@ -25,32 +25,89 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
open class RoomListRoomSummaryProvider : PreviewParameterProvider {
override val values: Sequence
get() = sequenceOf(
- aRoomListRoomSummary(),
- aRoomListRoomSummary().copy(lastMessage = null),
- aRoomListRoomSummary().copy(hasUnread = true, notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
- aRoomListRoomSummary().copy(timestamp = "88:88", notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
- aRoomListRoomSummary().copy(timestamp = "88:88", notificationMode = RoomNotificationMode.MUTE),
- aRoomListRoomSummary().copy(timestamp = "88:88", hasUnread = true),
- aRoomListRoomSummary().copy(isPlaceholder = true, timestamp = "88:88"),
- aRoomListRoomSummary().copy(
- name = "A very long room name that should be truncated",
- lastMessage = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" +
- " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" +
- "modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
- timestamp = "yesterday",
- hasUnread = true,
+ listOf(
+ aRoomListRoomSummary(isPlaceholder = true),
+ aRoomListRoomSummary(),
+ aRoomListRoomSummary(lastMessage = null),
+ aRoomListRoomSummary(
+ name = "A very long room name that should be truncated",
+ lastMessage = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" +
+ " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" +
+ "modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
+ timestamp = "yesterday",
+ numberOfUnreadMessages = 1,
+ ),
),
- aRoomListRoomSummary().copy(hasUnread = true, hasOngoingCall = true),
- )
+ listOf(false, true).map { hasCall ->
+ listOf(
+ RoomNotificationMode.ALL_MESSAGES,
+ RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
+ RoomNotificationMode.MUTE,
+ ).map { roomNotificationMode ->
+ listOf(
+ aRoomListRoomSummary(
+ name = roomNotificationMode.name,
+ lastMessage = "No activity" + if (hasCall) ", call" else "",
+ notificationMode = roomNotificationMode,
+ numberOfUnreadMessages = 0,
+ numberOfUnreadMentions = 0,
+ hasRoomCall = hasCall,
+ ),
+ aRoomListRoomSummary(
+ name = roomNotificationMode.name,
+ lastMessage = "New messages" + if (hasCall) ", call" else "",
+ notificationMode = roomNotificationMode,
+ numberOfUnreadMessages = 1,
+ numberOfUnreadMentions = 0,
+ hasRoomCall = hasCall,
+ ),
+ aRoomListRoomSummary(
+ name = roomNotificationMode.name,
+ lastMessage = "New messages, mentions" + if (hasCall) ", call" else "",
+ notificationMode = roomNotificationMode,
+ numberOfUnreadMessages = 1,
+ numberOfUnreadMentions = 1,
+ hasRoomCall = hasCall,
+ ),
+ aRoomListRoomSummary(
+ name = roomNotificationMode.name,
+ lastMessage = "New mentions" + if (hasCall) ", call" else "",
+ notificationMode = roomNotificationMode,
+ numberOfUnreadMessages = 0,
+ numberOfUnreadMentions = 1,
+ hasRoomCall = hasCall,
+ ),
+ )
+ }.flatten()
+ }.flatten(),
+ ).flatten()
}
-fun aRoomListRoomSummary() = RoomListRoomSummary(
- id = "!roomId",
- roomId = RoomId("!roomId:domain"),
- name = "Room name",
- hasUnread = false,
- timestamp = null,
- lastMessage = "Last message",
- avatarData = AvatarData("!roomId", "Room name", size = AvatarSize.RoomListItem),
- isPlaceholder = false,
+internal fun aRoomListRoomSummary(
+ id: String = "!roomId:domain",
+ name: String = "Room name",
+ numberOfUnreadMessages: Int = 0,
+ numberOfUnreadMentions: Int = 0,
+ numberOfUnreadNotifications: Int = 0,
+ lastMessage: String? = "Last message",
+ timestamp: String? = lastMessage?.let { "88:88" },
+ isPlaceholder: Boolean = false,
+ notificationMode: RoomNotificationMode? = null,
+ hasRoomCall: Boolean = false,
+ avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
+ isDm: Boolean = false,
+) = RoomListRoomSummary(
+ id = id,
+ roomId = RoomId(id),
+ name = name,
+ numberOfUnreadMessages = numberOfUnreadMessages,
+ numberOfUnreadMentions = numberOfUnreadMentions,
+ numberOfUnreadNotifications = numberOfUnreadNotifications,
+ timestamp = timestamp,
+ lastMessage = lastMessage,
+ avatarData = avatarData,
+ isPlaceholder = isPlaceholder,
+ userDefinedNotificationMode = notificationMode,
+ hasRoomCall = hasRoomCall,
+ isDm = isDm,
)
diff --git a/features/roomlist/impl/src/main/res/values-cs/translations.xml b/features/roomlist/impl/src/main/res/values-cs/translations.xml
index 553aba069c..506c0c60b4 100644
--- a/features/roomlist/impl/src/main/res/values-cs/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-cs/translations.xml
@@ -5,6 +5,11 @@
"Vytvořte novou konverzaci nebo místnost"
"Začněte tím, že někomu pošnete zprávu."
"Zatím žádné konverzace."
+ "Oblíbené"
+ "Nízká priorita"
+ "Lidé"
+ "Místnosti"
+ "Nepřečtené"
"Všechny chaty"
"Zdá se, že používáte nové zařízení. Ověřte přihlášení, abyste měli přístup k zašifrovaným zprávám."
"Ověřte, že jste to vy"
diff --git a/features/roomlist/impl/src/main/res/values-fr/translations.xml b/features/roomlist/impl/src/main/res/values-fr/translations.xml
index 57ad02caca..70795bcd5f 100644
--- a/features/roomlist/impl/src/main/res/values-fr/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-fr/translations.xml
@@ -5,6 +5,11 @@
"Créer une nouvelle discussion ou un nouveau salon"
"Commencez par envoyer un message à quelqu’un."
"Aucune discussion pour le moment."
+ "Favoris"
+ "Priorité basse"
+ "Discussions"
+ "Salons"
+ "Non-lus"
"Conversations"
"Il semblerait que vous utilisiez un nouvel appareil. Vérifiez la session avec un autre de vos appareils pour accéder à vos messages chiffrés."
"Vérifier que c’est bien vous"
diff --git a/features/roomlist/impl/src/main/res/values-hu/translations.xml b/features/roomlist/impl/src/main/res/values-hu/translations.xml
index cd5dced50a..21c3e645a3 100644
--- a/features/roomlist/impl/src/main/res/values-hu/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-hu/translations.xml
@@ -1,6 +1,6 @@
- "A csevegés biztonsági mentés nincs szinkronban. Meg kell erősítenie a helyreállítási kulcsát, hogy továbbra is hozzáférjen a csevegés biztonsági mentéséhez."
+ "A csevegés biztonsági mentése nincs szinkronban. Meg kell erősítened a helyreállítási kulcsát, hogy továbbra is hozzáférj a csevegés biztonsági mentéséhez."
"Helyreállítási kulcs megerősítése"
"Új beszélgetés vagy szoba létrehozása"
"Kezdje azzal, hogy üzenetet küld valakinek."
diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml
index cbe93e52d9..2d186453a9 100644
--- a/features/roomlist/impl/src/main/res/values-it/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-it/translations.xml
@@ -1,7 +1,11 @@
+ "Il backup della chat non è attualmente sincronizzato. Devi confermare la chiave di recupero per mantenere l\'accesso al backup della chat."
+ "Conferma la chiave di recupero"
"Crea una nuova conversazione o stanza"
+ "Inizia inviando un messaggio a qualcuno."
+ "Ancora nessuna chat."
"Tutte le conversazioni"
- "Sembra che tu stia utilizzando un nuovo dispositivo. Verifica di essere tu per accedere ai tuoi messaggi crittografati."
- "Accedi alla cronologia dei messaggi"
+ "Sembra che tu stia usando un nuovo dispositivo. Verificati con un altro dispositivo per accedere ai tuoi messaggi cifrati."
+ "Verifica che sei tu"
diff --git a/features/roomlist/impl/src/main/res/values-ru/translations.xml b/features/roomlist/impl/src/main/res/values-ru/translations.xml
index caba3f6dfe..d752ef073a 100644
--- a/features/roomlist/impl/src/main/res/values-ru/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-ru/translations.xml
@@ -5,7 +5,12 @@
"Создайте новую беседу или комнату"
"Начните переписку с отправки сообщения."
"Пока нет доступных чатов."
+ "Избранное"
+ "Низкий приоритет"
+ "Люди"
+ "Комнаты"
+ "Непрочитанные"
"Все чаты"
- "Похоже, вы используете новое устройство. Чтобы получить доступ к зашифрованным сообщениям в дальнейшем, проверьте их на другом устройстве."
+ "Похоже, вы используете новое устройство. Чтобы получить доступ к зашифрованным сообщениям пройдите верификацию с другим устройством."
"Подтвердите, что это вы"
diff --git a/features/roomlist/impl/src/main/res/values-sk/translations.xml b/features/roomlist/impl/src/main/res/values-sk/translations.xml
index b4a8e8690a..705d31cee3 100644
--- a/features/roomlist/impl/src/main/res/values-sk/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-sk/translations.xml
@@ -5,6 +5,11 @@
"Vytvorte novú konverzáciu alebo miestnosť"
"Začnite tým, že niekomu pošlete správu."
"Zatiaľ žiadne konverzácie."
+ "Obľúbené"
+ "Nízka priorita"
+ "Ľudia"
+ "Miestnosti"
+ "Neprečítané"
"Všetky konverzácie"
"Vyzerá to tak, že používate nové zariadenie. Overte svoj prístup k zašifrovaným správam pomocou vášho druhého zariadenia."
"Overte, že ste to vy"
diff --git a/features/roomlist/impl/src/main/res/values/localazy.xml b/features/roomlist/impl/src/main/res/values/localazy.xml
index 08a630c6b5..660017d240 100644
--- a/features/roomlist/impl/src/main/res/values/localazy.xml
+++ b/features/roomlist/impl/src/main/res/values/localazy.xml
@@ -5,7 +5,14 @@
"Create a new conversation or room"
"Get started by messaging someone."
"No chats yet."
+ "Favourites"
+ "Low Priority"
+ "People"
+ "Rooms"
+ "Unreads"
"All Chats"
+ "Mark as read"
+ "Mark as unread"
"Looks like you’re using a new device. Verify with another device to access your encrypted messages."
"Verify it’s you"
diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
index f3b678dd35..839fa04cf7 100644
--- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
+++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
@@ -28,8 +28,8 @@ import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
+import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
-import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@@ -122,7 +122,7 @@ class RoomListPresenterTests {
fun `present - should start with no user and then load user with error`() = runTest {
val matrixClient = FakeMatrixClient(
userDisplayName = Result.failure(AN_EXCEPTION),
- userAvatarURLString = Result.failure(AN_EXCEPTION),
+ userAvatarUrl = Result.failure(AN_EXCEPTION),
)
val scope = CoroutineScope(coroutineContext + SupervisorJob())
val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope)
@@ -316,12 +316,12 @@ class RoomListPresenterTests {
skipItems(1)
val initialState = awaitItem()
- val summary = aRoomListRoomSummary()
+ val summary = aRoomListRoomSummary
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
val shownState = awaitItem()
assertThat(shownState.contextMenu)
- .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name))
+ .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
scope.cancel()
}
}
@@ -336,12 +336,12 @@ class RoomListPresenterTests {
skipItems(1)
val initialState = awaitItem()
- val summary = aRoomListRoomSummary()
+ val summary = aRoomListRoomSummary
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
val shownState = awaitItem()
assertThat(shownState.contextMenu)
- .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name))
+ .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
shownState.eventSink(RoomListEvents.HideContextMenu)
val hiddenState = awaitItem()
@@ -384,11 +384,11 @@ class RoomListPresenterTests {
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode)
val updatedState = consumeItemsUntilPredicate { state ->
- state.roomList.any { it.id == A_ROOM_ID.value && it.notificationMode == userDefinedMode }
+ state.roomList.any { it.id == A_ROOM_ID.value && it.userDefinedNotificationMode == userDefinedMode }
}.last()
val room = updatedState.roomList.find { it.id == A_ROOM_ID.value }
- assertThat(room?.notificationMode).isEqualTo(userDefinedMode)
+ assertThat(room?.userDefinedNotificationMode).isEqualTo(userDefinedMode)
cancelAndIgnoreRemainingEvents()
scope.cancel()
}
@@ -415,9 +415,11 @@ class RoomListPresenterTests {
inviteStateDataSource = inviteStateDataSource,
leaveRoomPresenter = leaveRoomPresenter,
roomListDataSource = RoomListDataSource(
- client.roomListService,
- lastMessageTimestampFormatter,
- roomLastMessageFormatter,
+ roomListService = client.roomListService,
+ roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
+ lastMessageTimestampFormatter = lastMessageTimestampFormatter,
+ roomLastMessageFormatter = roomLastMessageFormatter,
+ ),
coroutineDispatchers = testCoroutineDispatchers(),
notificationSettingsService = client.notificationSettingsService(),
appScope = coroutineScope
@@ -438,9 +440,14 @@ private val aRoomListRoomSummary = RoomListRoomSummary(
id = A_ROOM_ID.value,
roomId = A_ROOM_ID,
name = A_ROOM_NAME,
- hasUnread = true,
+ numberOfUnreadMentions = 1,
+ numberOfUnreadMessages = 2,
+ numberOfUnreadNotifications = 0,
timestamp = A_FORMATTED_DATE,
lastMessage = "",
avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem),
isPlaceholder = false,
+ userDefinedNotificationMode = null,
+ hasRoomCall = false,
+ isDm = false,
)
diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt
index c12ea2d24e..73d8058036 100644
--- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt
+++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt
@@ -22,7 +22,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.securebackup.impl.loggerTagDisable
-import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.encryption.EncryptionService
@@ -36,14 +36,14 @@ class SecureBackupEnablePresenter @Inject constructor(
) : Presenter {
@Composable
override fun present(): SecureBackupEnableState {
- val enableAction = remember { mutableStateOf>(AsyncData.Uninitialized) }
+ val enableAction = remember { mutableStateOf>(AsyncAction.Uninitialized) }
val coroutineScope = rememberCoroutineScope()
fun handleEvents(event: SecureBackupEnableEvents) {
when (event) {
is SecureBackupEnableEvents.EnableBackup ->
coroutineScope.enableBackup(enableAction)
SecureBackupEnableEvents.DismissDialog -> {
- enableAction.value = AsyncData.Uninitialized
+ enableAction.value = AsyncAction.Uninitialized
}
}
}
@@ -54,7 +54,7 @@ class SecureBackupEnablePresenter @Inject constructor(
)
}
- private fun CoroutineScope.enableBackup(action: MutableState>) = launch {
+ private fun CoroutineScope.enableBackup(action: MutableState>) = launch {
suspend {
Timber.tag(loggerTagDisable.value).d("Calling encryptionService.enableBackups()")
encryptionService.enableBackups().getOrThrow()
diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt
index bd81004f9c..7c38501bf0 100644
--- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt
+++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt
@@ -16,9 +16,9 @@
package io.element.android.features.securebackup.impl.enable
-import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.architecture.AsyncAction
data class SecureBackupEnableState(
- val enableAction: AsyncData,
+ val enableAction: AsyncAction,
val eventSink: (SecureBackupEnableEvents) -> Unit
)
diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt
index 1029278c05..fc48036626 100644
--- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt
+++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt
@@ -17,20 +17,20 @@
package io.element.android.features.securebackup.impl.enable
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
-import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.architecture.AsyncAction
open class SecureBackupEnableStateProvider : PreviewParameterProvider {
override val values: Sequence
get() = sequenceOf(
aSecureBackupEnableState(),
- aSecureBackupEnableState(enableAction = AsyncData.Loading()),
- aSecureBackupEnableState(enableAction = AsyncData.Failure(Exception("Failed to enable"))),
+ aSecureBackupEnableState(enableAction = AsyncAction.Loading),
+ aSecureBackupEnableState(enableAction = AsyncAction.Failure(Exception("Failed to enable"))),
// Add other states here
)
}
fun aSecureBackupEnableState(
- enableAction: AsyncData = AsyncData.Uninitialized,
+ enableAction: AsyncAction = AsyncAction.Uninitialized,
) = SecureBackupEnableState(
enableAction = enableAction,
eventSink = {}
diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt
index 37a46a1408..db41289ecc 100644
--- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt
+++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt
@@ -19,16 +19,14 @@ package io.element.android.features.securebackup.impl.enable
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.securebackup.impl.R
-import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
-import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
+import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
@@ -41,11 +39,6 @@ fun SecureBackupEnableView(
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
- LaunchedEffect(state.enableAction) {
- if (state.enableAction is AsyncData.Success) {
- onDone()
- }
- }
FlowStepPage(
modifier = modifier,
onBackClicked = onBackClicked,
@@ -53,12 +46,12 @@ fun SecureBackupEnableView(
iconVector = ImageVector.vectorResource(CommonDrawables.ic_key),
buttons = { Buttons(state = state) }
)
- if (state.enableAction is AsyncData.Failure) {
- ErrorDialog(
- content = state.enableAction.error.let { it.message ?: it.toString() },
- onDismiss = { state.eventSink.invoke(SecureBackupEnableEvents.DismissDialog) },
- )
- }
+ AsyncActionView(
+ async = state.enableAction,
+ progressDialog = { },
+ onSuccess = { onDone() },
+ onErrorDismiss = { state.eventSink.invoke(SecureBackupEnableEvents.DismissDialog) }
+ )
}
@Composable
diff --git a/features/securebackup/impl/src/main/res/values-it/translations.xml b/features/securebackup/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..2670c5b052
--- /dev/null
+++ b/features/securebackup/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,44 @@
+
+
+ "Disattiva il backup"
+ "Attiva il backup"
+ "Il backup ti garantisce di non perdere la cronologia dei messaggi. %1$s."
+ "Backup"
+ "Cambia la chiave di recupero"
+ "Conferma la chiave di recupero"
+ "Il backup della chat attualmente non è sincronizzato."
+ "Configura il recupero"
+ "Ottieni l\'accesso ai tuoi messaggi cifrati se perdi tutti i tuoi dispositivi o se sei disconnesso da %1$s ovunque."
+ "Disattiva"
+ "Perderai i tuoi messaggi cifrati se sei disconnesso da tutti i dispositivi."
+ "Vuoi davvero disattivare il backup?"
+ "La disattivazione del backup rimuoverà il backup dell\'attuale chiave crittografica e disattiverà altre funzioni di sicurezza. In questo caso:"
+ "Non avrai la cronologia dei messaggi cifrati su nuovi dispositivi"
+ "Perderai l\'accesso ai tuoi messaggi cifrati se ti sei disconnesso da %1$s ovunque"
+ "Vuoi davvero disattivare il backup?"
+ "Ottieni una nuova chiave di recupero se hai perso quella esistente. Dopo averla cambiata, quella vecchia non funzionerà più."
+ "Genera una nuova chiave di recupero"
+ "Assicurati di conservare la chiave di recupero in un posto sicuro"
+ "Chiave di recupero cambiata"
+ "Cambiare la chiave di recupero?"
+ "Inserisci la tua chiave di recupero per confermare l\'accesso al backup della chat."
+ "Riprova per confermare l\'accesso al backup della chat."
+ "Chiave di recupero errata"
+ "Inserisci il codice di 48 caratteri."
+ "Inserisci…"
+ "Chiave di recupero confermata"
+ "Conferma la chiave di recupero"
+ "Chiave di recupero copiata"
+ "Generazione…"
+ "Salva la chiave di recupero"
+ "Annota la chiave di recupero in un posto sicuro o salvala in un gestore di password."
+ "Tocca per copiare la chiave di recupero"
+ "Salva la tua chiave di recupero"
+ "Dopo questo passaggio non potrai accedere alla nuova chiave di recupero."
+ "Hai salvato la chiave di recupero?"
+ "Il backup della chat è protetto da una chiave di recupero. Se hai bisogno di una nuova chiave di recupero dopo la configurazione, puoi ricrearla selezionando \"Cambia chiave di recupero\"."
+ "Genera la tua chiave di recupero"
+ "Assicurati di conservare la chiave di recupero in un posto sicuro"
+ "Configurazione del recupero completata"
+ "Configura il recupero"
+
diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt
index 39d8ebf81f..4ee16285cd 100644
--- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt
+++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.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.libraries.architecture.AsyncData
+import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
@@ -40,7 +40,7 @@ class SecureBackupEnablePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
- assertThat(initialState.enableAction).isEqualTo(AsyncData.Uninitialized)
+ assertThat(initialState.enableAction).isEqualTo(AsyncAction.Uninitialized)
}
}
@@ -53,9 +53,9 @@ class SecureBackupEnablePresenterTest {
val initialState = awaitItem()
initialState.eventSink(SecureBackupEnableEvents.EnableBackup)
val loadingState = awaitItem()
- assertThat(loadingState.enableAction).isInstanceOf(AsyncData.Loading::class.java)
+ assertThat(loadingState.enableAction).isInstanceOf(AsyncAction.Loading::class.java)
val finalState = awaitItem()
- assertThat(finalState.enableAction).isEqualTo(AsyncData.Success(Unit))
+ assertThat(finalState.enableAction).isEqualTo(AsyncAction.Success(Unit))
}
}
@@ -70,12 +70,12 @@ class SecureBackupEnablePresenterTest {
val initialState = awaitItem()
initialState.eventSink(SecureBackupEnableEvents.EnableBackup)
val loadingState = awaitItem()
- assertThat(loadingState.enableAction).isInstanceOf(AsyncData.Loading::class.java)
+ assertThat(loadingState.enableAction).isInstanceOf(AsyncAction.Loading::class.java)
val errorState = awaitItem()
- assertThat(errorState.enableAction).isEqualTo(AsyncData.Failure(AN_EXCEPTION))
+ assertThat(errorState.enableAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
errorState.eventSink(SecureBackupEnableEvents.DismissDialog)
val finalState = awaitItem()
- assertThat(finalState.enableAction).isEqualTo(AsyncData.Uninitialized)
+ assertThat(finalState.enableAction).isEqualTo(AsyncAction.Uninitialized)
}
}
diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt
index 0182e87cf3..df3549b0f8 100644
--- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt
+++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt
@@ -50,5 +50,6 @@ fun aSessionData(
loginTimestamp = null,
isTokenValid = isTokenValid,
loginType = LoginType.UNKNOWN,
+ passphrase = null,
)
}
diff --git a/features/signedout/impl/src/main/res/values-it/translations.xml b/features/signedout/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..c9da145562
--- /dev/null
+++ b/features/signedout/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Hai cambiato la password in un\'altra sessione"
+ "Hai eliminato la sessione da un\'altra sessione"
+ "L\'amministratore del tuo server ha invalidato il tuo accesso"
+ "Potresti essere stato disconnesso per uno dei motivi elencati di seguito. Accedi di nuovo per continuare a usare %s."
+ "Sei disconnesso"
+
diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml
index 852e94b463..0f4f86adf4 100644
--- a/features/verifysession/impl/src/main/res/values-it/translations.xml
+++ b/features/verifysession/impl/src/main/res/values-it/translations.xml
@@ -3,12 +3,15 @@
"C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata."
"Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione."
"Confronta le emoji"
+ "Conferma che i numeri seguenti corrispondano a quelli mostrati nell\'altra sessione."
+ "Confronta i numeri"
"La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile."
"Dimostra la tua identità per accedere alla cronologia dei messaggi crittografati."
"Apri una sessione esistente"
"Riprova la verifica"
"Sono pronto"
"In attesa di un riscontro"
+ "Confronta un set unico di emoji."
"Confronta le emoji uniche, assicurandoti che appaiano nello stesso ordine."
"Non corrispondono"
"Corrispondono"
diff --git a/features/verifysession/impl/src/main/res/values-ru/translations.xml b/features/verifysession/impl/src/main/res/values-ru/translations.xml
index 8351059b5d..78bc282cf4 100644
--- a/features/verifysession/impl/src/main/res/values-ru/translations.xml
+++ b/features/verifysession/impl/src/main/res/values-ru/translations.xml
@@ -1,7 +1,7 @@
- "Кажется, что-то не так. Время ожидания запроса истекло, либо запрос был отклонен."
- "Убедитесь, что приведенные ниже смайлики совпадают со смайликами, показанными во время другого сеанса."
+ "Похоже, что-то не так. Время ожидания запроса либо истекло, либо запрос был отклонен."
+ "Убедитесь, что приведенные ниже емоджи совпадают с емоджи показанными во время другого сеанса."
"Сравните емодзи"
"Убедитесь, что приведенные ниже числа совпадают с цифрами, показанными в другом сеансе."
"Сравните числа"
diff --git a/features/viewfolder/api/build.gradle.kts b/features/viewfolder/api/build.gradle.kts
new file mode 100644
index 0000000000..98a53dad87
--- /dev/null
+++ b/features/viewfolder/api/build.gradle.kts
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+plugins {
+ id("io.element.android-library")
+}
+
+android {
+ namespace = "io.element.android.features.viewfolder.api"
+}
+
+dependencies {
+ implementation(projects.libraries.architecture)
+}
diff --git a/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt
new file mode 100644
index 0000000000..f3fb62374e
--- /dev/null
+++ b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.features.viewfolder.api
+
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import io.element.android.libraries.architecture.FeatureEntryPoint
+
+interface ViewFolderEntryPoint : FeatureEntryPoint {
+ data class Params(
+ val rootPath: String,
+ )
+
+ fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
+
+ interface NodeBuilder {
+ fun params(params: Params): NodeBuilder
+ fun callback(callback: Callback): NodeBuilder
+ fun build(): Node
+ }
+
+ interface Callback : Plugin {
+ fun onDone()
+ }
+}
diff --git a/features/viewfolder/impl/build.gradle.kts b/features/viewfolder/impl/build.gradle.kts
new file mode 100644
index 0000000000..67c818f185
--- /dev/null
+++ b/features/viewfolder/impl/build.gradle.kts
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+plugins {
+ id("io.element.android-compose-library")
+ alias(libs.plugins.anvil)
+ alias(libs.plugins.ksp)
+ id("kotlin-parcelize")
+}
+
+android {
+ namespace = "io.element.android.features.viewfolder.impl"
+}
+
+anvil {
+ generateDaggerFactories.set(true)
+}
+
+dependencies {
+ implementation(projects.anvilannotations)
+ anvil(projects.anvilcodegen)
+ implementation(projects.libraries.androidutils)
+ implementation(projects.libraries.architecture)
+ implementation(projects.libraries.core)
+ implementation(projects.libraries.designsystem)
+ implementation(projects.libraries.uiStrings)
+ api(projects.features.viewfolder.api)
+ ksp(libs.showkase.processor)
+
+ testImplementation(libs.test.junit)
+ testImplementation(libs.test.robolectric)
+ testImplementation(libs.coroutines.test)
+ testImplementation(libs.molecule.runtime)
+ testImplementation(libs.test.truth)
+ testImplementation(libs.test.turbine)
+ testImplementation(projects.tests.testutils)
+ testImplementation(projects.libraries.matrix.test)
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt
new file mode 100644
index 0000000000..0383c03b8f
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.features.viewfolder.impl
+
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
+import io.element.android.features.viewfolder.impl.root.ViewFolderRootNode
+import io.element.android.libraries.architecture.createNode
+import io.element.android.libraries.di.AppScope
+import javax.inject.Inject
+
+@ContributesBinding(AppScope::class)
+class DefaultViewFolderEntryPoint @Inject constructor() : ViewFolderEntryPoint {
+ override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ViewFolderEntryPoint.NodeBuilder {
+ val plugins = ArrayList()
+
+ return object : ViewFolderEntryPoint.NodeBuilder {
+ override fun params(params: ViewFolderEntryPoint.Params): ViewFolderEntryPoint.NodeBuilder {
+ plugins += ViewFolderRootNode.Inputs(params.rootPath)
+ return this
+ }
+
+ override fun callback(callback: ViewFolderEntryPoint.Callback): ViewFolderEntryPoint.NodeBuilder {
+ plugins += callback
+ return this
+ }
+
+ override fun build(): Node {
+ return parentNode.createNode(buildContext, plugins)
+ }
+ }
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt
new file mode 100644
index 0000000000..bc723f70cb
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.di.AppScope
+import kotlinx.coroutines.withContext
+import java.io.File
+import javax.inject.Inject
+
+interface FileContentReader {
+ suspend fun getLines(path: String): Result>
+}
+
+@ContributesBinding(AppScope::class)
+class DefaultFileContentReader @Inject constructor(
+ private val dispatchers: CoroutineDispatchers,
+) : FileContentReader {
+ override suspend fun getLines(path: String): Result> = withContext(dispatchers.io) {
+ runCatching {
+ File(path).readLines()
+ }
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt
new file mode 100644
index 0000000000..9c78bb21aa
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import android.content.ContentValues
+import android.content.Context
+import android.os.Build
+import android.os.Environment
+import android.provider.MediaStore
+import androidx.annotation.RequiresApi
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.androidutils.system.toast
+import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.core.mimetype.MimeTypes
+import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.ApplicationContext
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+import java.io.File
+import java.io.FileOutputStream
+import javax.inject.Inject
+
+interface FileSave {
+ suspend fun save(
+ path: String,
+ )
+}
+
+@ContributesBinding(AppScope::class)
+class DefaultFileSave @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val dispatchers: CoroutineDispatchers,
+) : FileSave {
+ override suspend fun save(
+ path: String,
+ ) {
+ withContext(dispatchers.io) {
+ runCatching {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ saveOnDiskUsingMediaStore(path)
+ } else {
+ saveOnDiskUsingExternalStorageApi(path)
+ }
+ }.onSuccess {
+ Timber.v("Save on disk succeed")
+ withContext(dispatchers.main) {
+ context.toast("Save on disk succeed")
+ }
+ }.onFailure {
+ Timber.e(it, "Save on disk failed")
+ }
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun saveOnDiskUsingMediaStore(path: String) {
+ val file = File(path)
+ val contentValues = ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, file.name)
+ put(MediaStore.MediaColumns.MIME_TYPE, MimeTypes.OctetStream)
+ put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
+ }
+ val resolver = context.contentResolver
+ val outputUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
+ if (outputUri != null) {
+ file.inputStream().use { input ->
+ resolver.openOutputStream(outputUri).use { output ->
+ input.copyTo(output!!, DEFAULT_BUFFER_SIZE)
+ }
+ }
+ }
+ }
+
+ private fun saveOnDiskUsingExternalStorageApi(path: String) {
+ val file = File(path)
+ val target = File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
+ file.name
+ )
+ file.inputStream().use { input ->
+ FileOutputStream(target).use { output ->
+ input.copyTo(output)
+ }
+ }
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt
new file mode 100644
index 0000000000..c0dd573a3e
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.core.content.FileProvider
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.core.meta.BuildMeta
+import io.element.android.libraries.core.mimetype.MimeTypes
+import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.ApplicationContext
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+import java.io.File
+import javax.inject.Inject
+
+interface FileShare {
+ suspend fun share(
+ path: String
+ )
+}
+
+@ContributesBinding(AppScope::class)
+class DefaultFileShare @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val dispatchers: CoroutineDispatchers,
+ private val buildMeta: BuildMeta,
+) : FileShare {
+ override suspend fun share(
+ path: String,
+ ) {
+ runCatching {
+ val file = File(path)
+ val shareableUri = file.toShareableUri()
+ val shareMediaIntent = Intent(Intent.ACTION_SEND)
+ .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .putExtra(Intent.EXTRA_STREAM, shareableUri)
+ .setTypeAndNormalize(MimeTypes.OctetStream)
+ withContext(dispatchers.main) {
+ val intent = Intent.createChooser(shareMediaIntent, null)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+ }.onSuccess {
+ Timber.v("Share file succeed")
+ }.onFailure {
+ Timber.e(it, "Share file failed")
+ }
+ }
+
+ private fun File.toShareableUri(): Uri {
+ val authority = "${buildMeta.applicationId}.fileprovider"
+ return FileProvider.getUriForFile(context, authority, this).normalizeScheme()
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt
new file mode 100644
index 0000000000..dfea39d2e6
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+sealed interface ViewFileEvents {
+ data object SaveOnDisk : ViewFileEvents
+ data object Share : ViewFileEvents
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt
new file mode 100644
index 0000000000..3d4ad727fe
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import com.bumble.appyx.core.plugin.plugins
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.anvilannotations.ContributesNode
+import io.element.android.libraries.architecture.NodeInputs
+import io.element.android.libraries.architecture.inputs
+import io.element.android.libraries.di.AppScope
+
+@ContributesNode(AppScope::class)
+class ViewFileNode @AssistedInject constructor(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+ presenterFactory: ViewFilePresenter.Factory,
+) : Node(buildContext, plugins = plugins) {
+ data class Inputs(
+ val path: String,
+ val name: String,
+ ) : NodeInputs
+
+ interface Callback : Plugin {
+ fun onBackPressed()
+ }
+
+ private val inputs: Inputs = inputs()
+
+ private val presenter = presenterFactory.create(
+ path = inputs.path,
+ name = inputs.name,
+ )
+
+ private fun onBackPressed() {
+ plugins().forEach { it.onBackPressed() }
+ }
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ val state = presenter.present()
+ ViewFileView(
+ state = state,
+ modifier = modifier,
+ onBackPressed = ::onBackPressed,
+ )
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt
new file mode 100644
index 0000000000..4af90a83ed
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.architecture.Presenter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class ViewFilePresenter @AssistedInject constructor(
+ @Assisted("path") val path: String,
+ @Assisted("name") val name: String,
+ private val fileContentReader: FileContentReader,
+ private val fileShare: FileShare,
+ private val fileSave: FileSave,
+) : Presenter {
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ @Assisted("path") path: String,
+ @Assisted("name") name: String,
+ ): ViewFilePresenter
+ }
+
+ @Composable
+ override fun present(): ViewFileState {
+ val coroutineScope = rememberCoroutineScope()
+ val colorationMode = remember { name.toColorationMode() }
+
+ fun handleEvent(event: ViewFileEvents) {
+ when (event) {
+ ViewFileEvents.Share -> coroutineScope.share(path)
+ ViewFileEvents.SaveOnDisk -> coroutineScope.save(path)
+ }
+ }
+
+ var lines: AsyncData> by remember { mutableStateOf(AsyncData.Loading()) }
+ LaunchedEffect(Unit) {
+ lines = fileContentReader.getLines(path).fold(
+ onSuccess = { AsyncData.Success(it) },
+ onFailure = { AsyncData.Failure(it) }
+ )
+ }
+ return ViewFileState(
+ name = name,
+ lines = lines,
+ colorationMode = colorationMode,
+ eventSink = ::handleEvent,
+ )
+ }
+
+ private fun CoroutineScope.share(path: String) = launch {
+ fileShare.share(path)
+ }
+
+ private fun CoroutineScope.save(path: String) = launch {
+ fileSave.save(path)
+ }
+}
+
+private fun String.toColorationMode(): ColorationMode {
+ return when {
+ equals("logcat.log") -> ColorationMode.Logcat
+ startsWith("logs.") -> ColorationMode.RustLogs
+ else -> ColorationMode.None
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt
new file mode 100644
index 0000000000..35ed0991ad
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.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
+ *
+ * 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.features.viewfolder.impl.file
+
+import io.element.android.libraries.architecture.AsyncData
+
+data class ViewFileState(
+ val name: String,
+ val lines: AsyncData>,
+ val colorationMode: ColorationMode,
+ val eventSink: (ViewFileEvents) -> Unit,
+)
+
+enum class ColorationMode {
+ Logcat,
+ RustLogs,
+ None,
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt
new file mode 100644
index 0000000000..3213036d4d
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import io.element.android.libraries.architecture.AsyncData
+
+open class ViewFileStateProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ aViewFileState(),
+ aViewFileState(lines = AsyncData.Loading()),
+ aViewFileState(lines = AsyncData.Failure(Exception("A failure"))),
+ aViewFileState(lines = AsyncData.Success(emptyList())),
+ aViewFileState(
+ name = "logcat.log",
+ lines = AsyncData.Success(
+ listOf(
+ "Line 1",
+ "Line 2",
+ "Line 3 lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" +
+ " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,",
+ "01-23 13:14:50.740 25818 25818 V verbose",
+ "01-23 13:14:50.740 25818 25818 D debug",
+ "01-23 13:14:50.740 25818 25818 I info",
+ "01-23 13:14:50.740 25818 25818 W warning",
+ "01-23 13:14:50.740 25818 25818 E error",
+ "01-23 13:14:50.740 25818 25818 A assertion",
+ )
+ ),
+ colorationMode = ColorationMode.Logcat,
+ ),
+ aViewFileState(
+ name = "logs.2024-01-26",
+ lines = AsyncData.Success(
+ listOf(
+ "Line 1",
+ "Line 2",
+ "Line 3 lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" +
+ " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,",
+ "2024-01-26T10:22:26.947416Z TRACE trace",
+ "2024-01-26T10:22:26.947416Z DEBUG debug",
+ "2024-01-26T10:22:26.947416Z INFO info",
+ "2024-01-26T10:22:26.947416Z WARN warn",
+ "2024-01-26T10:22:26.947416Z ERROR error",
+ )
+ ),
+ colorationMode = ColorationMode.RustLogs,
+ )
+ )
+}
+
+fun aViewFileState(
+ name: String = "aName",
+ lines: AsyncData> = AsyncData.Uninitialized,
+ colorationMode: ColorationMode = ColorationMode.None,
+) = ViewFileState(
+ name = name,
+ lines = lines,
+ colorationMode = colorationMode,
+ eventSink = {},
+)
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt
new file mode 100644
index 0000000000..d5b3818227
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.features.viewfolder.impl.file
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import io.element.android.compound.theme.ElementTheme
+import io.element.android.libraries.androidutils.system.copyToClipboard
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.designsystem.components.async.AsyncFailure
+import io.element.android.libraries.designsystem.components.async.AsyncLoading
+import io.element.android.libraries.designsystem.components.button.BackButton
+import io.element.android.libraries.designsystem.icons.CompoundDrawables
+import io.element.android.libraries.designsystem.preview.ElementPreview
+import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.theme.aliasScreenTitle
+import io.element.android.libraries.designsystem.theme.components.Icon
+import io.element.android.libraries.designsystem.theme.components.IconButton
+import io.element.android.libraries.designsystem.theme.components.Scaffold
+import io.element.android.libraries.designsystem.theme.components.Text
+import io.element.android.libraries.designsystem.theme.components.TopAppBar
+import io.element.android.libraries.ui.strings.CommonStrings
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toImmutableList
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ViewFileView(
+ state: ViewFileState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ TopAppBar(
+ navigationIcon = {
+ BackButton(onClick = onBackPressed)
+ },
+ title = {
+ Text(
+ text = state.name,
+ style = ElementTheme.typography.aliasScreenTitle,
+ )
+ },
+ actions = {
+ IconButton(
+ onClick = {
+ state.eventSink(ViewFileEvents.Share)
+ },
+ ) {
+ Icon(
+ resourceId = CompoundDrawables.ic_share_android,
+ contentDescription = stringResource(id = CommonStrings.action_share),
+ )
+ }
+ IconButton(
+ onClick = {
+ state.eventSink(ViewFileEvents.SaveOnDisk)
+ },
+ ) {
+ Icon(
+ resourceId = CompoundDrawables.ic_download,
+ contentDescription = stringResource(id = CommonStrings.action_save),
+ )
+ }
+ }
+ )
+ },
+ content = { padding ->
+ Column(
+ modifier = Modifier
+ .padding(padding)
+ .consumeWindowInsets(padding)
+ ) {
+ when (state.lines) {
+ AsyncData.Uninitialized,
+ is AsyncData.Loading -> AsyncLoading()
+ is AsyncData.Success -> FileContent(
+ modifier = Modifier.weight(1f),
+ lines = state.lines.data.toImmutableList(),
+ colorationMode = state.colorationMode,
+ )
+ is AsyncData.Failure -> AsyncFailure(throwable = state.lines.error, onRetry = null)
+ }
+ }
+ }
+ )
+}
+
+@Composable
+private fun FileContent(
+ lines: ImmutableList,
+ colorationMode: ColorationMode,
+ modifier: Modifier = Modifier,
+) {
+ LazyColumn(
+ modifier = modifier
+ ) {
+ if (lines.isEmpty()) {
+ item {
+ Spacer(Modifier.size(80.dp))
+ Text(
+ text = "Empty file",
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.tertiary,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ } else {
+ itemsIndexed(
+ items = lines,
+ ) { index, line ->
+ LineRow(
+ lineNumber = index + 1,
+ line = line,
+ colorationMode = colorationMode,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun LineRow(
+ lineNumber: Int,
+ line: String,
+ colorationMode: ColorationMode,
+) {
+ val context = LocalContext.current
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(onClick = {
+ context.copyToClipboard(
+ line,
+ "Line copied to clipboard",
+ )
+ })
+ ) {
+ Text(
+ modifier = Modifier
+ .widthIn(min = 36.dp)
+ .padding(horizontal = 4.dp),
+ text = "$lineNumber",
+ textAlign = TextAlign.End,
+ color = ElementTheme.colors.textSecondary,
+ style = ElementTheme.typography.fontBodyMdMedium,
+ )
+ val color = ElementTheme.colors.textSecondary
+ val width = 0.5.dp.value
+ Text(
+ modifier = Modifier
+ .weight(1f)
+ .drawWithContent {
+ // Using .height(IntrinsicSize.Min) on the Row does not work well inside LazyColumn
+ drawLine(
+ color = color,
+ start = Offset(0f, 0f),
+ end = Offset(0f, size.height),
+ strokeWidth = width
+ )
+ drawContent()
+ }
+ .padding(horizontal = 4.dp),
+ text = line,
+ color = line.toColor(colorationMode),
+ style = ElementTheme.typography.fontBodyMdRegular
+ )
+ }
+}
+
+/**
+ * Convert a line to a color.
+ * Ex for logcat:
+ * `01-23 13:14:50.740 25818 25818 D org.matrix.rust.sdk: elementx: SyncIndicator = Hide | RustRoomListService.kt:81`
+ * ^ use this char to determine the color
+ * Ex for Rust logs:
+ * `2024-01-26T10:22:26.947416Z WARN elementx: Restore with non-empty map | MatrixClientsHolder.kt:68`
+ * ^ use this char to determine the color, see [LogLevel]
+ */
+@Composable
+private fun String.toColor(colorationMode: ColorationMode): Color {
+ return when (colorationMode) {
+ ColorationMode.Logcat -> when (getOrNull(31)) {
+ 'D' -> colorDebug
+ 'I' -> colorInfo
+ 'W' -> colorWarning
+ 'E' -> colorError
+ 'A' -> colorError
+ else -> ElementTheme.colors.textPrimary
+ }
+ ColorationMode.RustLogs -> when (getOrNull(32)) {
+ 'E' -> ElementTheme.colors.textPrimary
+ 'G' -> colorDebug
+ 'O' -> colorInfo
+ 'N' -> colorWarning
+ 'R' -> colorError
+ else -> ElementTheme.colors.textPrimary
+ }
+ ColorationMode.None -> ElementTheme.colors.textPrimary
+ }
+}
+
+private val colorDebug = Color(0xFF299999)
+private val colorInfo = Color(0xFFABC023)
+private val colorWarning = Color(0xFFBBB529)
+private val colorError = Color(0xFFFF6B68)
+
+@PreviewsDayNight
+@Composable
+internal fun ViewFileViewPreview(@PreviewParameter(ViewFileStateProvider::class) state: ViewFileState) = ElementPreview {
+ ViewFileView(
+ state = state,
+ onBackPressed = {},
+ )
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt
new file mode 100644
index 0000000000..68a0373b8a
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.features.viewfolder.impl.folder
+
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.features.viewfolder.impl.model.Item
+import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
+import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.di.AppScope
+import kotlinx.coroutines.withContext
+import java.io.File
+import javax.inject.Inject
+
+interface FolderExplorer {
+ suspend fun getItems(path: String): List-
+}
+
+@ContributesBinding(AppScope::class)
+class DefaultFolderExplorer @Inject constructor(
+ private val fileSizeFormatter: FileSizeFormatter,
+ private val dispatchers: CoroutineDispatchers,
+) : FolderExplorer {
+ override suspend fun getItems(path: String): List
- = withContext(dispatchers.io) {
+ val current = File(path)
+ if (current.isFile) {
+ error("Not a folder")
+ }
+ val folderContent = current.listFiles().orEmpty().map { file ->
+ if (file.isDirectory) {
+ Item.Folder(
+ path = file.path,
+ name = file.name
+ )
+ } else {
+ Item.File(
+ path = file.path,
+ name = file.name,
+ formattedSize = fileSizeFormatter.format(file.length()),
+ )
+ }
+ }
+ buildList {
+ addAll(folderContent.filterIsInstance().sortedBy(Item.Folder::name))
+ addAll(folderContent.filterIsInstance().sortedBy(Item.File::name))
+ }
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt
new file mode 100644
index 0000000000..23dac7bc4e
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.features.viewfolder.impl.folder
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import com.bumble.appyx.core.plugin.plugins
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.anvilannotations.ContributesNode
+import io.element.android.features.viewfolder.impl.model.Item
+import io.element.android.libraries.architecture.NodeInputs
+import io.element.android.libraries.architecture.inputs
+import io.element.android.libraries.di.AppScope
+
+@ContributesNode(AppScope::class)
+class ViewFolderNode @AssistedInject constructor(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+ presenterFactory: ViewFolderPresenter.Factory,
+) : Node(buildContext, plugins = plugins) {
+ data class Inputs(
+ val canGoUp: Boolean,
+ val path: String,
+ ) : NodeInputs
+
+ interface Callback : Plugin {
+ fun onBackPressed()
+ fun onNavigateTo(item: Item)
+ }
+
+ private val inputs: Inputs = inputs()
+
+ private val presenter = presenterFactory.create(
+ canGoUp = inputs.canGoUp,
+ path = inputs.path,
+ )
+
+ private fun onBackPressed() {
+ plugins().forEach { it.onBackPressed() }
+ }
+
+ private fun onNavigateTo(item: Item) {
+ plugins().forEach { it.onNavigateTo(item) }
+ }
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ val state = presenter.present()
+ ViewFolderView(
+ state = state,
+ modifier = modifier,
+ onNavigateTo = ::onNavigateTo,
+ onBackPressed = ::onBackPressed,
+ )
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt
new file mode 100644
index 0000000000..64e58036e2
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.features.viewfolder.impl.folder
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import io.element.android.features.viewfolder.impl.model.Item
+import io.element.android.libraries.architecture.Presenter
+import kotlinx.collections.immutable.toImmutableList
+
+class ViewFolderPresenter @AssistedInject constructor(
+ @Assisted val canGoUp: Boolean,
+ @Assisted val path: String,
+ private val folderExplorer: FolderExplorer,
+) : Presenter {
+ @AssistedFactory
+ interface Factory {
+ fun create(canGoUp: Boolean, path: String): ViewFolderPresenter
+ }
+
+ @Composable
+ override fun present(): ViewFolderState {
+ var content by remember { mutableStateOf(emptyList
- ()) }
+ LaunchedEffect(Unit) {
+ content = buildList {
+ if (canGoUp) add(Item.Parent)
+ addAll(folderExplorer.getItems(path))
+ }
+ }
+ return ViewFolderState(
+ path = path,
+ content = content.toImmutableList(),
+ )
+ }
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.kt
new file mode 100644
index 0000000000..d31e2ff3cd
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.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
+ *
+ * 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.features.viewfolder.impl.folder
+
+import io.element.android.features.viewfolder.impl.model.Item
+import kotlinx.collections.immutable.ImmutableList
+
+data class ViewFolderState(
+ val path: String,
+ val content: ImmutableList
- ,
+)
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt
new file mode 100644
index 0000000000..76e7fbbe8d
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.features.viewfolder.impl.folder
+
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import io.element.android.features.viewfolder.impl.model.Item
+import kotlinx.collections.immutable.toImmutableList
+
+open class ViewFolderStateProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ aViewFolderState(),
+ aViewFolderState(
+ content = listOf(
+ Item.Parent,
+ Item.Folder("aPath", "aFolder"),
+ Item.File("aPath", "aFile", "12kB"),
+ )
+ )
+ )
+}
+
+fun aViewFolderState(
+ path: String = "aPath",
+ content: List
- = emptyList(),
+) = ViewFolderState(
+ path = path,
+ content = content.toImmutableList(),
+)
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt
new file mode 100644
index 0000000000..44453c253e
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.features.viewfolder.impl.folder
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Description
+import androidx.compose.material.icons.outlined.Folder
+import androidx.compose.material.icons.outlined.SubdirectoryArrowLeft
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import io.element.android.compound.theme.ElementTheme
+import io.element.android.features.viewfolder.impl.model.Item
+import io.element.android.libraries.designsystem.components.button.BackButton
+import io.element.android.libraries.designsystem.components.list.ListItemContent
+import io.element.android.libraries.designsystem.preview.ElementPreview
+import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.theme.aliasScreenTitle
+import io.element.android.libraries.designsystem.theme.components.IconSource
+import io.element.android.libraries.designsystem.theme.components.ListItem
+import io.element.android.libraries.designsystem.theme.components.Scaffold
+import io.element.android.libraries.designsystem.theme.components.Text
+import io.element.android.libraries.designsystem.theme.components.TopAppBar
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ViewFolderView(
+ state: ViewFolderState,
+ onNavigateTo: (Item) -> Unit,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ TopAppBar(
+ navigationIcon = {
+ BackButton(onClick = onBackPressed)
+ },
+ title = {
+ Text(
+ text = state.path,
+ style = ElementTheme.typography.aliasScreenTitle,
+ )
+ }
+ )
+ },
+ content = { padding ->
+ Column(
+ modifier = Modifier
+ .padding(padding)
+ .consumeWindowInsets(padding)
+ ) {
+ LazyColumn(
+ modifier = Modifier.weight(1f)
+ ) {
+ items(
+ items = state.content,
+ ) { item ->
+ ItemRow(
+ item = item,
+ onItemClicked = { onNavigateTo(item) },
+ )
+ }
+ if (state.content.none { it !is Item.Parent }) {
+ item {
+ Spacer(Modifier.size(80.dp))
+ Text(
+ text = "Empty folder",
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.tertiary,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+ }
+ }
+ }
+ )
+}
+
+@Composable
+private fun ItemRow(
+ item: Item,
+ onItemClicked: () -> Unit,
+) {
+ when (item) {
+ Item.Parent -> {
+ ListItem(
+ leadingContent = ListItemContent.Icon(IconSource.Vector(Icons.Outlined.SubdirectoryArrowLeft)),
+ headlineContent = {
+ Text(
+ text = "..",
+ modifier = Modifier.padding(16.dp),
+ style = ElementTheme.typography.fontBodyMdMedium,
+ )
+ },
+ onClick = onItemClicked,
+ )
+ }
+ is Item.Folder -> {
+ ListItem(
+ leadingContent = ListItemContent.Icon(IconSource.Vector(Icons.Outlined.Folder)),
+ headlineContent = {
+ Text(
+ text = item.name,
+ modifier = Modifier.padding(16.dp),
+ style = ElementTheme.typography.fontBodyMdMedium,
+ )
+ },
+ onClick = onItemClicked,
+ )
+ }
+ is Item.File -> {
+ ListItem(
+ leadingContent = ListItemContent.Icon(IconSource.Vector(Icons.Outlined.Description)),
+ headlineContent = {
+ Text(
+ text = item.name,
+ modifier = Modifier.padding(16.dp),
+ style = ElementTheme.typography.fontBodyMdMedium,
+ )
+ },
+ trailingContent = ListItemContent.Text(item.formattedSize),
+ onClick = onItemClicked,
+ )
+ }
+ }
+}
+
+@PreviewsDayNight
+@Composable
+internal fun ViewFolderViewPreview(@PreviewParameter(ViewFolderStateProvider::class) state: ViewFolderState) = ElementPreview {
+ ViewFolderView(
+ state = state,
+ onNavigateTo = {},
+ onBackPressed = {},
+ )
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt
new file mode 100644
index 0000000000..2969ec3018
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.features.viewfolder.impl.model
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+sealed interface Item {
+ data object Parent : Item
+
+ data class Folder(
+ val path: String,
+ val name: String,
+ ) : Item
+
+ data class File(
+ val path: String,
+ val name: String,
+ val formattedSize: String,
+ ) : Item
+}
diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderRootNode.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderRootNode.kt
new file mode 100644
index 0000000000..697bd76d13
--- /dev/null
+++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderRootNode.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.features.viewfolder.impl.root
+
+import android.os.Parcelable
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import com.bumble.appyx.core.plugin.plugins
+import com.bumble.appyx.navmodel.backstack.BackStack
+import com.bumble.appyx.navmodel.backstack.operation.pop
+import com.bumble.appyx.navmodel.backstack.operation.push
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.anvilannotations.ContributesNode
+import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
+import io.element.android.features.viewfolder.impl.file.ViewFileNode
+import io.element.android.features.viewfolder.impl.folder.ViewFolderNode
+import io.element.android.features.viewfolder.impl.model.Item
+import io.element.android.libraries.architecture.BackstackView
+import io.element.android.libraries.architecture.BaseFlowNode
+import io.element.android.libraries.architecture.NodeInputs
+import io.element.android.libraries.architecture.createNode
+import io.element.android.libraries.architecture.inputs
+import io.element.android.libraries.di.AppScope
+import kotlinx.parcelize.Parcelize
+
+@ContributesNode(AppScope::class)
+class ViewFolderRootNode @AssistedInject constructor(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+) : BaseFlowNode(
+ backstack = BackStack(
+ initialElement = NavTarget.Root,
+ savedStateMap = buildContext.savedStateMap,
+ ),
+ buildContext = buildContext,
+ plugins = plugins
+) {
+ sealed interface NavTarget : Parcelable {
+ @Parcelize
+ data object Root : NavTarget
+
+ @Parcelize
+ data class Folder(
+ val path: String,
+ ) : NavTarget
+
+ @Parcelize
+ data class File(
+ val path: String,
+ val name: String,
+ ) : NavTarget
+ }
+
+ data class Inputs(
+ val rootPath: String,
+ ) : NodeInputs
+
+ private val inputs: Inputs = inputs()
+
+ override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
+ return when (navTarget) {
+ is NavTarget.Root -> {
+ createViewFolderNode(
+ buildContext,
+ inputs = ViewFolderNode.Inputs(
+ canGoUp = false,
+ path = inputs.rootPath,
+ )
+ )
+ }
+ is NavTarget.Folder -> {
+ createViewFolderNode(
+ buildContext,
+ inputs = ViewFolderNode.Inputs(
+ canGoUp = true,
+ path = navTarget.path,
+ )
+ )
+ }
+ is NavTarget.File -> {
+ val callback: ViewFileNode.Callback = object : ViewFileNode.Callback {
+ override fun onBackPressed() {
+ backstack.pop()
+ }
+ }
+ val inputs = ViewFileNode.Inputs(
+ path = navTarget.path,
+ name = navTarget.name,
+ )
+ createNode(buildContext, plugins = listOf(inputs, callback))
+ }
+ }
+ }
+
+ private fun createViewFolderNode(
+ buildContext: BuildContext,
+ inputs: ViewFolderNode.Inputs,
+ ): Node {
+ val callback: ViewFolderNode.Callback = object : ViewFolderNode.Callback {
+ override fun onBackPressed() {
+ onDone()
+ }
+
+ override fun onNavigateTo(item: Item) {
+ when (item) {
+ Item.Parent -> {
+ // Should not happen when in Root since parent is not accessible from root (canGoUp set to false)
+ backstack.pop()
+ }
+ is Item.Folder -> {
+ backstack.push(NavTarget.Folder(path = item.path))
+ }
+ is Item.File -> {
+ backstack.push(NavTarget.File(path = item.path, name = item.name))
+ }
+ }
+ }
+ }
+ return createNode(buildContext, plugins = listOf(inputs, callback))
+ }
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ BackstackView()
+ }
+
+ private fun onDone() {
+ plugins().forEach { it.onDone() }
+ }
+}
diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.kt
new file mode 100644
index 0000000000..5d8b658587
--- /dev/null
+++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.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
+ *
+ * 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.features.viewfolder.test.file
+
+import io.element.android.features.viewfolder.impl.file.FileContentReader
+
+class FakeFileContentReader : FileContentReader {
+ private var result: Result
> = Result.success(emptyList())
+
+ fun givenResult(result: Result>) {
+ this.result = result
+ }
+
+ override suspend fun getLines(path: String): Result> = result
+}
diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt
new file mode 100644
index 0000000000..0a35526188
--- /dev/null
+++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.features.viewfolder.test.file
+
+import io.element.android.features.viewfolder.impl.file.FileSave
+
+class FakeFileSave : FileSave {
+ var hasBeenCalled = false
+ private set
+
+ override suspend fun save(path: String) {
+ hasBeenCalled = true
+ }
+}
diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt
new file mode 100644
index 0000000000..34b30a99ef
--- /dev/null
+++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.features.viewfolder.test.file
+
+import io.element.android.features.viewfolder.impl.file.FileShare
+
+class FakeFileShare : FileShare {
+ var hasBeenCalled = false
+ private set
+
+ override suspend fun share(path: String) {
+ hasBeenCalled = true
+ }
+}
diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt
new file mode 100644
index 0000000000..6cef49a72d
--- /dev/null
+++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.features.viewfolder.test.file
+
+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.viewfolder.impl.file.ColorationMode
+import io.element.android.features.viewfolder.impl.file.FileContentReader
+import io.element.android.features.viewfolder.impl.file.FileSave
+import io.element.android.features.viewfolder.impl.file.FileShare
+import io.element.android.features.viewfolder.impl.file.ViewFileEvents
+import io.element.android.features.viewfolder.impl.file.ViewFilePresenter
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.matrix.test.AN_EXCEPTION
+import io.element.android.tests.testutils.WarmUpRule
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class ViewFilePresenterTest {
+ @get:Rule
+ val warmUpRule = WarmUpRule()
+
+ @Test
+ fun `present - initial state`() = runTest {
+ val fileContentReader = FakeFileContentReader().apply {
+ givenResult(Result.success(listOf("aLine")))
+ }
+ val presenter = createPresenter(fileContentReader = fileContentReader)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.name).isEqualTo("aName")
+ assertThat(initialState.lines).isInstanceOf(AsyncData.Loading::class.java)
+ assertThat(initialState.colorationMode).isEqualTo(ColorationMode.None)
+ val loadedState = awaitItem()
+ val lines = (loadedState.lines as AsyncData.Success).data
+ assertThat(lines.size).isEqualTo(1)
+ assertThat(lines.first()).isEqualTo("aLine")
+ }
+ }
+
+ @Test
+ fun `present - coloration mode for logcat`() = runTest {
+ val presenter = createPresenter(name = "logcat.log")
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.colorationMode).isEqualTo(ColorationMode.Logcat)
+ cancelAndConsumeRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - coloration mode for logs`() = runTest {
+ val presenter = createPresenter(name = "logs.date")
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.colorationMode).isEqualTo(ColorationMode.RustLogs)
+ cancelAndConsumeRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - share should not have any side effect`() = runTest {
+ val fileContentReader = FakeFileContentReader().apply {
+ givenResult(Result.success(listOf("aLine")))
+ }
+ val fileShare = FakeFileShare()
+ val fileSave = FakeFileSave()
+ val presenter = createPresenter(fileContentReader = fileContentReader, fileShare = fileShare, fileSave = fileSave)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val initialState = awaitItem()
+ initialState.eventSink(ViewFileEvents.Share)
+ assertThat(fileShare.hasBeenCalled).isTrue()
+ assertThat(fileSave.hasBeenCalled).isFalse()
+ }
+ }
+
+ @Test
+ fun `present - with error loading file`() = runTest {
+ val fileContentReader = FakeFileContentReader().apply {
+ givenResult(Result.failure(AN_EXCEPTION))
+ }
+ val presenter = createPresenter(fileContentReader = fileContentReader)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val errorState = awaitItem()
+ assertThat(errorState.lines).isInstanceOf(AsyncData.Failure::class.java)
+ }
+ }
+
+ @Test
+ fun `present - save should not have any side effect`() = runTest {
+ val fileContentReader = FakeFileContentReader().apply {
+ givenResult(Result.success(listOf("aLine")))
+ }
+ val fileShare = FakeFileShare()
+ val fileSave = FakeFileSave()
+ val presenter = createPresenter(fileContentReader = fileContentReader, fileShare = fileShare, fileSave = fileSave)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val initialState = awaitItem()
+ initialState.eventSink(ViewFileEvents.SaveOnDisk)
+ assertThat(fileShare.hasBeenCalled).isFalse()
+ assertThat(fileSave.hasBeenCalled).isTrue()
+ }
+ }
+
+ private fun createPresenter(
+ path: String = "aPath",
+ name: String = "aName",
+ fileContentReader: FileContentReader = FakeFileContentReader(),
+ fileShare: FileShare = FakeFileShare(),
+ fileSave: FileSave = FakeFileSave(),
+ ) = ViewFilePresenter(
+ path = path,
+ name = name,
+ fileContentReader = fileContentReader,
+ fileShare = fileShare,
+ fileSave = fileSave,
+ )
+}
diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt
new file mode 100644
index 0000000000..c4a60303b8
--- /dev/null
+++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.features.viewfolder.test.folder
+
+import io.element.android.features.viewfolder.impl.folder.FolderExplorer
+import io.element.android.features.viewfolder.impl.model.Item
+
+class FakeFolderExplorer : FolderExplorer {
+ private var result: List- = emptyList()
+
+ fun givenResult(result: List
- ) {
+ this.result = result
+ }
+
+ override suspend fun getItems(path: String): List
- = result
+}
diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt
new file mode 100644
index 0000000000..209d76cb9d
--- /dev/null
+++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.features.viewfolder.test.folder
+
+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.viewfolder.impl.folder.FolderExplorer
+import io.element.android.features.viewfolder.impl.folder.ViewFolderPresenter
+import io.element.android.features.viewfolder.impl.model.Item
+import io.element.android.tests.testutils.WarmUpRule
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class ViewFolderPresenterTest {
+ @get:Rule
+ val warmUpRule = WarmUpRule()
+
+ @Test
+ fun `present - initial state`() = runTest {
+ val presenter = createPresenter()
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.path).isEqualTo("aPath")
+ assertThat(initialState.content).isEmpty()
+ }
+ }
+
+ @Test
+ fun `present - list items from root`() = runTest {
+ val items = listOf(
+ Item.Folder("aFilePath", "aFilename"),
+ Item.File("aFolderPath", "aFolderName", "aSize"),
+ )
+ val folderExplorer = FakeFolderExplorer().apply {
+ givenResult(items)
+ }
+ val presenter = createPresenter(folderExplorer = folderExplorer)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val initialState = awaitItem()
+ assertThat(initialState.path).isEqualTo("aPath")
+ assertThat(initialState.content.toList()).isEqualTo(items)
+ }
+ }
+
+ @Test
+ fun `present - list items from a folder`() = runTest {
+ val items = listOf(
+ Item.Folder("aFilePath", "aFilename"),
+ Item.File("aFolderPath", "aFolderName", "aSize"),
+ )
+ val folderExplorer = FakeFolderExplorer().apply {
+ givenResult(items)
+ }
+ val presenter = createPresenter(
+ canGoUp = true,
+ folderExplorer = folderExplorer
+ )
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val initialState = awaitItem()
+ assertThat(initialState.path).isEqualTo("aPath")
+ assertThat(initialState.content.toList()).isEqualTo(listOf(Item.Parent) + items)
+ }
+ }
+
+ private fun createPresenter(
+ canGoUp: Boolean = false,
+ path: String = "aPath",
+ folderExplorer: FolderExplorer = FakeFolderExplorer(),
+ ) = ViewFolderPresenter(
+ path = path,
+ canGoUp = canGoUp,
+ folderExplorer = folderExplorer,
+ )
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8ce7d5a4ef..eb10ea0b09 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,9 +3,9 @@
[versions]
# Project
-android_gradle_plugin = "8.2.1"
-kotlin = "1.9.21"
-ksp = "1.9.21-1.0.16"
+android_gradle_plugin = "8.2.2"
+kotlin = "1.9.22"
+ksp = "1.9.22-1.0.17"
firebaseAppDistribution = "4.0.1"
# AndroidX
@@ -15,17 +15,17 @@ constraintlayout = "2.1.4"
constraintlayout_compose = "1.0.1"
lifecycle = "2.7.0"
activity = "1.8.2"
-media3 = "1.2.0"
+media3 = "1.2.1"
# Compose
-compose_bom = "2023.10.01"
-composecompiler = "1.5.7"
+compose_bom = "2024.01.00"
+composecompiler = "1.5.8"
# Coroutines
coroutines = "1.7.3"
# Accompanist
-accompanist = "0.32.0"
+accompanist = "0.34.0"
# Test
test_core = "1.5.0"
@@ -33,12 +33,12 @@ test_core = "1.5.0"
#other
coil = "2.5.0"
datetime = "0.5.0"
-dependencyAnalysis = "1.28.0"
+dependencyAnalysis = "1.29.0"
serialization_json = "1.6.2"
showkase = "1.0.2"
appyx = "1.4.0"
sqldelight = "2.0.1"
-wysiwyg = "2.24.0"
+wysiwyg = "2.25.0"
# DI
dagger = "2.50"
@@ -62,7 +62,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.0"
# https://firebase.google.com/docs/android/setup#available-libraries
-google_firebase_bom = "com.google.firebase:firebase-bom:32.7.0"
+google_firebase_bom = "com.google.firebase:firebase-bom:32.7.1"
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" }
@@ -90,10 +90,10 @@ androidx_activity_activity = { module = "androidx.activity:activity", version.re
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
androidx_startup = "androidx.startup:startup-runtime:1.1.1"
androidx_preference = "androidx.preference:preference:1.2.1"
-androidx_webkit = "androidx.webkit:webkit:1.9.0"
+androidx_webkit = "androidx.webkit:webkit:1.10.0"
androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
-androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha11" # Keep using alpha11 for now as it breaks timeline scrolling a bit.
+androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-rc01"
androidx_compose_ui = { module = "androidx.compose.ui:ui" }
androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" }
@@ -132,8 +132,8 @@ test_junitext = "androidx.test.ext:junit:1.1.5"
test_mockk = "io.mockk:mockk:1.13.9"
test_konsist = "com.lemonappdev:konsist:0.13.0"
test_turbine = "app.cash.turbine:turbine:1.0.0"
-test_truth = "com.google.truth:truth:1.2.0"
-test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.14"
+test_truth = "com.google.truth:truth:1.3.0"
+test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.15"
test_robolectric = "org.robolectric:robolectric:4.11.1"
test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" }
@@ -152,7 +152,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:1.3.2"
timber = "com.jakewharton.timber:timber:5.0.1"
-matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.83"
+matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.96"
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" }
@@ -169,11 +169,11 @@ maplibre = "org.maplibre.gl:android-sdk:10.2.0"
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.2"
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.2"
opusencoder = "io.element.android:opusencoder:1.1.0"
-kotlinpoet = "com.squareup:kotlinpoet:1.15.3"
+kotlinpoet = "com.squareup:kotlinpoet:1.16.0"
# Analytics
-posthog = "com.posthog:posthog-android:3.1.1"
-sentry = "io.sentry:sentry-android:7.1.0"
+posthog = "com.posthog:posthog-android:3.1.5"
+sentry = "io.sentry:sentry-android:7.3.0"
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:aa14cbcdf81af2746d20a71779ec751f971e1d7f"
# Emojibase
@@ -211,17 +211,14 @@ kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
anvil = { id = "com.squareup.anvil", version.ref = "anvil" }
-detekt = "io.gitlab.arturbosch.detekt:1.23.4"
+detekt = "io.gitlab.arturbosch.detekt:1.23.5"
ktlint = "org.jlleitschuh.gradle.ktlint:12.1.0"
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
-dependencycheck = "org.owasp.dependencycheck:9.0.8"
+dependencycheck = "org.owasp.dependencycheck:9.0.9"
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
-paparazzi = "app.cash.paparazzi:1.3.1"
+paparazzi = "app.cash.paparazzi:1.3.2"
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
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" }
-
-# Version '4.3.1.3277' introduced some regressions in CI time (more than 2x slower), so make sure
-# this is no longer the case before upgrading.
-sonarqube = "org.sonarqube:4.2.1.3168"
+sonarqube = "org.sonarqube:4.4.1.3373"
diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt
index aaf54cc0f8..cac30e02e4 100644
--- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt
+++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt
@@ -25,7 +25,6 @@ data class BuildMeta(
val versionName: String,
val versionCode: Int,
val gitRevision: String,
- val gitRevisionDate: String,
val gitBranchName: String,
val flavorDescription: String,
val flavorShortDescription: String,
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt
index 732a3473a3..f82553d731 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt
@@ -18,6 +18,8 @@ package io.element.android.libraries.designsystem.components.async
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.ProgressDialog
@@ -67,8 +69,9 @@ fun AsyncActionView(
}
}
is AsyncAction.Success -> {
+ val latestOnSuccess by rememberUpdatedState(onSuccess)
LaunchedEffect(async) {
- onSuccess(async.data)
+ latestOnSuccess(async.data)
}
}
}
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt
index b61aa3dda2..af1e03e69f 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt
@@ -16,6 +16,7 @@
package io.element.android.libraries.designsystem.components.tooltip
+import androidx.compose.material3.CaretScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TooltipDefaults
import androidx.compose.runtime.Composable
@@ -27,7 +28,7 @@ import androidx.compose.material3.PlainTooltip as M3PlainTooltip
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun PlainTooltip(
+fun CaretScope.PlainTooltip(
modifier: Modifier = Modifier,
contentColor: Color = ElementTheme.colors.textOnSolidPrimary,
containerColor: Color = ElementTheme.colors.bgActionPrimaryRest,
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt
index fc11758104..f469e32de6 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt
@@ -16,6 +16,7 @@
package io.element.android.libraries.designsystem.components.tooltip
+import androidx.compose.material3.CaretScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TooltipState
import androidx.compose.runtime.Composable
@@ -27,7 +28,7 @@ import androidx.compose.material3.TooltipBox as M3TooltipBox
@Composable
fun TooltipBox(
positionProvider: PopupPositionProvider,
- tooltip: @Composable () -> Unit,
+ tooltip: @Composable CaretScope.() -> Unit,
state: TooltipState,
modifier: Modifier = Modifier,
focusable: Boolean = true,
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt
index 007755ddff..77ecd3afd6 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.CheckboxColors
import androidx.compose.material3.CheckboxDefaults
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -57,7 +58,7 @@ fun Checkbox(
onCheckedChange(!checked)
}
},
- modifier = modifier,
+ modifier = modifier.minimumInteractiveComponentSize(),
enabled = enabled,
colors = colors,
interactionSource = interactionSource,
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt
index 3b438b5f2f..d8c84ac69b 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.RadioButtonColors
import androidx.compose.material3.RadioButtonDefaults
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -48,7 +49,7 @@ fun RadioButton(
androidx.compose.material3.RadioButton(
selected = selected,
onClick = onClick,
- modifier = modifier,
+ modifier = modifier.minimumInteractiveComponentSize(),
enabled = enabled,
colors = colors,
interactionSource = interactionSource,
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt
index 7efb9d52eb..d93a01e4b6 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt
@@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.SwitchColors
import androidx.compose.material3.SwitchDefaults
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -52,7 +53,7 @@ fun Switch(
Material3Switch(
checked = checked,
onCheckedChange = onCheckedChange,
- modifier = modifier,
+ modifier = modifier.minimumInteractiveComponentSize(),
enabled = enabled,
colors = colors,
interactionSource = interactionSource,
diff --git a/libraries/di/src/main/kotlin/io/element/android/libraries/di/annotations/SessionCoroutineScope.kt b/libraries/di/src/main/kotlin/io/element/android/libraries/di/annotations/SessionCoroutineScope.kt
new file mode 100644
index 0000000000..10174dee78
--- /dev/null
+++ b/libraries/di/src/main/kotlin/io/element/android/libraries/di/annotations/SessionCoroutineScope.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.di.annotations
+
+import javax.inject.Qualifier
+
+/**
+ * Qualifies a [CoroutineScope] object which represents the base coroutine scope to use for an active session.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@MustBeDocumented
+@Qualifier
+annotation class SessionCoroutineScope
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
index 131250fac7..e9066681c5 100644
--- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
@@ -69,6 +69,8 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? {
val isOutgoing = event.isOwn
+ // Note: we do not use disambiguated display name here, see
+ // https://github.com/element-hq/element-x-ios/issues/1845#issuecomment-1888707428
val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value
return when (val content = event.content) {
is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom)
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt
index 63c8f939a0..e147f25e16 100644
--- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt
@@ -27,13 +27,13 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
-import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
+import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject
@@ -48,7 +48,7 @@ class DefaultTimelineEventFormatter @Inject constructor(
) : TimelineEventFormatter {
override fun format(event: EventTimelineItem): CharSequence? {
val isOutgoing = event.isOwn
- val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value
+ val senderDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender)
return when (val content = event.content) {
is RoomMembershipContent -> {
roomMembershipContentFormatter.format(content, senderDisplayName, isOutgoing)
diff --git a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml
index 2e2e914dfe..85a55d51a4 100644
--- a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml
+++ b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml
@@ -39,6 +39,8 @@
"Hai cambiato il nome della stanza in: %1$s"
"%1$s ha rimosso il nome della stanza"
"Hai rimosso il nome della stanza"
+ "%1$s non ha apportato modifiche"
+ "Non hai apportato modifiche"
"%1$s ha rifiutato l\'invito"
"Hai rifiutato l\'invito"
"%1$s ha rimosso %2$s"
diff --git a/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml b/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml
index 7930c496dc..d2cfadc57f 100644
--- a/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml
+++ b/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml
@@ -55,5 +55,5 @@
"Вы удалили тему комнаты"
"%1$s разблокирован %2$s"
"Вы разблокировали %1$s"
- "%1$s внес неизвестное изменение в составе"
+ "%1$s внес неизвестное изменение для своих участников"
diff --git a/libraries/matrix/api/build.gradle.kts b/libraries/matrix/api/build.gradle.kts
index f8a600df1e..b9467af169 100644
--- a/libraries/matrix/api/build.gradle.kts
+++ b/libraries/matrix/api/build.gradle.kts
@@ -46,4 +46,5 @@ dependencies {
testImplementation(libs.test.truth)
testImplementation(libs.test.robolectric)
testImplementation(projects.tests.testutils)
+ testImplementation(projects.libraries.matrix.test)
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
index dc452f1514..5c5de97a9c 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
@@ -34,12 +34,15 @@ import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
+import kotlinx.coroutines.CoroutineScope
import java.io.Closeable
interface MatrixClient : Closeable {
val sessionId: SessionId
+ val deviceId: String
val roomListService: RoomListService
val mediaLoader: MatrixMediaLoader
+ val sessionCoroutineScope: CoroutineScope
suspend fun getRoom(roomId: RoomId): MatrixRoom?
suspend fun findDM(userId: UserId): RoomId?
suspend fun ignoreUser(userId: UserId): Result
@@ -72,7 +75,7 @@ interface MatrixClient : Closeable {
*/
suspend fun logout(ignoreSdkError: Boolean): String?
suspend fun loadUserDisplayName(): Result
- suspend fun loadUserAvatarURLString(): Result
+ suspend fun loadUserAvatarUrl(): Result
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result
suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result
fun roomMembershipObserver(): RoomMembershipObserver
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt
index 8275b8ee5f..3fff2dd468 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt
@@ -27,7 +27,9 @@ data class NotificationData(
val roomId: RoomId,
// mxc url
val senderAvatarUrl: String?,
- val senderDisplayName: String?,
+ // private, must use `getSenderName`
+ private val senderDisplayName: String?,
+ private val senderIsNameAmbiguous: Boolean,
val roomAvatarUrl: String?,
val roomDisplayName: String?,
val isDirect: Boolean,
@@ -36,7 +38,13 @@ data class NotificationData(
val timestamp: Long,
val content: NotificationContent,
val hasMention: Boolean,
-)
+) {
+ fun getSenderName(userId: UserId): String = when {
+ senderDisplayName.isNullOrBlank() -> userId.value
+ senderIsNameAmbiguous -> "$senderDisplayName ($userId)"
+ else -> senderDisplayName
+ }
+}
sealed interface NotificationContent {
sealed interface MessageLike : NotificationContent {
@@ -54,11 +62,13 @@ sealed interface NotificationContent {
data class ReactionContent(
val relatedEventId: String
) : MessageLike
+
data object RoomEncrypted : MessageLike
data class RoomMessage(
val senderId: UserId,
val messageType: MessageType
) : MessageLike
+
data object RoomRedaction : MessageLike
data object Sticker : MessageLike
data class Poll(
@@ -83,6 +93,7 @@ sealed interface NotificationContent {
val userId: String,
val membershipState: RoomMembershipState
) : StateEvent
+
data object RoomName : StateEvent
data object RoomPinnedEvents : StateEvent
data object RoomPowerLevels : StateEvent
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
index 00364d4822..550bb7012a 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
@@ -52,6 +52,9 @@ interface MatrixRoom : Closeable {
val activeMemberCount: Long
val joinedMemberCount: Long
+ /** Whether the room is a direct message. */
+ val isDm: Boolean get() = isDirect && isOneToOne
+
val roomInfoFlow: Flow
/**
@@ -72,7 +75,7 @@ interface MatrixRoom : Closeable {
/**
* Try to load the room members and update the membersFlow.
*/
- suspend fun updateMembers(): Result
+ suspend fun updateMembers()
suspend fun updateRoomNotificationSettings(): Result
@@ -124,7 +127,9 @@ interface MatrixRoom : Closeable {
suspend fun canUserInvite(userId: UserId): Result
- suspend fun canUserRedact(userId: UserId): Result
+ suspend fun canUserRedactOwn(userId: UserId): Result
+
+ suspend fun canUserRedactOther(userId: UserId): Result
suspend fun canUserSendState(userId: UserId, type: StateEventType): Result
@@ -219,6 +224,12 @@ interface MatrixRoom : Closeable {
progressCallback: ProgressCallback?
): Result
+ /**
+ * Send a typing notification.
+ * @param isTyping True if the user is typing, false otherwise.
+ */
+ suspend fun typingNotice(isTyping: Boolean): Result
+
/**
* Generates a Widget url to display in a [android.webkit.WebView] given the provided parameters.
* @param widgetSettings The widget settings to use.
@@ -241,7 +252,5 @@ interface MatrixRoom : Closeable {
*/
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result
- fun pollHistory(): MatrixTimeline
-
override fun close() = destroy()
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt
index 3a89f61d9d..c228a9cb2f 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt
@@ -36,6 +36,11 @@ suspend fun MatrixRoom.canSendState(type: StateEventType): Result = can
suspend fun MatrixRoom.canSendMessage(type: MessageEventType): Result = canUserSendMessage(sessionId, type)
/**
- * Shortcut for calling [MatrixRoom.canUserRedact] with our own user.
+ * Shortcut for calling [MatrixRoom.canUserRedactOwn] with our own user.
*/
-suspend fun MatrixRoom.canRedact(): Result = canUserRedact(sessionId)
+suspend fun MatrixRoom.canRedactOwn(): Result = canUserRedactOwn(sessionId)
+
+/**
+ * Shortcut for calling [MatrixRoom.canRedactOther] with our own user.
+ */
+suspend fun MatrixRoom.canRedactOther(): Result = canUserRedactOther(sessionId)
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 0a6aa7bbaa..cc584f08bb 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
@@ -36,13 +36,17 @@ sealed interface RoomSummary {
data class RoomSummaryDetails(
val roomId: RoomId,
val name: String,
- val canonicalAlias: String? = null,
+ val canonicalAlias: String?,
val isDirect: Boolean,
- val avatarURLString: String?,
+ val avatarUrl: String?,
val lastMessage: RoomMessage?,
- val lastMessageTimestamp: Long?,
- val unreadNotificationCount: Int,
- val inviter: RoomMember? = null,
- val notificationMode: RoomNotificationMode? = null,
- val hasOngoingCall: Boolean = false,
-)
+ val numUnreadMessages: Int,
+ val numUnreadMentions: Int,
+ val numUnreadNotifications: Int,
+ val inviter: RoomMember?,
+ val userDefinedNotificationMode: RoomNotificationMode?,
+ val hasRoomCall: Boolean,
+ val isDm: Boolean,
+) {
+ val lastMessageTimestamp = lastMessage?.originServerTs
+}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt
index fc4840d610..87e4f4ab73 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt
@@ -85,7 +85,11 @@ data class ProfileChangeContent(
data class StateContent(
val stateKey: String,
val content: OtherState
-) : EventContent
+) : EventContent {
+ fun isVisibleInTimeline(): Boolean {
+ return content.isVisibleInTimeline()
+ }
+}
data class FailedToParseMessageLikeContent(
val eventType: String,
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt
index 6960b3565d..e5119c388e 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt
@@ -41,4 +41,30 @@ sealed interface OtherState {
data object SpaceChild : OtherState
data object SpaceParent : OtherState
data class Custom(val eventType: String) : OtherState
+
+ fun isVisibleInTimeline() = when (this) {
+ // Visible
+ is RoomAvatar,
+ is RoomName,
+ is RoomTopic,
+ is RoomThirdPartyInvite,
+ is RoomCreate,
+ is RoomEncryption,
+ is Custom -> true
+ // Hidden
+ is RoomAliases,
+ is RoomCanonicalAlias,
+ is RoomGuestAccess,
+ is RoomHistoryVisibility,
+ is RoomJoinRules,
+ is RoomPinnedEvents,
+ is RoomPowerLevels,
+ is RoomServerAcl,
+ is RoomTombstone,
+ is SpaceChild,
+ is SpaceParent,
+ is PolicyRuleRoom,
+ is PolicyRuleServer,
+ is PolicyRuleUser -> false
+ }
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt
index 44664a256a..efbc2564a9 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt
@@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
+import io.element.android.libraries.matrix.api.core.UserId
@Immutable
sealed interface ProfileTimelineDetails {
@@ -34,3 +35,20 @@ sealed interface ProfileTimelineDetails {
val message: String
) : ProfileTimelineDetails
}
+
+/**
+ * Returns a disambiguated display name for the user.
+ * If the display name is null, or profile is not Ready, the user ID is returned.
+ * If the display name is ambiguous, the user ID is appended in parentheses.
+ * Otherwise, the display name is returned.
+ */
+fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String {
+ return when (this) {
+ is ProfileTimelineDetails.Ready -> when {
+ displayName == null -> userId.value
+ displayNameAmbiguous -> "$displayName ($userId)"
+ else -> displayName
+ }
+ else -> userId.value
+ }
+}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt
index 5cd6750552..114c7df0cb 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt
@@ -29,6 +29,7 @@ data class TracingFilterConfiguration(
Target.MATRIX_SDK_SLIDING_SYNC to LogLevel.TRACE,
Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.TRACE,
Target.MATRIX_SDK_UI_TIMELINE to LogLevel.TRACE,
+ Target.MATRIX_SDK_BASE_READ_RECEIPTS to LogLevel.TRACE,
)
fun getLogLevel(target: Target): LogLevel {
@@ -37,7 +38,7 @@ data class TracingFilterConfiguration(
val filter: String
get() {
- val fullMap = Target.values().associateWith {
+ val fullMap = Target.entries.associateWith {
overrides[it] ?: targetsToLogLevel[it] ?: defaultLogLevel
}
return fullMap.map {
@@ -64,6 +65,7 @@ enum class Target(open val filter: String) {
MATRIX_SDK_SLIDING_SYNC("matrix_sdk::sliding_sync"),
MATRIX_SDK_BASE_SLIDING_SYNC("matrix_sdk_base::sliding_sync"),
MATRIX_SDK_UI_TIMELINE("matrix_sdk_ui::timeline"),
+ MATRIX_SDK_BASE_READ_RECEIPTS("matrix_sdk_base::read_receipts"),
}
enum class LogLevel(open val filter: String) {
@@ -80,6 +82,12 @@ object TracingFilterConfigurations {
Target.ELEMENT to LogLevel.DEBUG
),
)
+ val nightly = TracingFilterConfiguration(
+ overrides = mapOf(
+ Target.ELEMENT to LogLevel.TRACE,
+ Target.MATRIX_SDK_BASE_READ_RECEIPTS to LogLevel.TRACE,
+ ),
+ )
val debug = TracingFilterConfiguration(
overrides = mapOf(
Target.ELEMENT to LogLevel.TRACE
@@ -89,7 +97,7 @@ object TracingFilterConfigurations {
/**
* Use this method to create a custom configuration where all targets will have the same log level.
*/
- fun custom(logLevel: LogLevel) = TracingFilterConfiguration(overrides = Target.values().associateWith { logLevel })
+ fun custom(logLevel: LogLevel) = TracingFilterConfiguration(overrides = Target.entries.associateWith { logLevel })
/**
* Use this method to override the log level of specific targets.
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/CurrentUser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/CurrentUser.kt
index 3968b058d9..56b61e4cde 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/CurrentUser.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/CurrentUser.kt
@@ -19,11 +19,11 @@ package io.element.android.libraries.matrix.api.user
import io.element.android.libraries.matrix.api.MatrixClient
/**
- * Get the current user, as [MatrixUser], using [MatrixClient.loadUserAvatarURLString]
+ * Get the current user, as [MatrixUser], using [MatrixClient.loadUserAvatarUrl]
* and [MatrixClient.loadUserDisplayName].
*/
suspend fun MatrixClient.getCurrentUser(): MatrixUser {
- val userAvatarUrl = loadUserAvatarURLString().getOrNull()
+ val userAvatarUrl = loadUserAvatarUrl().getOrNull()
val userDisplayName = loadUserDisplayName().getOrNull()
return MatrixUser(
userId = sessionId,
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt
index f0a22d0128..9e5d8a41ad 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt
@@ -21,6 +21,7 @@ import java.util.UUID
interface CallWidgetSettingsProvider {
fun provide(
baseUrl: String,
- widgetId: String = UUID.randomUUID().toString()
+ widgetId: String = UUID.randomUUID().toString(),
+ encrypted: Boolean,
): MatrixWidgetSettings
}
diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt
new file mode 100644
index 0000000000..77a4188a68
--- /dev/null
+++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.matrix.api.notification
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.test.AN_EVENT_ID
+import io.element.android.libraries.matrix.test.A_ROOM_ID
+import io.element.android.libraries.matrix.test.A_USER_ID
+import org.junit.Test
+
+class NotificationDataTest {
+ @Test
+ fun `getSenderName should return user id if there is no sender name`() {
+ val sut = aNotificationData(
+ senderDisplayName = null,
+ senderIsNameAmbiguous = false,
+ )
+ assertThat(sut.getSenderName(A_USER_ID)).isEqualTo("@alice:server.org")
+ }
+
+ @Test
+ fun `getSenderName should return sender name if defined`() {
+ val sut = aNotificationData(
+ senderDisplayName = "Alice",
+ senderIsNameAmbiguous = false,
+ )
+ assertThat(sut.getSenderName(A_USER_ID)).isEqualTo("Alice")
+ }
+
+ @Test
+ fun `getSenderName should return sender name and user id in case of ambiguous display name`() {
+ val sut = aNotificationData(
+ senderDisplayName = "Alice",
+ senderIsNameAmbiguous = true,
+ )
+ assertThat(sut.getSenderName(A_USER_ID)).isEqualTo("Alice (@alice:server.org)")
+ }
+
+ private fun aNotificationData(
+ senderDisplayName: String?,
+ senderIsNameAmbiguous: Boolean,
+ ): NotificationData {
+ return NotificationData(
+ eventId = AN_EVENT_ID,
+ roomId = A_ROOM_ID,
+ senderAvatarUrl = null,
+ senderDisplayName = senderDisplayName,
+ senderIsNameAmbiguous = senderIsNameAmbiguous,
+ roomAvatarUrl = null,
+ roomDisplayName = null,
+ isDirect = false,
+ isEncrypted = false,
+ isNoisy = false,
+ timestamp = 0L,
+ content = NotificationContent.MessageLike.RoomEncrypted,
+ hasMention = false,
+ )
+ }
+}
diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt
new file mode 100644
index 0000000000..e760cfa210
--- /dev/null
+++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.timeline.item.event
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.api.core.UserId
+import org.junit.Test
+
+private const val A_USER_ID = "@foo:example.org"
+private val aUserId = UserId(A_USER_ID)
+
+class ProfileTimelineDetailsTest {
+ @Test
+ fun `getDisambiguatedDisplayName of Unavailable should be equal to userId`() {
+ assertThat(ProfileTimelineDetails.Unavailable.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
+ }
+
+ @Test
+ fun `getDisambiguatedDisplayName of Error should be equal to userId`() {
+ assertThat(ProfileTimelineDetails.Error("An error").getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
+ }
+
+ @Test
+ fun `getDisambiguatedDisplayName of Pending should be equal to userId`() {
+ assertThat(ProfileTimelineDetails.Pending.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
+ }
+
+ @Test
+ fun `getDisambiguatedDisplayName of Ready without display name should be equal to userId`() {
+ assertThat(
+ ProfileTimelineDetails.Ready(
+ displayName = null,
+ displayNameAmbiguous = false,
+ avatarUrl = null,
+ ).getDisambiguatedDisplayName(aUserId)
+ ).isEqualTo(A_USER_ID)
+ }
+
+ @Test
+ fun `getDisambiguatedDisplayName of Ready with display name should be equal to display name`() {
+ assertThat(
+ ProfileTimelineDetails.Ready(
+ displayName = "Alice",
+ displayNameAmbiguous = false,
+ avatarUrl = null,
+ ).getDisambiguatedDisplayName(aUserId)
+ ).isEqualTo("Alice")
+ }
+
+ @Test
+ fun `getDisambiguatedDisplayName of Ready with display name and ambiguous should be equal to display name with user id`() {
+ assertThat(
+ ProfileTimelineDetails.Ready(
+ displayName = "Alice",
+ displayNameAmbiguous = true,
+ avatarUrl = null,
+ ).getDisambiguatedDisplayName(aUserId)
+ ).isEqualTo("Alice ($A_USER_ID)")
+ }
+}
diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts
index 41c84a0384..8815214dd6 100644
--- a/libraries/matrix/impl/build.gradle.kts
+++ b/libraries/matrix/impl/build.gradle.kts
@@ -53,4 +53,5 @@ dependencies {
testImplementation(libs.test.truth)
testImplementation(projects.libraries.matrix.test)
testImplementation(libs.coroutines.test)
+ testImplementation(libs.test.turbine)
}
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 94e0eb56e4..26bc775e55 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
@@ -76,11 +76,14 @@ import kotlinx.coroutines.withTimeout
import org.matrix.rustcomponents.sdk.BackupState
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
+import org.matrix.rustcomponents.sdk.FilterStateEventType
+import org.matrix.rustcomponents.sdk.FilterTimelineEventType
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
import org.matrix.rustcomponents.sdk.PowerLevels
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.TaskHandle
+import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.io.File
@@ -102,9 +105,11 @@ class RustMatrixClient(
private val clock: SystemClock,
) : MatrixClient {
override val sessionId: UserId = UserId(client.userId())
+ override val deviceId: String = client.deviceId()
+ override val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId")
+
private val innerRoomListService = syncService.roomListService()
private val sessionDispatcher = dispatchers.io.limitedParallelism(64)
- private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId")
private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope)
private val verificationService = RustSessionVerificationService(rustSyncService, sessionCoroutineScope)
private val pushersService = RustPushersService(
@@ -139,15 +144,25 @@ class RustMatrixClient(
// TODO handle isSoftLogout parameter.
appCoroutineScope.launch {
val existingData = sessionStore.getSession(client.userId())
+ val anonymizedToken = existingData?.accessToken?.takeLast(4)
+ Timber.d("Removing session data with token: '...$anonymizedToken'.")
if (existingData != null) {
// Set isTokenValid to false
val newData = client.session().toSessionData(
isTokenValid = false,
loginType = existingData.loginType,
+ passphrase = existingData.passphrase,
)
sessionStore.updateData(newData)
+ Timber.d("Removed session data with token: '...$anonymizedToken'.")
+ } else {
+ Timber.d("No session data found.")
}
doLogout(doRequest = false, removeSession = false, ignoreSdkError = false)
+ }.invokeOnCompletion {
+ if (it != null) {
+ Timber.e(it, "Failed to remove session data.")
+ }
}
} else {
Timber.v("didReceiveAuthError -> already cleaning up")
@@ -158,11 +173,19 @@ class RustMatrixClient(
Timber.w("didRefreshTokens()")
appCoroutineScope.launch {
val existingData = sessionStore.getSession(client.userId()) ?: return@launch
+ val anonymizedToken = client.session().accessToken.takeLast(4)
+ Timber.d("Saving new session data with token: '...$anonymizedToken'. Was token valid: ${existingData.isTokenValid}")
val newData = client.session().toSessionData(
- isTokenValid = existingData.isTokenValid,
+ isTokenValid = true,
loginType = existingData.loginType,
+ passphrase = existingData.passphrase,
)
sessionStore.updateData(newData)
+ Timber.d("Saved new session data with token: '...$anonymizedToken'.")
+ }.invokeOnCompletion {
+ if (it != null) {
+ Timber.e(it, "Failed to save new session data.")
+ }
}
}
}
@@ -178,6 +201,25 @@ class RustMatrixClient(
),
)
+ private val eventFilters = TimelineEventTypeFilter.exclude(
+ listOf(
+ FilterStateEventType.ROOM_ALIASES,
+ FilterStateEventType.ROOM_CANONICAL_ALIAS,
+ FilterStateEventType.ROOM_GUEST_ACCESS,
+ FilterStateEventType.ROOM_HISTORY_VISIBILITY,
+ FilterStateEventType.ROOM_JOIN_RULES,
+ FilterStateEventType.ROOM_PINNED_EVENTS,
+ FilterStateEventType.ROOM_POWER_LEVELS,
+ FilterStateEventType.ROOM_SERVER_ACL,
+ FilterStateEventType.ROOM_TOMBSTONE,
+ FilterStateEventType.SPACE_CHILD,
+ FilterStateEventType.SPACE_PARENT,
+ FilterStateEventType.POLICY_RULE_ROOM,
+ FilterStateEventType.POLICY_RULE_SERVER,
+ FilterStateEventType.POLICY_RULE_USER,
+ ).map(FilterTimelineEventType::State)
+ )
+
override val roomListService: RoomListService
get() = rustRoomListService
@@ -226,10 +268,14 @@ class RustMatrixClient(
}
}
- private fun pairOfRoom(roomId: RoomId): Pair? {
+ private suspend fun pairOfRoom(roomId: RoomId): Pair? {
val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value)
- // Keep using fullRoomBlocking for now as it's faster.
- val fullRoom = cachedRoomListItem?.fullRoomBlocking()
+ val fullRoom = cachedRoomListItem?.let { roomListItem ->
+ if (!roomListItem.isTimelineInitialized()) {
+ roomListItem.initTimeline(eventFilters)
+ }
+ roomListItem.fullRoom()
+ }
return if (cachedRoomListItem == null || fullRoom == null) {
Timber.d("No room cached for $roomId")
null
@@ -411,7 +457,7 @@ class RustMatrixClient(
}
}
- override suspend fun loadUserAvatarURLString(): Result = withContext(sessionDispatcher) {
+ override suspend fun loadUserAvatarUrl(): Result = withContext(sessionDispatcher) {
runCatching {
client.avatarUrl()
}
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 53aa560b97..0f1c47445b 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
@@ -44,6 +44,7 @@ class RustMatrixClientFactory @Inject constructor(
.basePath(baseDirectory.absolutePath)
.homeserverUrl(sessionData.homeserverUrl)
.username(sessionData.userId)
+ .passphrase(sessionData.passphrase)
.userAgent(userAgentProvider.provide())
// 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"))
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
index bfce2b3ec2..b777988783 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
@@ -19,6 +19,8 @@ package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
+import io.element.android.libraries.core.meta.BuildMeta
+import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
@@ -28,6 +30,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
import io.element.android.libraries.matrix.impl.exception.mapClientException
+import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator
import io.element.android.libraries.matrix.impl.mapper.toSessionData
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.libraries.sessionstorage.api.LoggedInState
@@ -39,6 +42,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.OidcAuthenticationData
import org.matrix.rustcomponents.sdk.use
+import timber.log.Timber
import java.io.File
import javax.inject.Inject
import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService
@@ -51,10 +55,15 @@ class RustMatrixAuthenticationService @Inject constructor(
private val sessionStore: SessionStore,
userAgentProvider: UserAgentProvider,
private val rustMatrixClientFactory: RustMatrixClientFactory,
+ private val passphraseGenerator: PassphraseGenerator,
+ private val buildMeta: BuildMeta,
) : MatrixAuthenticationService {
+ // Passphrase which will be used for new sessions. Existing sessions will use the passphrase
+ // stored in the SessionData.
+ private val pendingPassphrase = getDatabasePassphrase()
private val authService: RustAuthenticationService = RustAuthenticationService(
basePath = baseDirectory.absolutePath,
- passphrase = null,
+ passphrase = pendingPassphrase,
userAgent = userAgentProvider.provide(),
oidcConfiguration = oidcConfiguration,
customSlidingSyncProxy = null,
@@ -76,6 +85,12 @@ class RustMatrixAuthenticationService @Inject constructor(
val sessionData = sessionStore.getSession(sessionId.value)
if (sessionData != null) {
if (sessionData.isTokenValid) {
+ // Use the sessionData.passphrase, which can be null for a previously created session
+ if (sessionData.passphrase == null) {
+ Timber.w("Restoring a session without a passphrase")
+ } else {
+ Timber.w("Restoring a session with a passphrase")
+ }
rustMatrixClientFactory.create(sessionData)
} else {
error("Token is not valid")
@@ -88,6 +103,21 @@ class RustMatrixAuthenticationService @Inject constructor(
}
}
+ private fun getDatabasePassphrase(): String? {
+ // TODO Remove this if block at some point
+ // Return a passphrase only for debug and nightly build for now
+ if (buildMeta.buildType == BuildType.RELEASE) {
+ Timber.w("New sessions will not be encrypted with a passphrase (release build)")
+ return null
+ }
+
+ val passphrase = passphraseGenerator.generatePassphrase()
+ if (passphrase != null) {
+ Timber.w("New sessions will be encrypted with a passphrase")
+ }
+ return passphrase
+ }
+
override fun getHomeserverDetails(): StateFlow = currentHomeserver
override suspend fun setHomeserver(homeserver: String): Result =
@@ -111,6 +141,7 @@ class RustMatrixAuthenticationService @Inject constructor(
it.session().toSessionData(
isTokenValid = true,
loginType = LoginType.PASSWORD,
+ passphrase = pendingPassphrase,
)
}
sessionStore.storeData(sessionData)
@@ -158,6 +189,7 @@ class RustMatrixAuthenticationService @Inject constructor(
it.session().toSessionData(
isTokenValid = true,
loginType = LoginType.OIDC,
+ passphrase = pendingPassphrase
)
}
pendingOidcAuthenticationData?.close()
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt
index 71f01f0aac..17ea8ee444 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt
@@ -20,6 +20,7 @@ import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
@@ -27,6 +28,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
+import kotlinx.coroutines.CoroutineScope
@Module
@ContributesTo(SessionScope::class)
@@ -60,4 +62,10 @@ object SessionMatrixModule {
fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader {
return matrixClient.mediaLoader
}
+
+ @SessionCoroutineScope
+ @Provides
+ fun provideSessionCoroutineScope(matrixClient: MatrixClient): CoroutineScope {
+ return matrixClient.sessionCoroutineScope
+ }
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt
new file mode 100644
index 0000000000..7e72dfeb55
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.impl.keys
+
+import android.util.Base64
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.di.AppScope
+import java.security.SecureRandom
+import javax.inject.Inject
+
+private const val SECRET_SIZE = 256
+
+@ContributesBinding(AppScope::class)
+class DefaultPassphraseGenerator @Inject constructor() : PassphraseGenerator {
+ override fun generatePassphrase(): String? {
+ val key = ByteArray(size = SECRET_SIZE)
+ SecureRandom().nextBytes(key)
+ return Base64.encodeToString(key, Base64.NO_PADDING or Base64.NO_WRAP)
+ }
+}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt
new file mode 100644
index 0000000000..2b62a36d07
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.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
+ *
+ * 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.impl.keys
+
+interface PassphraseGenerator {
+ /**
+ * Generate a passphrase to encrypt the databases of a session.
+ * Return null to not encrypt the databases.
+ */
+ fun generatePassphrase(): String?
+}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt
index fe21a460c8..aea838b705 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt
@@ -24,6 +24,7 @@ import java.util.Date
internal fun Session.toSessionData(
isTokenValid: Boolean,
loginType: LoginType,
+ passphrase: String?,
) = SessionData(
userId = userId,
deviceId = deviceId,
@@ -35,4 +36,5 @@ internal fun Session.toSessionData(
loginTimestamp = Date(),
isTokenValid = isTokenValid,
loginType = loginType,
+ passphrase = passphrase,
)
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 6285ba393f..4b7c08f9b4 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
@@ -45,6 +45,7 @@ class NotificationMapper(
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 },
roomDisplayName = item.roomInfo.displayName,
isDirect = item.roomInfo.isDirect,
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt
index cc14fad815..6526120328 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt
@@ -18,7 +18,7 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.NotificationContent
-import io.element.android.libraries.matrix.impl.room.RoomMemberMapper
+import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper
import org.matrix.rustcomponents.sdk.MessageLikeEventContent
import org.matrix.rustcomponents.sdk.StateEventContent
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt
index 2eee0a324e..bfe2ef2c15 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt
@@ -143,6 +143,6 @@ class RustNotificationSettingsService(
override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result =
runCatching {
- notificationSettings.canHomeserverPushEncryptedEventToDevice()
+ notificationSettings.canPushEncryptedEventToDevice()
}
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt
index b2a13fbe4a..909649441f 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt
@@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
+import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.use
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 84ccacb75f..3fc7d65468 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
@@ -18,7 +18,6 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
-import io.element.android.libraries.core.coroutine.parallelMap
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId
@@ -39,7 +38,6 @@ import io.element.android.libraries.matrix.api.room.Mention
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.location.AssetType
-import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
@@ -51,20 +49,16 @@ import io.element.android.libraries.matrix.impl.media.toMSC3246range
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.location.toInner
-import io.element.android.libraries.matrix.impl.timeline.AsyncMatrixTimeline
+import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
-import io.element.android.libraries.matrix.impl.util.destroyAll
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.services.toolbox.api.systemclock.SystemClock
-import kotlinx.collections.immutable.toImmutableList
-import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -75,7 +69,6 @@ import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomInfoListener
import org.matrix.rustcomponents.sdk.RoomListItem
-import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
import org.matrix.rustcomponents.sdk.WidgetCapabilities
@@ -125,8 +118,8 @@ class RustMatrixRoom(
private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8)
private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId")
- private val _membersStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown)
private val _syncUpdateFlow = MutableStateFlow(0L)
+ private val roomMemberListFetcher = RoomMemberListFetcher(innerRoom, roomMembersDispatcher)
private val _roomNotificationSettingsStateFlow = MutableStateFlow(MatrixRoomNotificationSettingsState.Unknown)
override val roomNotificationSettingsStateFlow: StateFlow = _roomNotificationSettingsStateFlow
@@ -135,7 +128,7 @@ class RustMatrixRoom(
_syncUpdateFlow.value = systemClock.epochMillis()
}
- override val membersStateFlow: StateFlow = _membersStateFlow.asStateFlow()
+ override val membersStateFlow: StateFlow = roomMemberListFetcher.membersFlow
override val syncUpdateFlow: StateFlow = _syncUpdateFlow.asStateFlow()
@@ -192,35 +185,7 @@ class RustMatrixRoom(
override val activeMemberCount: Long
get() = innerRoom.activeMembersCount().toLong()
- override suspend fun updateMembers(): Result = withContext(roomMembersDispatcher) {
- val currentState = _membersStateFlow.value
- val currentMembers = currentState.roomMembers()?.toImmutableList()
- _membersStateFlow.value = MatrixRoomMembersState.Pending(prevRoomMembers = currentMembers)
- var rustMembers: List? = null
- try {
- rustMembers = innerRoom.members().use { membersIterator ->
- buildList {
- while (true) {
- // Loading the whole membersIterator as a stop-gap measure.
- // We should probably implement some sort of paging in the future.
- ensureActive()
- addAll(membersIterator.nextChunk(1000u) ?: break)
- }
- }
- }
- val mappedMembers = rustMembers.parallelMap(RoomMemberMapper::map)
- _membersStateFlow.value = MatrixRoomMembersState.Ready(mappedMembers.toImmutableList())
- Result.success(Unit)
- } catch (exception: CancellationException) {
- _membersStateFlow.value = MatrixRoomMembersState.Error(prevRoomMembers = currentMembers, failure = exception)
- throw exception
- } catch (exception: Exception) {
- _membersStateFlow.value = MatrixRoomMembersState.Error(prevRoomMembers = currentMembers, failure = exception)
- Result.failure(exception)
- } finally {
- rustMembers?.destroyAll()
- }
- }
+ override suspend fun updateMembers() = roomMemberListFetcher.fetchRoomMembers()
override suspend fun userDisplayName(userId: UserId): Result = withContext(roomDispatcher) {
runCatching {
@@ -335,9 +300,15 @@ class RustMatrixRoom(
}
}
- override suspend fun canUserRedact(userId: UserId): Result {
+ override suspend fun canUserRedactOwn(userId: UserId): Result {
return runCatching {
- innerRoom.canUserRedact(userId.value)
+ innerRoom.canUserRedactOwn(userId.value)
+ }
+ }
+
+ override suspend fun canUserRedactOther(userId: UserId): Result {
+ return runCatching {
+ innerRoom.canUserRedactOther(userId.value)
}
}
@@ -548,6 +519,10 @@ class RustMatrixRoom(
)
}
+ override suspend fun typingNotice(isTyping: Boolean) = runCatching {
+ innerRoom.typingNotice(isTyping)
+ }
+
override suspend fun generateWidgetWebViewUrl(
widgetSettings: MatrixWidgetSettings,
clientId: String,
@@ -569,14 +544,6 @@ class RustMatrixRoom(
)
}
- override fun pollHistory() = AsyncMatrixTimeline(
- coroutineScope = roomCoroutineScope,
- dispatcher = roomDispatcher
- ) {
- val innerTimeline = innerRoom.pollHistory()
- createMatrixTimeline(innerTimeline)
- }
-
private fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result {
return runCatching {
MediaUploadHandlerImpl(files, handle())
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt
new file mode 100644
index 0000000000..83850f574b
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.impl.room.member
+
+import io.element.android.libraries.core.coroutine.parallelMap
+import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.api.room.roomMembers
+import io.element.android.libraries.matrix.impl.util.destroyAll
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import org.matrix.rustcomponents.sdk.RoomInterface
+import org.matrix.rustcomponents.sdk.RoomMembersIterator
+import org.matrix.rustcomponents.sdk.use
+import timber.log.Timber
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.coroutines.coroutineContext
+
+/**
+ * This class fetches the room members for a given room in a 'paginated' way, and taking into account previous cached values.
+ */
+internal class RoomMemberListFetcher(
+ private val room: RoomInterface,
+ private val dispatcher: CoroutineDispatcher,
+ private val pageSize: Int = 1000,
+) {
+ private val updatedRoomMemberMutex = Mutex()
+ private val roomId = room.id()
+
+ private val _membersFlow = MutableStateFlow(MatrixRoomMembersState.Unknown)
+ val membersFlow: StateFlow = _membersFlow
+
+ /**
+ * Fetches the room members for the given room.
+ * It will emit the cached members first, and then the updated members in batches of [pageSize] items, through [membersFlow].
+ * @param withCache Whether to load the cached members first. Defaults to true.
+ */
+ suspend fun fetchRoomMembers(withCache: Boolean = true) {
+ if (updatedRoomMemberMutex.isLocked) {
+ Timber.i("Room members are already being updated for room $roomId")
+ return
+ }
+ updatedRoomMemberMutex.withLock {
+ withContext(dispatcher) {
+ // Load cached members as fallback and to get faster results
+ if (withCache) {
+ if (_membersFlow.value !is MatrixRoomMembersState.Ready) {
+ fetchCachedRoomMembers()
+ } else {
+ Timber.i("No need to load cached members found for room $roomId")
+ }
+ }
+
+ val prevRoomMembers = (_membersFlow.value as? MatrixRoomMembersState.Ready)?.roomMembers?.toImmutableList()
+ _membersFlow.value = MatrixRoomMembersState.Pending(prevRoomMembers = prevRoomMembers)
+
+ try {
+ // Start loading new members
+ parseAndEmitMembers(room.members())
+ } catch (exception: CancellationException) {
+ Timber.d("Cancelled loading updated members for room $roomId")
+ throw exception
+ } catch (exception: Exception) {
+ Timber.e(exception, "Failed to load updated members for room $roomId")
+ _membersFlow.value = MatrixRoomMembersState.Error(exception, prevRoomMembers)
+ }
+ }
+ }
+ }
+
+ internal suspend fun fetchCachedRoomMembers() = withContext(dispatcher) {
+ Timber.i("Loading cached members for room $roomId")
+ try {
+ val iterator = room.membersNoSync()
+ parseAndEmitMembers(iterator)
+ } catch (exception: CancellationException) {
+ Timber.d("Cancelled loading cached members for room $roomId")
+ throw exception
+ } catch (exception: Exception) {
+ Timber.e(exception, "Failed to load cached members for room $roomId")
+ _membersFlow.value = MatrixRoomMembersState.Error(exception, _membersFlow.value.roomMembers()?.toImmutableList())
+ }
+ }
+
+ private suspend fun parseAndEmitMembers(roomMembersIterator: RoomMembersIterator) {
+ roomMembersIterator.use { iterator ->
+ val results = buildList {
+ while (true) {
+ // Loading the whole membersIterator as a stop-gap measure.
+ // We should probably implement some sort of paging in the future.
+ coroutineContext.ensureActive()
+ val chunk = iterator.nextChunk(pageSize.toUInt())
+ val members = try {
+ // Load next chunk. If null (no more items), exit the loop
+ chunk?.parallelMap(RoomMemberMapper::map) ?: break
+ } finally {
+ // Make sure we clear all member references
+ chunk?.destroyAll()
+ }
+ addAll(members)
+ Timber.i("Emitting first $size members for room $roomId")
+ _membersFlow.value = MatrixRoomMembersState.Ready(toImmutableList())
+ }
+ }
+ if (results.isEmpty()) {
+ _membersFlow.value = MatrixRoomMembersState.Ready(results.toImmutableList())
+ }
+ }
+ }
+}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt
similarity index 94%
rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt
rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt
index 3354abbe16..e3d5b37cd4 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 New Vector Ltd
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.element.android.libraries.matrix.impl.room
+package io.element.android.libraries.matrix.impl.room.member
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
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 81507b6955..1b3e900f17 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
@@ -19,7 +19,7 @@ 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.RoomSummaryDetails
import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper
-import io.element.android.libraries.matrix.impl.room.RoomMemberMapper
+import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.use
@@ -34,13 +34,15 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
name = roomInfo.name ?: roomInfo.id,
canonicalAlias = roomInfo.canonicalAlias,
isDirect = roomInfo.isDirect,
- avatarURLString = roomInfo.avatarUrl,
- unreadNotificationCount = roomInfo.notificationCount.toInt(),
+ avatarUrl = roomInfo.avatarUrl,
+ numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
+ numUnreadMessages = roomInfo.numUnreadMessages.toInt(),
+ numUnreadNotifications = roomInfo.numUnreadNotifications.toInt(),
lastMessage = latestRoomMessage,
- lastMessageTimestamp = latestRoomMessage?.originServerTs,
inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
- notificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
- hasOngoingCall = roomInfo.hasRoomCall,
+ userDefinedNotificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
+ hasRoomCall = roomInfo.hasRoomCall,
+ isDm = roomInfo.isDirect && roomInfo.activeMembersCount.toLong() == 2L,
)
}
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt
index 0cfb8dfc26..2d6fbd253b 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt
@@ -38,6 +38,7 @@ import timber.log.Timber
/**
* This class is a wrapper around a [MatrixTimeline] that will be created asynchronously.
*/
+@Suppress("unused")
class AsyncMatrixTimeline(
coroutineScope: CoroutineScope,
dispatcher: CoroutineDispatcher,
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 8eb2f999d1..f8109259d5 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
@@ -27,13 +27,13 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
-import org.matrix.rustcomponents.sdk.BackPaginationStatus
import org.matrix.rustcomponents.sdk.BackPaginationStatusListener
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.TimelineListener
import timber.log.Timber
+import uniffi.matrix_sdk_ui.BackPaginationStatus
internal fun Timeline.timelineDiffFlow(onInitialList: suspend (List) -> Unit): Flow> =
callbackFlow {
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt
index 80c8f1d391..7b1e7d0500 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt
@@ -27,6 +27,8 @@ import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessage
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
+import io.element.android.libraries.matrix.impl.timeline.postprocessor.DmBeginningTimelineProcessor
+import io.element.android.libraries.matrix.impl.timeline.postprocessor.FilterHiddenStateEventsProcessor
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -44,13 +46,13 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import org.matrix.rustcomponents.sdk.BackPaginationStatus
-import org.matrix.rustcomponents.sdk.EventItemOrigin
import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber
+import uniffi.matrix_sdk_ui.BackPaginationStatus
+import uniffi.matrix_sdk_ui.EventItemOrigin
import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
@@ -82,6 +84,10 @@ class RustMatrixTimeline(
dispatcher = dispatcher,
)
+ private val filterHiddenStateEventsProcessor = FilterHiddenStateEventsProcessor()
+
+ private val dmBeginningTimelineProcessor = DmBeginningTimelineProcessor()
+
private val timelineItemFactory = MatrixTimelineItemMapper(
fetchDetailsForEvent = this::fetchDetailsForEvent,
roomCoroutineScope = roomCoroutineScope,
@@ -101,9 +107,16 @@ class RustMatrixTimeline(
override val paginationState: StateFlow = _paginationState.asStateFlow()
@OptIn(ExperimentalCoroutinesApi::class)
- override val timelineItems: Flow> = _timelineItems.mapLatest { items ->
- encryptedHistoryPostProcessor.process(items)
- }
+ override val timelineItems: Flow> = _timelineItems
+ .mapLatest { items -> encryptedHistoryPostProcessor.process(items) }
+ .mapLatest { items -> filterHiddenStateEventsProcessor.process(items) }
+ .mapLatest { items ->
+ dmBeginningTimelineProcessor.process(
+ items = items,
+ isDm = matrixRoom.isDirect && matrixRoom.isOneToOne,
+ isAtStartOfTimeline = paginationState.value.beginningOfRoomReached
+ )
+ }
init {
Timber.d("Initialize timeline for room ${matrixRoom.roomId}")
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt
index 59165463bb..406d9615ce 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt
@@ -16,10 +16,10 @@
package io.element.android.libraries.matrix.impl.timeline
-import org.matrix.rustcomponents.sdk.EventItemOrigin
import org.matrix.rustcomponents.sdk.TimelineChange
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
+import uniffi.matrix_sdk_ui.EventItemOrigin
/**
* Tries to get an event origin from the TimelineDiff.
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt
index 1cfbb11e02..ed56d4a8d0 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt
@@ -31,12 +31,12 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.Reaction
-import org.matrix.rustcomponents.sdk.EventItemOrigin as RustEventItemOrigin
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo
import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails
import org.matrix.rustcomponents.sdk.Receipt as RustReceipt
+import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin
class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) {
fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use {
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessor.kt
new file mode 100644
index 0000000000..f36b4a78b8
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessor.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.impl.timeline.postprocessor
+
+import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
+import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
+import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
+import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+
+/**
+ * This timeline post-processor removes the room creation event and the self-join event from the timeline for DMs.
+ */
+class DmBeginningTimelineProcessor {
+ fun process(
+ items: List,
+ isDm: Boolean,
+ isAtStartOfTimeline: Boolean
+ ): List {
+ if (!isDm || !isAtStartOfTimeline) return items
+
+ // Find room creation event. This is usually index 0
+ val roomCreationEventIndex = items.indexOfFirst {
+ val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent
+ stateEventContent?.content is OtherState.RoomCreate
+ }
+
+ // Find self-join event for room creator. This is usually index 1
+ val roomCreatorUserId = (items.getOrNull(roomCreationEventIndex) as? MatrixTimelineItem.Event)?.event?.sender
+ val selfUserJoinedEventIndex = roomCreatorUserId?.let { creatorUserId ->
+ items.indexOfFirst {
+ val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? RoomMembershipContent
+ stateEventContent?.change == MembershipChange.JOINED && stateEventContent.userId == creatorUserId
+ }
+ } ?: -1
+
+ // Remove items at the indices we found
+ val newItems = items.toMutableList()
+ if (selfUserJoinedEventIndex in newItems.indices) {
+ newItems.removeAt(selfUserJoinedEventIndex)
+ }
+ if (roomCreationEventIndex in newItems.indices) {
+ newItems.removeAt(roomCreationEventIndex)
+ }
+ return newItems
+ }
+}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessor.kt
new file mode 100644
index 0000000000..c54221d84e
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessor.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
+ *
+ * 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.impl.timeline.postprocessor
+
+import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+
+/**
+ * This class is used to filter out 'hidden' state events from the timeline.
+ */
+class FilterHiddenStateEventsProcessor {
+ fun process(items: List): List {
+ return items.filter { item ->
+ when (item) {
+ is MatrixTimelineItem.Event -> {
+ when (val content = item.event.content) {
+ // If it's a state event, make sure it's visible
+ is StateContent -> content.isVisibleInTimeline()
+ // We can display any other event
+ else -> true
+ }
+ }
+ is MatrixTimelineItem.Virtual -> true
+ is MatrixTimelineItem.Other -> true
+ }
+ }
+ }
+}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt
index a337adff17..44c73ae9d3 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt
@@ -27,7 +27,7 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultCallWidgetSettingsProvider @Inject constructor() : CallWidgetSettingsProvider {
- override fun provide(baseUrl: String, widgetId: String): MatrixWidgetSettings {
+ override fun provide(baseUrl: String, widgetId: String, encrypted: Boolean): MatrixWidgetSettings {
val options = VirtualElementCallWidgetOptions(
elementCallUrl = baseUrl,
widgetId = widgetId,
@@ -40,7 +40,7 @@ class DefaultCallWidgetSettingsProvider @Inject constructor() : CallWidgetSettin
confineToRoom = true,
font = null,
analyticsId = null,
- encryption = EncryptionSystem.PerParticipantKeys,
+ encryption = if (encrypted) EncryptionSystem.PerParticipantKeys else EncryptionSystem.Unencrypted,
)
val rustWidgetSettings = newVirtualElementCallWidget(options)
return MatrixWidgetSettings.fromRustWidgetSettings(rustWidgetSettings)
diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt
new file mode 100644
index 0000000000..1c153435db
--- /dev/null
+++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt
@@ -0,0 +1,303 @@
+/*
+ * 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.impl.room.member
+
+import app.cash.turbine.test
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.api.room.roomMembers
+import io.element.android.libraries.matrix.test.A_ROOM_ID
+import io.element.android.libraries.matrix.test.A_USER_ID
+import io.element.android.libraries.matrix.test.A_USER_ID_2
+import io.element.android.libraries.matrix.test.A_USER_ID_3
+import io.element.android.libraries.matrix.test.A_USER_ID_4
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.matrix.rustcomponents.sdk.MembershipState
+import org.matrix.rustcomponents.sdk.NoPointer
+import org.matrix.rustcomponents.sdk.Room
+import org.matrix.rustcomponents.sdk.RoomMember
+import org.matrix.rustcomponents.sdk.RoomMembersIterator
+
+class RoomMemberListFetcherTest {
+ @Test
+ fun `fetchCachedRoomMembers - emits cached members, if any`() = runTest {
+ val room = FakeRustRoom(getMembersNoSync = {
+ FakeRoomMembersIterator(
+ listOf(
+ FakeRustRoomMember(A_USER_ID),
+ FakeRustRoomMember(A_USER_ID_2),
+ FakeRustRoomMember(A_USER_ID_3),
+ )
+ )
+ })
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ fetcher.membersFlow.test {
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+
+ fetcher.fetchCachedRoomMembers()
+
+ val readyItem = awaitItem()
+ assertThat(readyItem).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
+ assertThat((readyItem as? MatrixRoomMembersState.Ready)?.roomMembers?.size).isEqualTo(3)
+ }
+ }
+
+ @Test
+ fun `fetchCachedRoomMembers - emits empty list, if no members exist`() = runTest {
+ val room = FakeRustRoom(getMembersNoSync = {
+ FakeRoomMembersIterator(emptyList())
+ })
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ fetcher.membersFlow.test {
+ fetcher.fetchCachedRoomMembers()
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+ assertThat(awaitItem().roomMembers()).isEmpty()
+ }
+ }
+
+ @Test
+ fun `fetchCachedRoomMembers - emits Error on error found`() = runTest {
+ val room = FakeRustRoom(getMembersNoSync = {
+ error("Some unexpected issue")
+ })
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ fetcher.membersFlow.test {
+ fetcher.fetchCachedRoomMembers()
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Error::class.java)
+ }
+ }
+
+ @Test
+ fun `fetchCachedRoomMembers - emits items using page size`() = runTest {
+ val room = FakeRustRoom(getMembersNoSync = {
+ FakeRoomMembersIterator(
+ listOf(
+ FakeRustRoomMember(A_USER_ID),
+ FakeRustRoomMember(A_USER_ID_2),
+ FakeRustRoomMember(A_USER_ID_3),
+ )
+ )
+ })
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default, pageSize = 2)
+ fetcher.membersFlow.test {
+ fetcher.fetchCachedRoomMembers()
+
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+ assertThat((awaitItem() as? MatrixRoomMembersState.Ready)?.roomMembers?.size).isEqualTo(2)
+ assertThat((awaitItem() as? MatrixRoomMembersState.Ready)?.roomMembers?.size).isEqualTo(3)
+
+ ensureAllEventsConsumed()
+ }
+ }
+
+ @Test
+ fun `fetchRoomMembers - with 'withCache' set to false emits only new members, if any`() = runTest {
+ val room = FakeRustRoom(getMembers = {
+ FakeRoomMembersIterator(
+ listOf(
+ FakeRustRoomMember(A_USER_ID),
+ FakeRustRoomMember(A_USER_ID_2),
+ FakeRustRoomMember(A_USER_ID_3),
+ )
+ )
+ })
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ fetcher.membersFlow.test {
+ fetcher.fetchRoomMembers(withCache = false)
+
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
+ assertThat((awaitItem() as? MatrixRoomMembersState.Ready)?.roomMembers?.size).isEqualTo(3)
+ }
+ }
+
+ @Test
+ fun `fetchRoomMembers - on error it emits an Error item`() = runTest {
+ val room = FakeRustRoom(getMembers = { error("An unexpected error") })
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ fetcher.membersFlow.test {
+ fetcher.fetchRoomMembers(withCache = false)
+
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Error::class.java)
+ }
+ }
+
+ @Test
+ fun `fetchRoomMembers - with 'withCache' returns cached items first, then new ones`() = runTest {
+ val room = FakeRustRoom(
+ getMembersNoSync = {
+ FakeRoomMembersIterator(listOf(FakeRustRoomMember(A_USER_ID_4)))
+ },
+ getMembers = {
+ FakeRoomMembersIterator(
+ listOf(
+ FakeRustRoomMember(A_USER_ID),
+ FakeRustRoomMember(A_USER_ID_2),
+ FakeRustRoomMember(A_USER_ID_3),
+ )
+ )
+ }
+ )
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ fetcher.membersFlow.test {
+ fetcher.fetchRoomMembers(withCache = true)
+ // Initial
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
+ // Loaded cached
+ awaitItem().let { cached ->
+ assertThat(cached).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
+ assertThat(cached.roomMembers()).hasSize(1)
+ }
+ // Start loading new
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
+ awaitItem().let { ready ->
+ assertThat(ready).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
+ assertThat(ready.roomMembers()).hasSize(3)
+ }
+ }
+ }
+
+ @Test
+ fun `fetchRoomMembers - with 'withCache' skips cache if there is already a ready state`() = runTest {
+ val room = FakeRustRoom(
+ getMembersNoSync = {
+ FakeRoomMembersIterator(listOf(FakeRustRoomMember(A_USER_ID_4)))
+ },
+ getMembers = {
+ FakeRoomMembersIterator(
+ listOf(
+ FakeRustRoomMember(A_USER_ID),
+ FakeRustRoomMember(A_USER_ID_2),
+ FakeRustRoomMember(A_USER_ID_3),
+ )
+ )
+ }
+ )
+
+ val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
+ // Set a ready state
+ fetcher.fetchRoomMembers(withCache = false)
+
+ fetcher.membersFlow.test {
+ // Start loading new members
+ fetcher.fetchRoomMembers(withCache = true)
+ // Previous ready state
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
+ // New pending state
+ assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
+ // New ready state
+ awaitItem().let { ready ->
+ assertThat(ready).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
+ assertThat(ready.roomMembers()).hasSize(3)
+ }
+ }
+ }
+}
+
+class FakeRustRoom(
+ private val getMembers: () -> RoomMembersIterator = { FakeRoomMembersIterator() },
+ private val getMembersNoSync: () -> RoomMembersIterator = { FakeRoomMembersIterator() },
+) : Room(NoPointer) {
+ override fun id(): String {
+ return A_ROOM_ID.value
+ }
+
+ override suspend fun members(): RoomMembersIterator {
+ return getMembers()
+ }
+
+ override suspend fun membersNoSync(): RoomMembersIterator {
+ return getMembersNoSync()
+ }
+
+ override fun close() {
+ // No-op
+ }
+}
+
+class FakeRoomMembersIterator(
+ private var members: List? = null
+) : RoomMembersIterator(NoPointer) {
+ override fun len(): UInt {
+ return members?.size?.toUInt() ?: 0u
+ }
+
+ override fun nextChunk(chunkSize: UInt): List? {
+ if (members?.isEmpty() == true) {
+ return null
+ }
+ return members?.let {
+ val result = it.take(chunkSize.toInt())
+ members = it.subList(result.size, it.size)
+ result
+ }
+ }
+}
+
+class FakeRustRoomMember(
+ private val userId: UserId,
+ private val displayName: String? = null,
+ private val avatarUrl: String? = null,
+ private val membership: MembershipState = MembershipState.JOIN,
+ private val isNameAmbiguous: Boolean = false,
+ private val powerLevel: Long = 0L,
+) : RoomMember(NoPointer) {
+ override fun userId(): String {
+ return userId.value
+ }
+
+ override fun displayName(): String? {
+ return displayName
+ }
+
+ override fun avatarUrl(): String? {
+ return avatarUrl
+ }
+
+ override fun membership(): MembershipState {
+ return membership
+ }
+
+ override fun isNameAmbiguous(): Boolean {
+ return isNameAmbiguous
+ }
+
+ override fun powerLevel(): Long {
+ return powerLevel
+ }
+
+ override fun normalizedPowerLevel(): Long {
+ return powerLevel
+ }
+
+ override fun isIgnored(): Boolean {
+ return false
+ }
+}
diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessorTest.kt
new file mode 100644
index 0000000000..fc24f54c14
--- /dev/null
+++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessorTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.impl.timeline.postprocessor
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
+import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
+import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
+import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+import io.element.android.libraries.matrix.test.A_USER_ID
+import io.element.android.libraries.matrix.test.A_USER_ID_2
+import io.element.android.libraries.matrix.test.timeline.aMessageContent
+import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
+import org.junit.Test
+
+class DmBeginningTimelineProcessorTest {
+ @Test
+ fun `processor removes room creation event and self-join event from DM timeline`() {
+ val timelineItems = listOf(
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
+ )
+ val processor = DmBeginningTimelineProcessor()
+ val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = true)
+ assertThat(processedItems).isEmpty()
+ }
+
+ @Test
+ fun `processor removes room creation event and self-join event from DM timeline even if they're not the first items`() {
+ val timelineItems = listOf(
+ MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
+ MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
+ )
+ val expected = listOf(
+ MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
+ MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
+ )
+ val processor = DmBeginningTimelineProcessor()
+ val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = true)
+ assertThat(processedItems).isEqualTo(expected)
+ }
+
+ @Test
+ fun `processor won't remove items if it's not a DM`() {
+ val timelineItems = listOf(
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
+ )
+ val processor = DmBeginningTimelineProcessor()
+ val processedItems = processor.process(timelineItems, isDm = false, isAtStartOfTimeline = true)
+ assertThat(processedItems).isEqualTo(timelineItems)
+ }
+
+ @Test
+ fun `processor won't remove items if it's not at the start of the timeline`() {
+ val timelineItems = listOf(
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
+ )
+ val processor = DmBeginningTimelineProcessor()
+ val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = false)
+ assertThat(processedItems).isEqualTo(timelineItems)
+ }
+
+ @Test
+ fun `processor won't remove the first member join event if it can't find the room creation event`() {
+ val timelineItems = listOf(
+ MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
+ )
+ val processor = DmBeginningTimelineProcessor()
+ val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = false)
+ assertThat(processedItems).isEqualTo(timelineItems)
+ }
+
+ @Test
+ fun `processor won't remove the first member join event if it's not from the room creator`() {
+ val timelineItems = listOf(
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
+ )
+ val processor = DmBeginningTimelineProcessor()
+ val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = false)
+ assertThat(processedItems).isEqualTo(timelineItems)
+ }
+}
diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessorTest.kt
new file mode 100644
index 0000000000..0532d1b762
--- /dev/null
+++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.impl.timeline.postprocessor
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
+import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
+import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
+import org.junit.Test
+
+class FilterHiddenStateEventsProcessorTest {
+ @Test
+ fun test() {
+ val items = listOf(
+ // These are visible because they're not state events
+ MatrixTimelineItem.Other,
+ MatrixTimelineItem.Virtual("virtual", VirtualTimelineItem.ReadMarker),
+ MatrixTimelineItem.Event("event", anEventTimelineItem()),
+ // These are visible state events
+ MatrixTimelineItem.Event("m.room.avatar", anEventTimelineItem(content = StateContent("", OtherState.RoomAvatar("")))),
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.encrypted", anEventTimelineItem(content = StateContent("", OtherState.RoomEncryption))),
+ MatrixTimelineItem.Event("m.room.name", anEventTimelineItem(content = StateContent("", OtherState.RoomName("")))),
+ MatrixTimelineItem.Event("m.room.third_party_invite", anEventTimelineItem(content = StateContent("", OtherState.RoomThirdPartyInvite("")))),
+ MatrixTimelineItem.Event("m.room.topic", anEventTimelineItem(content = StateContent("", OtherState.RoomTopic("")))),
+ MatrixTimelineItem.Event("m.room.custom", anEventTimelineItem(content = StateContent("", OtherState.Custom("")))),
+ // These ones are hidden
+ MatrixTimelineItem.Event("m.room.aliases", anEventTimelineItem(content = StateContent("", OtherState.RoomAliases))),
+ MatrixTimelineItem.Event("m.room.canonical_alias", anEventTimelineItem(content = StateContent("", OtherState.RoomCanonicalAlias))),
+ MatrixTimelineItem.Event("m.room.guest_access", anEventTimelineItem(content = StateContent("", OtherState.RoomGuestAccess))),
+ MatrixTimelineItem.Event("m.room.history_visibility", anEventTimelineItem(content = StateContent("", OtherState.RoomHistoryVisibility))),
+ MatrixTimelineItem.Event("m.room.join_rules", anEventTimelineItem(content = StateContent("", OtherState.RoomJoinRules))),
+ MatrixTimelineItem.Event("m.room.pinned_events", anEventTimelineItem(content = StateContent("", OtherState.RoomPinnedEvents))),
+ MatrixTimelineItem.Event("m.room.power_levels", anEventTimelineItem(content = StateContent("", OtherState.RoomPowerLevels))),
+ MatrixTimelineItem.Event("m.room.server_acl", anEventTimelineItem(content = StateContent("", OtherState.RoomServerAcl))),
+ MatrixTimelineItem.Event("m.room.tombstone", anEventTimelineItem(content = StateContent("", OtherState.RoomTombstone))),
+ MatrixTimelineItem.Event("m.space.child", anEventTimelineItem(content = StateContent("", OtherState.SpaceChild))),
+ MatrixTimelineItem.Event("m.space.parent", anEventTimelineItem(content = StateContent("", OtherState.SpaceParent))),
+ MatrixTimelineItem.Event("m.room.policy.rule.room", anEventTimelineItem(content = StateContent("", OtherState.PolicyRuleRoom))),
+ MatrixTimelineItem.Event("m.room.policy.rule.server", anEventTimelineItem(content = StateContent("", OtherState.PolicyRuleServer))),
+ MatrixTimelineItem.Event("m.room.policy.rule.user", anEventTimelineItem(content = StateContent("", OtherState.PolicyRuleUser))),
+ )
+
+ val expected = listOf(
+ MatrixTimelineItem.Other,
+ MatrixTimelineItem.Virtual("virtual", VirtualTimelineItem.ReadMarker),
+ MatrixTimelineItem.Event("event", anEventTimelineItem()),
+ MatrixTimelineItem.Event("m.room.avatar", anEventTimelineItem(content = StateContent("", OtherState.RoomAvatar("")))),
+ MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(content = StateContent("", OtherState.RoomCreate))),
+ MatrixTimelineItem.Event("m.room.encrypted", anEventTimelineItem(content = StateContent("", OtherState.RoomEncryption))),
+ MatrixTimelineItem.Event("m.room.name", anEventTimelineItem(content = StateContent("", OtherState.RoomName("")))),
+ MatrixTimelineItem.Event("m.room.third_party_invite", anEventTimelineItem(content = StateContent("", OtherState.RoomThirdPartyInvite("")))),
+ MatrixTimelineItem.Event("m.room.topic", anEventTimelineItem(content = StateContent("", OtherState.RoomTopic("")))),
+ MatrixTimelineItem.Event("m.room.custom", anEventTimelineItem(content = StateContent("", OtherState.Custom("")))),
+ )
+
+ val processor = FilterHiddenStateEventsProcessor()
+
+ assertThat(processor.process(items)).isEqualTo(expected)
+ }
+}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
index a8d1d09818..1684a1715d 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
@@ -43,12 +43,16 @@ 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
import io.element.android.tests.testutils.simulateLongTask
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.test.TestScope
class FakeMatrixClient(
override val sessionId: SessionId = A_SESSION_ID,
+ override val deviceId: String = "A_DEVICE_ID",
+ override val sessionCoroutineScope: CoroutineScope = TestScope(),
private val userDisplayName: Result = Result.success(A_USER_NAME),
- private val userAvatarURLString: Result = Result.success(AN_AVATAR_URL),
+ private val userAvatarUrl: Result = Result.success(AN_AVATAR_URL),
override val roomListService: RoomListService = FakeRoomListService(),
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
@@ -135,8 +139,8 @@ class FakeMatrixClient(
return userDisplayName
}
- override suspend fun loadUserAvatarURLString(): Result {
- return userAvatarURLString
+ override suspend fun loadUserAvatarUrl(): Result {
+ return userAvatarUrl
}
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result {
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt
index d048101f87..f3a704ddc5 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt
@@ -28,7 +28,6 @@ fun aBuildMeta(
versionName: String = "",
versionCode: Int = 0,
gitRevision: String = "",
- gitRevisionDate: String = "",
gitBranchName: String = "",
flavorDescription: String = "",
flavorShortDescription: String = "",
@@ -41,7 +40,6 @@ fun aBuildMeta(
versionName,
versionCode,
gitRevision,
- gitRevisionDate,
gitBranchName,
flavorDescription,
flavorShortDescription
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 9c12ba1f4f..c33a6d43b2 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
@@ -78,7 +78,8 @@ class FakeMatrixRoom(
override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
- canRedact: Boolean = false,
+ canRedactOwn: Boolean = false,
+ canRedactOther: Boolean = false,
) : MatrixRoom {
private var ignoreResult: Result = Result.success(Unit)
private var unignoreResult: Result = Result.success(Unit)
@@ -88,7 +89,8 @@ class FakeMatrixRoom(
private var joinRoomResult = Result.success(Unit)
private var inviteUserResult = Result.success(Unit)
private var canInviteResult = Result.success(true)
- private var canRedactResult = Result.success(canRedact)
+ private var canRedactOwnResult = Result.success(canRedactOwn)
+ private var canRedactOtherResult = Result.success(canRedactOther)
private val canSendStateResults = mutableMapOf>()
private val canSendEventResults = mutableMapOf>()
private var sendMediaResult = Result.success(FakeMediaUploadHandler())
@@ -113,6 +115,9 @@ class FakeMatrixRoom(
private var canUserJoinCallResult: Result = Result.success(true)
var sendMessageMentions = emptyList()
val editMessageCalls = mutableListOf>()
+ private val _typingRecord = mutableListOf()
+ val typingRecord: List
+ get() = _typingRecord
var sendMediaCount = 0
private set
@@ -161,7 +166,7 @@ class FakeMatrixRoom(
private var leaveRoomError: Throwable? = null
- private val _roomInfoFlow: MutableSharedFlow = MutableStateFlow(aRoomInfo())
+ private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1)
override val roomInfoFlow: Flow = _roomInfoFlow
override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown)
@@ -169,9 +174,7 @@ class FakeMatrixRoom(
override val roomNotificationSettingsStateFlow: MutableStateFlow =
MutableStateFlow(MatrixRoomNotificationSettingsState.Unknown)
- override suspend fun updateMembers(): Result = simulateLongTask {
- updateMembersResult
- }
+ override suspend fun updateMembers() = Unit
override suspend fun updateRoomNotificationSettings(): Result = simulateLongTask {
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
@@ -276,8 +279,12 @@ class FakeMatrixRoom(
return canInviteResult
}
- override suspend fun canUserRedact(userId: UserId): Result {
- return canRedactResult
+ override suspend fun canUserRedactOwn(userId: UserId): Result {
+ return canRedactOwnResult
+ }
+
+ override suspend fun canUserRedactOther(userId: UserId): Result {
+ return canRedactOtherResult
}
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result {
@@ -422,6 +429,11 @@ class FakeMatrixRoom(
progressCallback: ProgressCallback?
): Result = fakeSendMedia(progressCallback)
+ override suspend fun typingNotice(isTyping: Boolean): Result {
+ _typingRecord += isTyping
+ return Result.success(Unit)
+ }
+
override suspend fun generateWidgetWebViewUrl(
widgetSettings: MatrixWidgetSettings,
clientId: String,
@@ -431,10 +443,6 @@ class FakeMatrixRoom(
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result = getWidgetDriverResult
- override fun pollHistory(): MatrixTimeline {
- return FakeMatrixTimeline()
- }
-
fun givenLeaveRoomError(throwable: Throwable?) {
this.leaveRoomError = throwable
}
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 79d3acb58b..2bd04894c0 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
@@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.libraries.matrix.api.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
@@ -34,42 +35,52 @@ fun aRoomSummaryFilled(
roomId: RoomId = A_ROOM_ID,
name: String = A_ROOM_NAME,
isDirect: Boolean = false,
- avatarURLString: String? = null,
+ avatarUrl: String? = null,
lastMessage: RoomMessage? = aRoomMessage(),
- lastMessageTimestamp: Long? = null,
- unreadNotificationCount: Int = 2,
+ numUnreadMentions: Int = 1,
+ numUnreadMessages: Int = 2,
notificationMode: RoomNotificationMode? = null,
) = RoomSummary.Filled(
- aRoomSummaryDetail(
+ aRoomSummaryDetails(
roomId = roomId,
name = name,
isDirect = isDirect,
- avatarURLString = avatarURLString,
+ avatarUrl = avatarUrl,
lastMessage = lastMessage,
- lastMessageTimestamp = lastMessageTimestamp,
- unreadNotificationCount = unreadNotificationCount,
+ numUnreadMentions = numUnreadMentions,
+ numUnreadMessages = numUnreadMessages,
notificationMode = notificationMode,
)
)
-fun aRoomSummaryDetail(
+fun aRoomSummaryDetails(
roomId: RoomId = A_ROOM_ID,
name: String = A_ROOM_NAME,
isDirect: Boolean = false,
- avatarURLString: String? = null,
+ avatarUrl: String? = null,
lastMessage: RoomMessage? = aRoomMessage(),
- lastMessageTimestamp: Long? = null,
- unreadNotificationCount: Int = 2,
+ numUnreadMentions: Int = 0,
+ numUnreadMessages: Int = 0,
+ numUnreadNotifications: Int = 0,
notificationMode: RoomNotificationMode? = null,
+ inviter: RoomMember? = null,
+ canonicalAlias: String? = null,
+ hasRoomCall: Boolean = false,
+ isDm: Boolean = false,
) = RoomSummaryDetails(
roomId = roomId,
name = name,
isDirect = isDirect,
- avatarURLString = avatarURLString,
+ avatarUrl = avatarUrl,
lastMessage = lastMessage,
- lastMessageTimestamp = lastMessageTimestamp,
- unreadNotificationCount = unreadNotificationCount,
- notificationMode = notificationMode
+ numUnreadMentions = numUnreadMentions,
+ numUnreadMessages = numUnreadMessages,
+ numUnreadNotifications = numUnreadNotifications,
+ userDefinedNotificationMode = notificationMode,
+ inviter = inviter,
+ canonicalAlias = canonicalAlias,
+ hasRoomCall = hasRoomCall,
+ isDm = isDm,
)
fun aRoomMessage(
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt
index 5530bb8d13..83ea98df6d 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt
@@ -39,7 +39,7 @@ class FakeMatrixTimeline(
private val _paginationState: MutableStateFlow = MutableStateFlow(initialPaginationState)
private val _timelineItems: MutableStateFlow> = MutableStateFlow(initialTimelineItems)
- var sendReadReceiptCount = 0
+ var sentReadReceipts = mutableListOf>()
private set
var sendReadReceiptLatch: CompletableDeferred? = null
@@ -81,7 +81,7 @@ class FakeMatrixTimeline(
eventId: EventId,
receiptType: ReceiptType,
): Result = simulateLongTask {
- sendReadReceiptCount++
+ sentReadReceipts.add(eventId to receiptType)
sendReadReceiptLatch?.complete(Unit)
Result.success(Unit)
}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt
index d2be886ea6..ca198129f6 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt
@@ -24,7 +24,7 @@ class FakeCallWidgetSettingsProvider(
) : CallWidgetSettingsProvider {
val providedBaseUrls = mutableListOf()
- override fun provide(baseUrl: String, widgetId: String): MatrixWidgetSettings {
+ override fun provide(baseUrl: String, widgetId: String, encrypted: Boolean): MatrixWidgetSettings {
providedBaseUrls += baseUrl
return provideFn(baseUrl, widgetId)
}
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableMatrixUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableMatrixUserRow.kt
deleted file mode 100644
index 805e240194..0000000000
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableMatrixUserRow.kt
+++ /dev/null
@@ -1,75 +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.matrix.ui.components
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.PreviewParameter
-import io.element.android.libraries.designsystem.components.avatar.AvatarSize
-import io.element.android.libraries.designsystem.preview.ElementPreview
-import io.element.android.libraries.designsystem.preview.PreviewsDayNight
-import io.element.android.libraries.matrix.api.user.MatrixUser
-import io.element.android.libraries.matrix.ui.model.getAvatarData
-import io.element.android.libraries.matrix.ui.model.getBestName
-
-@Composable
-fun CheckableMatrixUserRow(
- checked: Boolean,
- matrixUser: MatrixUser,
- onCheckedChange: (Boolean) -> Unit,
- modifier: Modifier = Modifier,
- avatarSize: AvatarSize = AvatarSize.UserListItem,
- enabled: Boolean = true,
-) = CheckableUserRow(
- checked = checked,
- avatarData = matrixUser.getAvatarData(avatarSize),
- name = matrixUser.getBestName(),
- subtext = if (matrixUser.displayName.isNullOrEmpty()) null else matrixUser.userId.value,
- modifier = modifier,
- onCheckedChange = onCheckedChange,
- enabled = enabled,
-)
-
-@PreviewsDayNight
-@Composable
-internal fun CheckableMatrixUserRowPreview(@PreviewParameter(MatrixUserProvider::class) matrixUser: MatrixUser) = ElementPreview {
- Column {
- CheckableMatrixUserRow(
- checked = true,
- onCheckedChange = { },
- matrixUser = matrixUser,
- )
- CheckableMatrixUserRow(
- checked = false,
- onCheckedChange = { },
- matrixUser = matrixUser,
- )
- CheckableMatrixUserRow(
- checked = true,
- onCheckedChange = { },
- matrixUser = matrixUser,
- enabled = false,
- )
- CheckableMatrixUserRow(
- checked = false,
- onCheckedChange = { },
- matrixUser = matrixUser,
- enabled = false,
- )
- }
-}
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUnresolvedUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUnresolvedUserRow.kt
deleted file mode 100644
index 125b9b13f6..0000000000
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUnresolvedUserRow.kt
+++ /dev/null
@@ -1,104 +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.matrix.ui.components
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import io.element.android.libraries.designsystem.components.avatar.AvatarData
-import io.element.android.libraries.designsystem.components.avatar.AvatarSize
-import io.element.android.libraries.designsystem.preview.ElementThemedPreview
-import io.element.android.libraries.designsystem.theme.components.Checkbox
-import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
-import io.element.android.libraries.matrix.ui.model.getAvatarData
-
-@Composable
-fun CheckableUnresolvedUserRow(
- checked: Boolean,
- onCheckedChange: (Boolean) -> Unit,
- avatarData: AvatarData,
- id: String,
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
-) {
- Row(
- modifier = modifier
- .fillMaxWidth()
- .clickable(role = Role.Checkbox, enabled = enabled) {
- onCheckedChange(!checked)
- },
- verticalAlignment = Alignment.CenterVertically,
- ) {
- UnresolvedUserRow(
- modifier = Modifier.weight(1f),
- avatarData = avatarData,
- id = id,
- )
-
- Checkbox(
- modifier = Modifier.padding(end = 16.dp),
- checked = checked,
- onCheckedChange = null,
- enabled = enabled,
- )
- }
-}
-
-@Preview
-@Composable
-internal fun CheckableUnresolvedUserRowPreview() = ElementThemedPreview {
- val matrixUser = aMatrixUser()
- Column {
- CheckableUnresolvedUserRow(
- checked = false,
- onCheckedChange = { },
- avatarData = matrixUser.getAvatarData(AvatarSize.UserListItem),
- id = matrixUser.userId.value,
- )
- HorizontalDivider()
- CheckableUnresolvedUserRow(
- checked = true,
- onCheckedChange = { },
- avatarData = matrixUser.getAvatarData(AvatarSize.UserListItem),
- id = matrixUser.userId.value,
- )
- HorizontalDivider()
- CheckableUnresolvedUserRow(
- checked = false,
- onCheckedChange = { },
- avatarData = matrixUser.getAvatarData(AvatarSize.UserListItem),
- id = matrixUser.userId.value,
- enabled = false,
- )
- HorizontalDivider()
- CheckableUnresolvedUserRow(
- checked = true,
- onCheckedChange = { },
- avatarData = matrixUser.getAvatarData(AvatarSize.UserListItem),
- id = matrixUser.userId.value,
- enabled = false,
- )
- }
-}
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt
index 1b7ead9131..318ab7eabd 100644
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt
@@ -17,24 +17,29 @@
package io.element.android.libraries.matrix.ui.components
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.avatar.AvatarData
+import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.theme.components.Checkbox
+import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
+import io.element.android.libraries.matrix.ui.model.getAvatarData
@Composable
fun CheckableUserRow(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
- avatarData: AvatarData,
- name: String,
- subtext: String?,
+ data: CheckableUserRowData,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
@@ -46,19 +51,119 @@ fun CheckableUserRow(
},
verticalAlignment = Alignment.CenterVertically,
) {
- UserRow(
- modifier = Modifier.weight(1f),
- avatarData = avatarData,
- name = name,
- subtext = subtext,
- )
+ val rowModifier = Modifier.weight(1f)
+ when (data) {
+ is CheckableUserRowData.Resolved -> {
+ UserRow(
+ modifier = rowModifier,
+ avatarData = data.avatarData,
+ name = data.name,
+ subtext = data.subtext,
+ )
+ }
+ is CheckableUserRowData.Unresolved -> {
+ UnresolvedUserRow(
+ modifier = rowModifier,
+ avatarData = data.avatarData,
+ id = data.id,
+ )
+ }
+ }
Checkbox(
- modifier = Modifier
- .padding(end = 16.dp),
+ modifier = Modifier.padding(end = 4.dp),
checked = checked,
onCheckedChange = null,
enabled = enabled,
)
}
}
+
+@Immutable
+sealed interface CheckableUserRowData {
+ data class Resolved(
+ val avatarData: AvatarData,
+ val name: String,
+ val subtext: String?,
+ ) : CheckableUserRowData
+
+ data class Unresolved(
+ val avatarData: AvatarData,
+ val id: String,
+ ) : CheckableUserRowData
+}
+
+@Preview
+@Composable
+internal fun CheckableResolvedUserRowPreview() = ElementThemedPreview {
+ val matrixUser = aMatrixUser()
+ val data = CheckableUserRowData.Resolved(
+ avatarData = matrixUser.getAvatarData(AvatarSize.UserListItem),
+ name = matrixUser.displayName.orEmpty(),
+ subtext = matrixUser.userId.value,
+ )
+ Column {
+ CheckableUserRow(
+ checked = false,
+ onCheckedChange = { },
+ data = data,
+ )
+ HorizontalDivider()
+ CheckableUserRow(
+ checked = true,
+ onCheckedChange = { },
+ data = data,
+ )
+ HorizontalDivider()
+ CheckableUserRow(
+ checked = false,
+ onCheckedChange = { },
+ data = data,
+ enabled = false,
+ )
+ HorizontalDivider()
+ CheckableUserRow(
+ checked = true,
+ onCheckedChange = { },
+ data = data,
+ enabled = false,
+ )
+ }
+}
+
+@Preview
+@Composable
+internal fun CheckableUnresolvedUserRowPreview() = ElementThemedPreview {
+ val matrixUser = aMatrixUser()
+ val data = CheckableUserRowData.Unresolved(
+ avatarData = matrixUser.getAvatarData(AvatarSize.UserListItem),
+ id = matrixUser.userId.value,
+ )
+ Column {
+ CheckableUserRow(
+ checked = false,
+ onCheckedChange = { },
+ data = data,
+ )
+ HorizontalDivider()
+ CheckableUserRow(
+ checked = true,
+ onCheckedChange = { },
+ data = data,
+ )
+ HorizontalDivider()
+ CheckableUserRow(
+ checked = false,
+ onCheckedChange = { },
+ data = data,
+ enabled = false,
+ )
+ HorizontalDivider()
+ CheckableUserRow(
+ checked = true,
+ onCheckedChange = { },
+ data = data,
+ enabled = false,
+ )
+ }
+}
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 02767e10bb..a4d9a479c2 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
@@ -44,6 +44,9 @@ 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.core.RoomId
+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.ui.strings.CommonStrings
@@ -60,7 +63,7 @@ fun SelectedRoom(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
- Avatar(AvatarData(roomSummary.roomId.value, roomSummary.name, roomSummary.avatarURLString, AvatarSize.SelectedRoom))
+ Avatar(AvatarData(roomSummary.roomId.value, roomSummary.name, roomSummary.avatarUrl, AvatarSize.SelectedRoom))
Text(
text = roomSummary.name,
overflow = TextOverflow.Ellipsis,
@@ -94,17 +97,37 @@ fun SelectedRoom(
@Composable
internal fun SelectedRoomPreview() = ElementPreview {
SelectedRoom(
- roomSummary = RoomSummaryDetails(
- roomId = RoomId("!room:domain"),
- name = "roomName",
- canonicalAlias = null,
- isDirect = true,
- avatarURLString = null,
- lastMessage = null,
- lastMessageTimestamp = null,
- unreadNotificationCount = 0,
- inviter = null,
- ),
+ roomSummary = aRoomSummaryDetails(),
onRoomRemoved = {},
)
}
+
+fun aRoomSummaryDetails(
+ roomId: RoomId = RoomId("!room:domain"),
+ name: String = "roomName",
+ canonicalAlias: String? = null,
+ isDirect: Boolean = true,
+ avatarUrl: String? = null,
+ lastMessage: RoomMessage? = null,
+ inviter: RoomMember? = null,
+ notificationMode: RoomNotificationMode? = null,
+ hasRoomCall: Boolean = false,
+ isDm: Boolean = false,
+ numUnreadMentions: Int = 0,
+ numUnreadMessages: Int = 0,
+ numUnreadNotifications: Int = 0,
+) = RoomSummaryDetails(
+ roomId = roomId,
+ name = name,
+ canonicalAlias = canonicalAlias,
+ isDirect = isDirect,
+ avatarUrl = avatarUrl,
+ lastMessage = lastMessage,
+ inviter = inviter,
+ userDefinedNotificationMode = notificationMode,
+ hasRoomCall = hasRoomCall,
+ isDm = isDm,
+ numUnreadMentions = numUnreadMentions,
+ numUnreadMessages = numUnreadMessages,
+ numUnreadNotifications = numUnreadNotifications,
+)
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt
index dea6195ba8..fbe35742e5 100644
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt
@@ -21,7 +21,8 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
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.powerlevels.canRedact
+import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
+import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
@Composable
@@ -32,8 +33,15 @@ fun MatrixRoom.canSendMessageAsState(type: MessageEventType, updateKey: Long): S
}
@Composable
-fun MatrixRoom.canRedactAsState(updateKey: Long): State {
+fun MatrixRoom.canRedactOwnAsState(updateKey: Long): State {
return produceState(initialValue = false, key1 = updateKey) {
- value = canRedact().getOrElse { false }
+ value = canRedactOwn().getOrElse { false }
+ }
+}
+
+@Composable
+fun MatrixRoom.canRedactOtherAsState(updateKey: Long): State {
+ return produceState(initialValue = false, key1 = updateKey) {
+ value = canRedactOther().getOrElse { false }
}
}
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PollHistory.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PollHistory.kt
deleted file mode 100644
index bc695864f7..0000000000
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PollHistory.kt
+++ /dev/null
@@ -1,36 +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.matrix.ui.room
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
-import io.element.android.libraries.matrix.api.room.MatrixRoom
-import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
-
-@Composable
-fun MatrixRoom.rememberPollHistory(): MatrixTimeline {
- val pollHistory = remember {
- pollHistory()
- }
- DisposableEffect(pollHistory) {
- onDispose {
- pollHistory.close()
- }
- }
- return pollHistory
-}
diff --git a/libraries/permissions/api/src/main/res/values-hu/translations.xml b/libraries/permissions/api/src/main/res/values-hu/translations.xml
index de3573fce2..aea9a1f772 100644
--- a/libraries/permissions/api/src/main/res/values-hu/translations.xml
+++ b/libraries/permissions/api/src/main/res/values-hu/translations.xml
@@ -1,7 +1,7 @@
"Hogy az alkalmazás használhassa a kamerát, adja meg az engedélyt a rendszerbeállításokban."
- "Adja meg az engedélyt a rendszerbeállításokban."
+ "Add meg az engedélyt a rendszerbeállításokban."
"Hogy az alkalmazás használhassa a mikrofont, adja meg az engedélyt a rendszerbeállításokban."
"Hogy az alkalmazás megjeleníthesse az értesítéseket, adja meg az engedélyt a rendszerbeállításokban."
diff --git a/libraries/permissions/api/src/main/res/values-it/translations.xml b/libraries/permissions/api/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..2fe09c68ba
--- /dev/null
+++ b/libraries/permissions/api/src/main/res/values-it/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Per permettere all\'applicazione di usare la fotocamera, concedi l\'autorizzazione nelle impostazioni di sistema."
+ "Concedi l\'autorizzazione nelle impostazioni di sistema."
+ "Per permettere all\'applicazione di usare il microfono, concedi l\'autorizzazione nelle impostazioni di sistema."
+ "Per permettere all\'applicazione di mostrare notifiche, concedi l\'autorizzazione nelle impostazioni di sistema."
+
diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/AppPreferencesStore.kt
similarity index 97%
rename from libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt
rename to libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/AppPreferencesStore.kt
index 2bd1fc6064..4e78978873 100644
--- a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt
+++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/AppPreferencesStore.kt
@@ -18,7 +18,7 @@ package io.element.android.features.preferences.api.store
import kotlinx.coroutines.flow.Flow
-interface PreferencesStore {
+interface AppPreferencesStore {
suspend fun setRichTextEditorEnabled(enabled: Boolean)
fun isRichTextEditorEnabledFlow(): Flow
diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt
new file mode 100644
index 0000000000..0174d8d1eb
--- /dev/null
+++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.features.preferences.api.store
+
+import kotlinx.coroutines.flow.Flow
+
+interface SessionPreferencesStore {
+ suspend fun setSendPublicReadReceipts(enabled: Boolean)
+ fun isSendPublicReadReceiptsEnabled(): Flow
+
+ suspend fun clear()
+}
diff --git a/libraries/preferences/impl/build.gradle.kts b/libraries/preferences/impl/build.gradle.kts
index 9c31d83481..3fa6324699 100644
--- a/libraries/preferences/impl/build.gradle.kts
+++ b/libraries/preferences/impl/build.gradle.kts
@@ -31,6 +31,8 @@ dependencies {
api(projects.libraries.preferences.api)
implementation(libs.dagger)
implementation(libs.androidx.datastore.preferences)
+ implementation(projects.libraries.androidutils)
implementation(projects.libraries.di)
implementation(projects.libraries.core)
+ implementation(projects.libraries.matrix.api)
}
diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt
similarity index 95%
rename from libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt
rename to libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt
index d00b7505d7..fdbd7dde8c 100644
--- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt
+++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt
@@ -24,7 +24,7 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.squareup.anvil.annotations.ContributesBinding
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.core.bool.orTrue
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType
@@ -42,10 +42,10 @@ private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseU
private val themeKey = stringPreferencesKey("theme")
@ContributesBinding(AppScope::class)
-class DefaultPreferencesStore @Inject constructor(
+class DefaultAppPreferencesStore @Inject constructor(
@ApplicationContext context: Context,
private val buildMeta: BuildMeta,
-) : PreferencesStore {
+) : AppPreferencesStore {
private val store = context.dataStore
override suspend fun setRichTextEditorEnabled(enabled: Boolean) {
diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt
new file mode 100644
index 0000000000..eb2fffb045
--- /dev/null
+++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.preferences.impl.store
+
+import android.content.Context
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.preferencesDataStoreFile
+import io.element.android.features.preferences.api.store.SessionPreferencesStore
+import io.element.android.libraries.androidutils.file.safeDelete
+import io.element.android.libraries.androidutils.hash.hash
+import io.element.android.libraries.di.annotations.SessionCoroutineScope
+import io.element.android.libraries.matrix.api.core.SessionId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import java.io.File
+
+class DefaultSessionPreferencesStore(
+ context: Context,
+ sessionId: SessionId,
+ @SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
+) : SessionPreferencesStore {
+ companion object {
+ fun storeFile(context: Context, sessionId: SessionId): File {
+ val hashedUserId = sessionId.value.hash().take(16)
+ return context.preferencesDataStoreFile("session_${hashedUserId}_preferences")
+ }
+ }
+ private val sendPublicReadReceiptsKey = booleanPreferencesKey("sendPublicReadReceipts")
+
+ private val dataStoreFile = storeFile(context, sessionId)
+ private val store = PreferenceDataStoreFactory.create(scope = sessionCoroutineScope) { dataStoreFile }
+
+ override suspend fun setSendPublicReadReceipts(enabled: Boolean) = update(sendPublicReadReceiptsKey, enabled)
+ override fun isSendPublicReadReceiptsEnabled(): Flow = get(sendPublicReadReceiptsKey, true)
+
+ override suspend fun clear() {
+ dataStoreFile.safeDelete()
+ }
+
+ private suspend fun update(key: Preferences.Key, value: T) {
+ store.edit { prefs -> prefs[key] = value }
+ }
+
+ private fun get(key: Preferences.Key, default: T): Flow {
+ return store.data.map { prefs -> prefs[key] ?: default }
+ }
+}
diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt
new file mode 100644
index 0000000000..84dcbac289
--- /dev/null
+++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.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
+ *
+ * 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.preferences.impl.store
+
+import android.content.Context
+import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.ApplicationContext
+import io.element.android.libraries.di.SingleIn
+import io.element.android.libraries.matrix.api.core.SessionId
+import kotlinx.coroutines.CoroutineScope
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+@SingleIn(AppScope::class)
+class DefaultSessionPreferencesStoreFactory @Inject constructor(
+ @ApplicationContext private val context: Context,
+) {
+ private val cache = ConcurrentHashMap()
+
+ fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): DefaultSessionPreferencesStore = cache.getOrPut(sessionId) {
+ DefaultSessionPreferencesStore(context, sessionId, sessionCoroutineScope)
+ }
+}
diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt
new file mode 100644
index 0000000000..36663f6628
--- /dev/null
+++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.preferences.impl.store
+
+import com.squareup.anvil.annotations.ContributesTo
+import dagger.Module
+import dagger.Provides
+import io.element.android.features.preferences.api.store.SessionPreferencesStore
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.di.annotations.SessionCoroutineScope
+import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+@ContributesTo(SessionScope::class)
+object SessionPreferencesModule {
+ @Provides
+ fun providesSessionPreferencesStore(
+ defaultSessionPreferencesStoreFactory: DefaultSessionPreferencesStoreFactory,
+ currentSessionIdHolder: CurrentSessionIdHolder,
+ @SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
+ ): SessionPreferencesStore {
+ return defaultSessionPreferencesStoreFactory
+ .get(currentSessionIdHolder.current, sessionCoroutineScope)
+ }
+}
diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryAppPreferencesStore.kt
similarity index 93%
rename from libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt
rename to libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryAppPreferencesStore.kt
index c143b3ff6c..c065622f3f 100644
--- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt
+++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryAppPreferencesStore.kt
@@ -16,16 +16,16 @@
package io.element.android.libraries.featureflag.test
-import io.element.android.features.preferences.api.store.PreferencesStore
+import io.element.android.features.preferences.api.store.AppPreferencesStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class InMemoryPreferencesStore(
+class InMemoryAppPreferencesStore(
isRichTextEditorEnabled: Boolean = false,
isDeveloperModeEnabled: Boolean = false,
customElementCallBaseUrl: String? = null,
theme: String? = null,
-) : PreferencesStore {
+) : AppPreferencesStore {
private val isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled)
private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled)
private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl)
diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt
new file mode 100644
index 0000000000..1f6f7a6724
--- /dev/null
+++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.featureflag.test
+
+import io.element.android.features.preferences.api.store.SessionPreferencesStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class InMemorySessionPreferencesStore(
+ isSendPublicReadReceiptsEnabled: Boolean = true,
+) : SessionPreferencesStore {
+ private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled)
+ var clearCallCount = 0
+ private set
+
+ override suspend fun setSendPublicReadReceipts(enabled: Boolean) {
+ isSendPublicReadReceiptsEnabled.tryEmit(enabled)
+ }
+ override fun isSendPublicReadReceiptsEnabled(): Flow {
+ return isSendPublicReadReceiptsEnabled
+ }
+
+ override suspend fun clear() {
+ clearCallCount++
+ isSendPublicReadReceiptsEnabled.tryEmit(true)
+ }
+}
diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt
index 1a0eb7a93c..5f4736e5ab 100644
--- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt
+++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt
@@ -24,6 +24,10 @@ interface PushService {
// TODO Move away
fun notificationStyleChanged()
+ /**
+ * Return the list of push providers, available at compile time, and
+ * available at runtime, sorted by index.
+ */
fun getAvailablePushProviders(): List
/**
diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt
index b16908269d..e60cd8b014 100644
--- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt
+++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt
@@ -38,7 +38,9 @@ class DefaultPushService @Inject constructor(
}
override fun getAvailablePushProviders(): List {
- return pushProviders.sortedBy { it.index }
+ return pushProviders
+ .filter { it.isAvailable() }
+ .sortedBy { it.index }
}
/**
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 904a6bf2ea..9d12d7bd6c 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
@@ -297,7 +297,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
operation = {
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value
- val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
+ val userAvatarUrl = client.loadUserAvatarUrl().getOrNull()
MatrixUser(
userId = sessionId,
displayName = myUserDisplayName,
diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt
index ebf8ac6a89..31e71dc6c9 100644
--- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt
+++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt
@@ -91,7 +91,8 @@ class NotifiableEventResolver @Inject constructor(
): NotifiableEvent? {
return when (val content = this.content) {
is NotificationContent.MessageLike.RoomMessage -> {
- val messageBody = descriptionFromMessageContent(content, senderDisplayName ?: content.senderId.value)
+ val senderName = getSenderName(content.senderId)
+ val messageBody = descriptionFromMessageContent(content, senderName)
val notificationBody = if (hasMention) {
stringProvider.getString(R.string.notification_mentioned_you_body, messageBody)
} else {
@@ -104,7 +105,7 @@ class NotifiableEventResolver @Inject constructor(
eventId = eventId,
noisy = isNoisy,
timestamp = this.timestamp,
- senderName = senderDisplayName,
+ senderName = senderName,
body = notificationBody,
imageUriString = fetchImageIfPresent(client)?.toString(),
roomName = roomDisplayName,
@@ -161,7 +162,7 @@ class NotifiableEventResolver @Inject constructor(
eventId = eventId,
noisy = isNoisy,
timestamp = this.timestamp,
- senderName = senderDisplayName,
+ senderName = getSenderName(content.senderId),
body = stringProvider.getString(CommonStrings.common_poll_summary, content.question),
imageUriString = null,
roomName = roomDisplayName,
diff --git a/libraries/push/impl/src/main/res/values-hu/translations.xml b/libraries/push/impl/src/main/res/values-hu/translations.xml
index f87455630b..23c3998bb8 100644
--- a/libraries/push/impl/src/main/res/values-hu/translations.xml
+++ b/libraries/push/impl/src/main/res/values-hu/translations.xml
@@ -4,7 +4,7 @@
"Események figyelése"
"Zajos értesítések"
"Csendes értesítések"
- "** Nem sikerült elküldeni – nyissa meg a szobát"
+ "** Nem sikerült elküldeni – kérlek nyisd meg a szobát"
"Csatlakozás"
"Elutasítás"
"Meghívta, hogy csevegjen"
@@ -14,7 +14,7 @@
"Megjelölés olvasottként"
"Meghívta, hogy csatlakozzon a szobához"
"Én"
- "Az értesítést nézi! Kattintson ide!"
+ "Az értesítést nézed! Kattints ide!"
"%1$s: %2$s"
"%1$s: %2$s %3$s"
"%1$s és %2$s"
@@ -44,7 +44,7 @@
- "%d szoba"
- "%d szoba"
- "Válassza ki az értesítések fogadásának módját"
+ "Válaszd ki az értesítések fogadásának módját"
"Háttérszinkronizálás"
"Google szolgáltatások"
"A Google Play szolgáltatások nem találhatók. Előfordulhat, hogy az értesítések nem működnek megfelelően."
diff --git a/libraries/push/impl/src/main/res/values-it/translations.xml b/libraries/push/impl/src/main/res/values-it/translations.xml
index 92c5c06f9b..9e512001be 100644
--- a/libraries/push/impl/src/main/res/values-it/translations.xml
+++ b/libraries/push/impl/src/main/res/values-it/translations.xml
@@ -1,5 +1,51 @@
+ "Chiamata"
+ "Notifiche silenziose"
+ "** Invio fallito - si prega di aprire la stanza"
+ "Entra"
+ "Rifiuta"
+ "Ti ha invitato a chattare"
+ "Ti ha menzionato: %1$s"
+ "Nuovi messaggi"
+ "Ha reagito con %1$s"
+ "Segna come letto"
+ "Ti ha invitato ad entrare nella stanza"
+ "Io"
+ "Stai visualizzando la notifica! Cliccami!"
+ "%1$s: %2$s"
+ "%1$s: %2$s %3$s"
+ "%1$s e %2$s"
+ "%1$s in %2$s"
+ "%1$s in %2$s e %3$s"
+
+ - "%1$s: %2$d messaggio"
+ - "%1$s: %2$d messaggi"
+
+
+ - "%d notifica"
+ - "%d notifiche"
+
+
+ - "%d invito"
+ - "%d inviti"
+
+
+ - "%d nuovo messaggio"
+ - "%d nuovi messaggi"
+
+
+ - "%d messaggio notificato non letto"
+ - "%d messaggi notificati non letti"
+
+
+ - "%d stanza"
+ - "%d stanze"
+
+ "Scegli come ricevere le notifiche"
+ "Sincronizzazione in background"
+ "Servizi Google"
+ "Google Play Services non trovato. Le notifiche non funzioneranno bene."
"Notifica"
"Risposta rapida"
diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt
index d824a193d2..7b31afc01e 100644
--- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt
+++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt
@@ -527,6 +527,7 @@ class NotifiableEventResolverTest {
roomId = A_ROOM_ID,
senderAvatarUrl = null,
senderDisplayName = "Bob",
+ senderIsNameAmbiguous = false,
roomAvatarUrl = null,
roomDisplayName = null,
isDirect = isDirect,
diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt
index d71cd9ec3f..3d8349a117 100644
--- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt
+++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt
@@ -32,6 +32,11 @@ interface PushProvider {
*/
val name: String
+ /**
+ * Return true if the push provider is available on this device.
+ */
+ fun isAvailable(): Boolean
+
fun getDistributors(): List
/**
diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt
index 1fad74e1be..4c3d6d3a20 100644
--- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt
+++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt
@@ -16,9 +16,13 @@
package io.element.android.libraries.pushproviders.firebase
+import android.content.Context
+import com.google.android.gms.common.ConnectionResult
+import com.google.android.gms.common.GoogleApiAvailability
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
@@ -30,6 +34,7 @@ private val loggerTag = LoggerTag("FirebasePushProvider", LoggerTag.PushLoggerTa
@ContributesMultibinding(AppScope::class)
class FirebasePushProvider @Inject constructor(
+ @ApplicationContext private val context: Context,
private val firebaseStore: FirebaseStore,
private val firebaseTroubleshooter: FirebaseTroubleshooter,
private val pusherSubscriber: PusherSubscriber,
@@ -37,6 +42,19 @@ class FirebasePushProvider @Inject constructor(
override val index = FirebaseConfig.INDEX
override val name = FirebaseConfig.NAME
+ override fun isAvailable(): Boolean {
+ // The PlayServices has to be available
+ val apiAvailability = GoogleApiAvailability.getInstance()
+ val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
+ return if (resultCode == ConnectionResult.SUCCESS) {
+ Timber.tag(loggerTag.value).d("Google Play Services is available")
+ true
+ } else {
+ Timber.tag(loggerTag.value).w("Google Play Services is not available")
+ false
+ }
+ }
+
override fun getDistributors(): List {
return listOf(Distributor("Firebase", "Firebase"))
}
diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt
index bca4fd3ed3..5d0a0da7a1 100644
--- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt
+++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt
@@ -19,6 +19,7 @@ package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.androidutils.system.getApplicationLabel
+import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
@@ -26,8 +27,11 @@ import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import org.unifiedpush.android.connector.UnifiedPush
+import timber.log.Timber
import javax.inject.Inject
+private val loggerTag = LoggerTag("UnifiedPushProvider", LoggerTag.PushLoggerTag)
+
@ContributesMultibinding(AppScope::class)
class UnifiedPushProvider @Inject constructor(
@ApplicationContext private val context: Context,
@@ -38,6 +42,17 @@ class UnifiedPushProvider @Inject constructor(
override val index = UnifiedPushConfig.INDEX
override val name = UnifiedPushConfig.NAME
+ override fun isAvailable(): Boolean {
+ val isAvailable = getDistributors().isNotEmpty()
+ return if (isAvailable) {
+ Timber.tag(loggerTag.value).d("UnifiedPush is available")
+ true
+ } else {
+ Timber.tag(loggerTag.value).w("UnifiedPush is not available")
+ false
+ }
+ }
+
override fun getDistributors(): List {
val distributors = UnifiedPush.getDistributors(context)
return distributors.mapNotNull {
diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt
index 05400e87f2..e28d67ecf7 100644
--- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt
+++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt
@@ -105,7 +105,6 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
*/
override fun onUnregistered(context: Context, instance: String) {
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
- TODO()
/*
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
pushDataStore.setFdroidSyncBackgroundMode(mode)
diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt
index dbdd22ce07..1700d41181 100644
--- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt
+++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt
@@ -29,9 +29,4 @@ interface PushClientSecret {
* Return null if not found.
*/
suspend fun getUserIdFromSecret(clientSecret: String): SessionId?
-
- /**
- * To call when the user signs out.
- */
- suspend fun resetSecretForUser(userId: SessionId)
}
diff --git a/libraries/pushstore/impl/build.gradle.kts b/libraries/pushstore/impl/build.gradle.kts
index 17e0268af1..28e53e011c 100644
--- a/libraries/pushstore/impl/build.gradle.kts
+++ b/libraries/pushstore/impl/build.gradle.kts
@@ -34,6 +34,7 @@ anvil {
dependencies {
implementation(libs.dagger)
implementation(projects.libraries.architecture)
+ implementation(projects.libraries.androidutils)
implementation(projects.libraries.core)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.pushstore.api)
@@ -48,6 +49,7 @@ dependencies {
testImplementation(libs.coroutines.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.services.appnavstate.test)
+ testImplementation(projects.libraries.sessionStorage.test)
androidTestImplementation(libs.coroutines.test)
androidTestImplementation(libs.test.core)
diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt
index a79eafbbfd..f7a159f6c3 100644
--- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt
+++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt
@@ -23,12 +23,15 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
+import androidx.datastore.preferences.preferencesDataStoreFile
+import io.element.android.libraries.androidutils.hash.hash
import io.element.android.libraries.core.bool.orTrue
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.pushstore.api.UserPushStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
+import timber.log.Timber
/**
* Store data related to push about a user.
@@ -37,7 +40,24 @@ class UserPushStoreDataStore(
private val context: Context,
userId: SessionId,
) : UserPushStore {
- private val Context.dataStore: DataStore by preferencesDataStore(name = "push_store_$userId")
+ // Hash the sessionId to get rid of exotic chars and take only the first 16 chars.
+ // The risk of collision is not high.
+ private val preferenceName = "push_store_${userId.value.hash().take(16)}"
+
+ init {
+ // Migrate legacy data. Previous file can be too long if the userId is too long. The userId can be up to 255 chars.
+ // Example of long file path, with `averylonguserid` replacing a very longer name
+ // /data/user/0/io.element.android.x.debug/files/datastore/push_store_@averylonguserid:example.org.preferences_pb
+ val legacyFile = context.preferencesDataStoreFile("push_store_$userId")
+ if (legacyFile.exists()) {
+ Timber.d("Migrating legacy push data store for $userId")
+ if (!legacyFile.renameTo(context.preferencesDataStoreFile(preferenceName))) {
+ Timber.w("Failed to migrate legacy push data store for $userId")
+ }
+ }
+ }
+
+ private val Context.dataStore: DataStore by preferencesDataStore(name = preferenceName)
private val pushProviderName = stringPreferencesKey("pushProviderName")
private val currentPushKey = stringPreferencesKey("currentPushKey")
private val notificationEnabled = booleanPreferencesKey("notificationEnabled")
@@ -80,5 +100,7 @@ class UserPushStoreDataStore(
context.dataStore.edit {
it.clear()
}
+ // Also delete the file
+ context.preferencesDataStoreFile(preferenceName).delete()
}
}
diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt
index ca0ed14e33..5f0c83b6d7 100644
--- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt
+++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt
@@ -18,17 +18,26 @@ package io.element.android.libraries.pushstore.impl.clientsecret
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretFactory
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore
+import io.element.android.libraries.sessionstorage.api.observer.SessionListener
+import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import javax.inject.Inject
-@ContributesBinding(AppScope::class)
+@SingleIn(AppScope::class)
+@ContributesBinding(AppScope::class, boundType = PushClientSecret::class)
class PushClientSecretImpl @Inject constructor(
private val pushClientSecretFactory: PushClientSecretFactory,
private val pushClientSecretStore: PushClientSecretStore,
-) : PushClientSecret {
+ private val sessionObserver: SessionObserver,
+) : PushClientSecret, SessionListener {
+ init {
+ observeSessions()
+ }
+
override suspend fun getSecretForUser(userId: SessionId): String {
val existingSecret = pushClientSecretStore.getSecret(userId)
if (existingSecret != null) {
@@ -43,7 +52,16 @@ class PushClientSecretImpl @Inject constructor(
return pushClientSecretStore.getUserIdFromSecret(clientSecret)
}
- override suspend fun resetSecretForUser(userId: SessionId) {
- pushClientSecretStore.resetSecret(userId)
+ private fun observeSessions() {
+ sessionObserver.addListener(this)
+ }
+
+ override suspend fun onSessionCreated(userId: String) {
+ // Nothing to do
+ }
+
+ override suspend fun onSessionDeleted(userId: String) {
+ // Delete the secret
+ pushClientSecretStore.resetSecret(SessionId(userId))
}
}
diff --git a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt
index 5af4877ed3..dc0e5b3651 100644
--- a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt
+++ b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt
@@ -18,6 +18,7 @@ package io.element.android.libraries.pushstore.impl.clientsecret
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.SessionId
+import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -31,7 +32,7 @@ internal class PushClientSecretImplTest {
fun test() = runTest {
val factory = FakePushClientSecretFactory()
val store = InMemoryPushClientSecretStore()
- val sut = PushClientSecretImpl(factory, store)
+ val sut = PushClientSecretImpl(factory, store, NoOpSessionObserver())
val secret0 = factory.getSecretForUser(0)
val secret1 = factory.getSecretForUser(1)
@@ -56,7 +57,7 @@ internal class PushClientSecretImplTest {
assertThat(sut.getUserIdFromSecret(A_UNKNOWN_SECRET)).isNull()
// User signs out
- sut.resetSecretForUser(A_USER_ID_0)
+ sut.onSessionDeleted(A_USER_ID_0.value)
assertThat(store.getSecrets()).hasSize(1)
// Create a new secret after reset
assertThat(sut.getSecretForUser(A_USER_ID_0)).isEqualTo(secret2)
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 032dc4a8d0..ecd13338ca 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
@@ -19,9 +19,8 @@ package io.element.android.libraries.roomselect.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.RoomId
-import io.element.android.libraries.matrix.api.room.RoomMember
-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.ui.components.aRoomSummaryDetails
import io.element.android.libraries.roomselect.api.RoomSelectMode
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -41,7 +40,7 @@ open class RoomSelectStateProvider : PreviewParameterProvider {
resultState = SearchBarResultState.Results(aForwardMessagesRoomList()),
query = "Test",
isSearchActive = true,
- selectedRooms = persistentListOf(aRoomDetailsState(roomId = RoomId("!room2:domain")))
+ selectedRooms = persistentListOf(aRoomSummaryDetails(roomId = RoomId("!room2:domain")))
),
// Add other states here
)
@@ -62,32 +61,10 @@ private fun aRoomSelectState(
)
private fun aForwardMessagesRoomList() = persistentListOf(
- aRoomDetailsState(),
- aRoomDetailsState(
+ aRoomSummaryDetails(),
+ aRoomSummaryDetails(
roomId = RoomId("!room2:domain"),
name = "Room with alias",
canonicalAlias = "#alias:example.org",
),
)
-
-private fun aRoomDetailsState(
- roomId: RoomId = RoomId("!room:domain"),
- name: String = "roomName",
- canonicalAlias: String? = null,
- isDirect: Boolean = true,
- avatarURLString: String? = null,
- lastMessage: RoomMessage? = null,
- lastMessageTimestamp: Long? = null,
- unreadNotificationCount: Int = 0,
- inviter: RoomMember? = null,
-) = RoomSummaryDetails(
- roomId = roomId,
- name = name,
- canonicalAlias = canonicalAlias,
- isDirect = isDirect,
- avatarURLString = avatarURLString,
- lastMessage = lastMessage,
- lastMessageTimestamp = lastMessageTimestamp,
- unreadNotificationCount = unreadNotificationCount,
- inviter = inviter,
-)
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 83b3608eae..d7453ec264 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
@@ -223,7 +223,7 @@ private fun RoomSummaryView(
avatarData = AvatarData(
id = summary.roomId.value,
name = summary.name,
- url = summary.avatarURLString,
+ url = summary.avatarUrl,
size = AvatarSize.RoomSelectRoomListItem,
),
)
diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt
index 72ab751c39..868c00d42e 100644
--- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt
+++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt
@@ -23,7 +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.RoomSummary
import io.element.android.libraries.matrix.test.FakeMatrixClient
-import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail
+import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.roomselect.api.RoomSelectMode
import io.element.android.tests.testutils.WarmUpRule
@@ -72,7 +72,7 @@ class RoomSelectPresenterTests {
@Test
fun `present - update query`() = runTest {
val roomListService = FakeRoomListService().apply {
- postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail())))
+ postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails())))
}
val client = FakeMatrixClient(roomListService = roomListService)
val presenter = aPresenter(client = client)
@@ -80,7 +80,7 @@ class RoomSelectPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
- assertThat(awaitItem().resultState as? SearchBarResultState.Results).isEqualTo(SearchBarResultState.Results(listOf(aRoomSummaryDetail())))
+ assertThat(awaitItem().resultState as? SearchBarResultState.Results).isEqualTo(SearchBarResultState.Results(listOf(aRoomSummaryDetails())))
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))
assertThat(awaitItem().query).isEqualTo("string not contained")
@@ -96,7 +96,7 @@ class RoomSelectPresenterTests {
}.test {
val initialState = awaitItem()
skipItems(1)
- val summary = aRoomSummaryDetail()
+ val summary = aRoomSummaryDetails()
initialState.eventSink(RoomSelectEvents.SetSelectedRoom(summary))
assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(summary))
diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt
index 25a48c0efe..7189442716 100644
--- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt
+++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt
@@ -29,4 +29,5 @@ data class SessionData(
val loginTimestamp: Date?,
val isTokenValid: Boolean,
val loginType: LoginType,
+ val passphrase: String?,
)
diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt
index 23d362d0b5..5ccf62c61d 100644
--- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt
+++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt
@@ -28,6 +28,8 @@ import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import javax.inject.Inject
@@ -37,6 +39,8 @@ class DatabaseSessionStore @Inject constructor(
private val database: SessionDatabase,
private val dispatchers: CoroutineDispatchers,
) : SessionStore {
+ private val sessionDataMutex = Mutex()
+
override fun isLoggedIn(): Flow {
return database.sessionDataQueries.selectFirst()
.asFlow()
@@ -53,11 +57,11 @@ class DatabaseSessionStore @Inject constructor(
}
}
- override suspend fun storeData(sessionData: SessionData) {
+ override suspend fun storeData(sessionData: SessionData) = sessionDataMutex.withLock {
database.sessionDataQueries.insertSessionData(sessionData.toDbModel())
}
- override suspend fun updateData(sessionData: SessionData) {
+ override suspend fun updateData(sessionData: SessionData) = sessionDataMutex.withLock {
val result = database.sessionDataQueries.selectByUserId(sessionData.userId)
.executeAsOneOrNull()
?.toApiModel()
@@ -66,8 +70,7 @@ class DatabaseSessionStore @Inject constructor(
Timber.e("User ${sessionData.userId} not found in session database")
return
}
-
- // Copy new data from SDK, but keep login timestamp
+ // Copy new data from SDK, but keep login timestamp
database.sessionDataQueries.updateSession(
sessionData.copy(
loginTimestamp = result.loginTimestamp,
@@ -76,21 +79,27 @@ class DatabaseSessionStore @Inject constructor(
}
override suspend fun getLatestSession(): SessionData? {
- return database.sessionDataQueries.selectFirst()
- .executeAsOneOrNull()
- ?.toApiModel()
+ return sessionDataMutex.withLock {
+ database.sessionDataQueries.selectFirst()
+ .executeAsOneOrNull()
+ ?.toApiModel()
+ }
}
override suspend fun getSession(sessionId: String): SessionData? {
- return database.sessionDataQueries.selectByUserId(sessionId)
- .executeAsOneOrNull()
- ?.toApiModel()
+ return sessionDataMutex.withLock {
+ database.sessionDataQueries.selectByUserId(sessionId)
+ .executeAsOneOrNull()
+ ?.toApiModel()
+ }
}
override suspend fun getAllSessions(): List {
- return database.sessionDataQueries.selectAll()
- .executeAsList()
- .map { it.toApiModel() }
+ return sessionDataMutex.withLock {
+ database.sessionDataQueries.selectAll()
+ .executeAsList()
+ .map { it.toApiModel() }
+ }
}
override fun sessionsFlow(): Flow> {
@@ -102,6 +111,8 @@ class DatabaseSessionStore @Inject constructor(
}
override suspend fun removeSession(sessionId: String) {
- database.sessionDataQueries.removeSession(sessionId)
+ sessionDataMutex.withLock {
+ database.sessionDataQueries.removeSession(sessionId)
+ }
}
}
diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt
index 1a81647f5c..3824def48c 100644
--- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt
+++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt
@@ -33,6 +33,7 @@ internal fun SessionData.toDbModel(): DbSessionData {
loginTimestamp = loginTimestamp?.time,
isTokenValid = if (isTokenValid) 1L else 0L,
loginType = loginType.name,
+ passphrase = passphrase,
)
}
@@ -48,5 +49,6 @@ internal fun DbSessionData.toApiModel(): SessionData {
loginTimestamp = loginTimestamp?.let { Date(it) },
isTokenValid = isTokenValid == 1L,
loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name),
+ passphrase = passphrase,
)
}
diff --git a/libraries/session-storage/impl/src/main/sqldelight/databases/5.db b/libraries/session-storage/impl/src/main/sqldelight/databases/5.db
new file mode 100644
index 0000000000..c6cf5ebef0
Binary files /dev/null and b/libraries/session-storage/impl/src/main/sqldelight/databases/5.db differ
diff --git a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq
index d6d16cb6e2..c33b4d7c7e 100644
--- a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq
+++ b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq
@@ -21,7 +21,9 @@ CREATE TABLE SessionData (
oidcData TEXT,
-- added in version 4
isTokenValid INTEGER NOT NULL DEFAULT 1,
- loginType TEXT
+ loginType TEXT,
+ -- added in version 5
+ passphrase TEXT
);
diff --git a/libraries/session-storage/impl/src/main/sqldelight/migrations/4.sqm b/libraries/session-storage/impl/src/main/sqldelight/migrations/4.sqm
new file mode 100644
index 0000000000..144d56959f
--- /dev/null
+++ b/libraries/session-storage/impl/src/main/sqldelight/migrations/4.sqm
@@ -0,0 +1,3 @@
+-- Migrate DB from version 4
+
+ALTER TABLE SessionData ADD COLUMN passphrase TEXT;
diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt
index a195c46c5c..760eefd20c 100644
--- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt
+++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt
@@ -44,6 +44,7 @@ class DatabaseSessionStoreTests {
oidcData = "aOidcData",
isTokenValid = 1,
loginType = LoginType.UNKNOWN.name,
+ passphrase = null,
)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -137,6 +138,7 @@ class DatabaseSessionStoreTests {
oidcData = "aOidcData",
isTokenValid = 1,
loginType = null,
+ passphrase = "aPassphrase",
)
val secondSessionData = SessionData(
userId = "userId",
@@ -149,6 +151,7 @@ class DatabaseSessionStoreTests {
oidcData = "aOidcDataAltered",
isTokenValid = 1,
loginType = null,
+ passphrase = "aPassphraseAltered",
)
assertThat(firstSessionData.userId).isEqualTo(secondSessionData.userId)
assertThat(firstSessionData.loginTimestamp).isNotEqualTo(secondSessionData.loginTimestamp)
@@ -168,5 +171,6 @@ class DatabaseSessionStoreTests {
// Check that alteredSession.loginTimestamp is not altered, so equal to firstSessionData.loginTimestamp
assertThat(alteredSession.loginTimestamp).isEqualTo(firstSessionData.loginTimestamp)
assertThat(alteredSession.oidcData).isEqualTo(secondSessionData.oidcData)
+ assertThat(alteredSession.passphrase).isEqualTo(secondSessionData.passphrase)
}
}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt
index 96b48dca6e..fd5f043b32 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt
@@ -18,6 +18,8 @@ package io.element.android.libraries.textcomposer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.viewinterop.AndroidView
import io.element.android.libraries.androidutils.ui.awaitWindowFocus
@@ -40,8 +42,10 @@ internal fun SoftKeyboardEffect(
predicate: (T) -> Boolean,
) {
val view = LocalView.current
+ val latestOnRequestFocus by rememberUpdatedState(onRequestFocus)
+ val latestPredicate by rememberUpdatedState(predicate)
LaunchedEffect(key) {
- if (predicate(key)) {
+ if (latestPredicate(key)) {
// Await window focus in case returning from a dialog
view.awaitWindowFocus()
@@ -49,7 +53,7 @@ internal fun SoftKeyboardEffect(
view.showKeyboard(andRequestFocus = true)
// Refocus to the correct view
- onRequestFocus()
+ latestOnRequestFocus()
}
}
}
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 06585d3b31..13359c1c0c 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
@@ -42,6 +42,7 @@ 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.draw.clip
@@ -111,6 +112,7 @@ fun TextComposer(
onSendVoiceMessage: () -> Unit,
onDeleteVoiceMessage: () -> Unit,
onError: (Throwable) -> Unit,
+ onTyping: (Boolean) -> Unit,
onSuggestionReceived: (Suggestion?) -> Unit,
onRichContentSelected: ((Uri) -> Unit)?,
modifier: Modifier = Modifier,
@@ -164,6 +166,7 @@ fun TextComposer(
resolveMentionDisplay = { text, url -> TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) },
resolveRoomMentionDisplay = { TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor("@room", "#")) },
onError = onError,
+ onTyping = onTyping,
onRichContentSelected = onRichContentSelected,
)
}
@@ -274,12 +277,13 @@ fun TextComposer(
}
val menuAction = state.menuAction
+ val latestOnSuggestionReceived by rememberUpdatedState(onSuggestionReceived)
LaunchedEffect(menuAction) {
if (menuAction is MenuAction.Suggestion) {
val suggestion = Suggestion(menuAction.suggestionPattern)
- onSuggestionReceived(suggestion)
+ latestOnSuggestionReceived(suggestion)
} else {
- onSuggestionReceived(null)
+ latestOnSuggestionReceived(null)
}
}
}
@@ -398,9 +402,10 @@ private fun TextInput(
onResetComposerMode: () -> Unit,
resolveRoomMentionDisplay: () -> TextDisplay,
resolveMentionDisplay: (text: String, url: String) -> TextDisplay,
+ onError: (Throwable) -> Unit,
+ onTyping: (Boolean) -> Unit,
+ onRichContentSelected: ((Uri) -> Unit)?,
modifier: Modifier = Modifier,
- onError: (Throwable) -> Unit = {},
- onRichContentSelected: ((Uri) -> Unit)? = null,
) {
val bgColor = ElementTheme.colors.bgSubtleSecondary
val borderColor = ElementTheme.colors.borderDisabled
@@ -449,6 +454,7 @@ private fun TextInput(
resolveRoomMentionDisplay = resolveRoomMentionDisplay,
onError = onError,
onRichContentSelected = onRichContentSelected,
+ onTyping = onTyping,
)
}
}
@@ -918,6 +924,7 @@ private fun ATextComposer(
onSendVoiceMessage = {},
onDeleteVoiceMessage = {},
onError = {},
+ onTyping = {},
onSuggestionReceived = {},
onRichContentSelected = null,
)
diff --git a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml
index e3034e8dfe..18bd369d74 100644
--- a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml
+++ b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml
@@ -1,8 +1,11 @@
"Attiva/disattiva l\'elenco puntato"
+ "Chiudi le opzioni di formattazione"
"Attiva/disattiva il blocco di codice"
"Messaggio…"
+ "Crea un collegamento"
+ "Modifica collegamento"
"Applica il formato in grassetto"
"Applicare il formato corsivo"
"Applica il formato barrato"
@@ -12,6 +15,11 @@
"Applicare il formato del codice in linea"
"Imposta collegamento"
"Attiva/disattiva elenco numerato"
+ "Apri le opzioni di composizione"
"Attiva/disattiva citazione"
+ "Rimuovi collegamento"
"Rientro a sinistra"
+ "Collegamento"
+ "Aggiungi allegato"
+ "Tieni premuto per registrare"
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 527ab2b866..3145967bf0 100644
--- a/libraries/ui-strings/src/main/res/values-cs/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml
@@ -57,6 +57,7 @@
"Vstoupit"
"Zjistit více"
"Odejít"
+ "Opustit konverzaci"
"Opustit místnost"
"Spravovat účet"
"Spravovat zařízení"
diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml
index b1fd043b64..1578326b9f 100644
--- a/libraries/ui-strings/src/main/res/values-de/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-de/translations.xml
@@ -90,7 +90,7 @@
"Chat starten"
"Verifizierung starten"
"Tippe, um die Karte zu laden"
- "Foto machen"
+ "Foto aufnehmen"
"Für Optionen tippen"
"Erneut versuchen"
"Quellcode anzeigen"
diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml
index 60e4e78c2e..c56ad20a88 100644
--- a/libraries/ui-strings/src/main/res/values-fr/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml
@@ -57,6 +57,7 @@
"Rejoindre"
"En savoir plus"
"Quitter"
+ "Quitter la discussion"
"Quitter le salon"
"Gérer le compte"
"Gérez les sessions"
diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml
index f59c70a05a..18c981005f 100644
--- a/libraries/ui-strings/src/main/res/values-hu/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml
@@ -46,8 +46,8 @@
"Szavazás szerkesztése"
"Engedélyezés"
"Szavazás lezárása"
- "Adja meg a PIN-kódot"
- "Elfelejtette a jelszót?"
+ "Add meg a PIN-kódot"
+ "Elfelejtetted a jelszavadat?"
"Tovább"
"Meghívás"
"Ismerősök meghívása"
@@ -82,17 +82,17 @@
"Üzenet küldése"
"Megosztás"
"Hivatkozás megosztása"
- "Jelentkezzen be újra"
+ "Jelentkezz be újra"
"Kijelentkezés"
"Kijelentkezés mindenképp"
"Kihagyás"
"Indítás"
"Csevegés indítása"
"Ellenőrzés elindítása"
- "Koppintson a térkép betöltéséhez"
+ "Koppints a térkép betöltéséhez"
"Fénykép készítése"
"Koppintson a lehetőségekért"
- "Próbálja újra"
+ "Próbáld újra"
"Forrás megtekintése"
"Igen"
"Továbbiak betöltése"
@@ -106,7 +106,7 @@
"Csevegés biztonsági mentése"
"Szerzői jogok"
"Szoba létrehozása…"
- "Elhagyta a szobát"
+ "Elhagytad a szobát"
"Sötét"
"Visszafejtési hiba"
"Fejlesztői beállítások"
@@ -115,7 +115,7 @@
"Szerkesztés"
"* %1$s %2$s"
"Titkosítás engedélyezve"
- "Adja meg a PIN-kódját"
+ "Add meg a PIN-kódodat"
"Hiba"
"Mindenki"
"Fájl"
@@ -156,7 +156,7 @@
"Formázott szöveges szerkesztő"
"Szoba"
"Szoba neve"
- "például a projekt neve"
+ "például a projekted neve"
"Képernyőzár"
"Személy keresése"
"Keresési találatok"
@@ -200,17 +200,17 @@
"Megerősítés"
"Figyelmeztetés"
"Nem sikerült létrehozni az állandó hivatkozást"
- "Az %1$s nem tudta betölteni a térképet. Próbálja meg újra később."
+ "A(z) %1$s nem tudta betölteni a térképet. Próbáld meg újra később."
"Nem sikerült betölteni az üzeneteket"
- "Az %1$s nem tudta elérni a tartózkodási helyét. Próbálja meg újra később."
+ "A(z) %1$s nem tudta elérni a tartózkodási helyét. Próbáld meg újra később."
"Nem sikerült feltölteni a hangüzenetét."
- "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Ezt a beállításokban engedélyezheti."
+ "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyedhez. Ezt a beállításokban engedélyezheted."
"Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Engedélyezze alább az elérését."
- "Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonjához. Engedélyezze, hogy tudjon hangüzenetet felvenni."
+ "Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonhoz. Engedélyezd, hogy tudjon hangüzenetet felvenni."
"Néhány üzenet nem került elküldésre"
"Elnézést, hiba történt"
"🔐️ Csatlakozz hozzám itt: %1$s"
- "Beszélj velem az %1$s használatával: %2$s"
+ "Beszélgessünk a(z) %1$s: %2$s -n"
"%1$s Android"
- "%1$d megadott számjegy"
diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml
index cfd24d7c08..ad2738557c 100644
--- a/libraries/ui-strings/src/main/res/values-it/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-it/translations.xml
@@ -1,11 +1,33 @@
+ "Elimina"
"Nascondi password"
+ "Vai alla fine"
+ "Solo menzioni"
+ "Silenziato"
+ "Pagina %1$d"
+ "Pausa"
+ "Campo del PIN"
+ "Riproduci"
+ "Sondaggio"
+ "Sondaggio terminato"
+ "Reagisci con %1$s"
+ "Reagisci con altri emoji"
+ "Letto da %1$s e %2$s"
+ "Letto da %1$s"
+ "Tocca per mostrare tutti"
+ "Rimuovi la reazione con %1$s"
"Invia file"
"Mostra password"
+ "Inizia una chiamata"
"Menu utente"
+ "Registra un messaggio vocale."
+ "Ferma la registrazione"
+ "Accetta"
+ "Aggiungi alla timeline"
"Indietro"
"Annulla"
+ "Scegli foto"
"Cancella"
"Chiudi"
"Completa verifica"
@@ -13,24 +35,44 @@
"Continua"
"Copia"
"Copia collegamento"
+ "Copia link nel messaggio"
+ "Crea"
"Crea una stanza"
+ "Rifiuta"
+ "Elimina sondaggio"
"Disabilita"
"Fine"
"Modifica"
+ "Modifica sondaggio"
"Attiva"
+ "Termina sondaggio"
+ "Inserisci PIN"
+ "Password dimenticata?"
+ "Inoltra"
"Invita"
- "Invita amici a %1$s"
+ "Invita persone"
+ "Invita persone su %1$s"
+ "Invita persone su %1$s"
+ "Inviti"
+ "Entra"
"Ulteriori informazioni"
"Esci"
+ "Abbandona la conversazione"
"Esci dalla stanza"
+ "Gestisci account"
+ "Gestisci dispositivi"
"Avanti"
"No"
"Non ora"
"OK"
+ "Impostazioni"
+ "Apri con"
"Risposta rapida"
"Citazione"
+ "Reagisci"
"Rimuovi"
"Rispondi"
+ "Rispondi nella conversazione"
"Segnala un problema"
"Segnala Contenuto"
"Riprova"
@@ -38,73 +80,166 @@
"Salva"
"Ricerca"
"Invia"
+ "Invia messaggio"
"Condividi"
"Condividi collegamento"
+ "Accedi di nuovo"
+ "Disconnetti"
+ "Disconnetti comunque"
"Salta"
"Inizia"
"Avvia conversazione"
"Avvia la verifica"
+ "Tocca per caricare la mappa"
+ "Scatta foto"
+ "Tocca per le opzioni"
+ "Riprova"
"Vedi Sorgente"
"Sì"
+ "Carica di più"
"Informazioni"
+ "Regole sull\'utilizzo consentito"
+ "Impostazioni avanzate"
+ "Statistiche"
+ "Aspetto"
"Audio"
"Fumetti"
+ "Backup della chat"
+ "Copyright"
"Creazione stanza…"
"Hai lasciato la stanza"
+ "Scuro"
"Errore di decrittazione"
"Opzioni sviluppatore"
+ "Chat diretta"
"(modificato)"
"Modifica in corso"
+ "* %1$s %2$s"
"Crittografia abilitata"
+ "Inserisci il PIN"
"Errore"
+ "Tutti"
"File"
+ "File salvato in Download"
+ "Inoltra messaggio"
"GIF"
"Immagine"
+ "In risposta a %1$s"
+ "Installa APK"
+ "Questo ID Matrix non può essere trovato, quindi l\'invito potrebbe non essere ricevuto."
+ "Lasciando la stanza"
+ "Chiaro"
"Collegamento copiato negli appunti"
"Caricamento…"
"Messaggio"
- "Layout del messaggio"
+ "Azioni del messaggio"
+ "Impaginazione del messaggio"
"Messaggio rimosso"
"Moderno"
+ "Silenzia"
"Nessun risultato"
"Non in linea"
"Password"
"Persone"
"Collegamento permanente"
+ "Autorizzazione"
+ "Voti totali: %1$s"
+ "I risultati verranno mostrati al termine del sondaggio"
+ "Informativa sulla privacy"
+ "Reazione"
"Reazioni"
+ "Chiave di recupero"
+ "Aggiornamento…"
"Risposta a %1$s"
"Segnala un problema"
+ "Segnala un problema"
"Segnalazione inviata"
+ "Editor in rich text"
+ "Stanza"
+ "Nome stanza"
+ "ad es. il nome del tuo progetto"
+ "Blocco schermo"
"Cerca qualcuno"
+ "Risultati di ricerca"
"Sicurezza"
+ "Visto da"
"Invio in corso…"
+ "Invio fallito"
+ "Inviato"
"Server non supportato"
"URL del server"
"Impostazioni"
+ "Posizione condivisa"
+ "Disconnessione"
+ "Avvio della chat…"
"Adesivo"
"Operazione riuscita"
"Suggerimenti"
+ "Sincronizzazione"
+ "Sistema"
+ "Testo"
+ "Comunicazioni di terze parti"
+ "Conversazione"
"Oggetto"
+ "Di cosa parla questa stanza?"
"Impossibile decrittografare"
+ "Non è stato possibile spedire inviti a uno o più utenti."
+ "Impossibile inviare inviti"
+ "Sblocca"
+ "Annulla silenzioso"
"Evento non supportato"
"Nome utente"
"Verifica annullata"
"Verifica completata"
"Video"
+ "Messaggio vocale"
"In attesa…"
+ "In attesa di questo messaggio"
+ "Vuoi davvero terminare questo sondaggio?"
+ "Sondaggio: %1$s"
+ "Verifica dispositivo"
"Conferma"
"Attenzione"
"Impossibile creare il collegamento permanente"
+ "%1$s non è riuscito a caricare la mappa. Riprova più tardi."
"Caricamento dei messaggi non riuscito"
+ "%1$s non è riuscito ad accedere alla tua posizione. Riprova più tardi."
+ "Invio del messaggio vocale fallito."
+ "%1$s non ha l\'autorizzazione di accedere alla tua posizione. Puoi attivare l\'accesso nelle impostazioni."
+ "%1$s non ha l\'autorizzazione per accedere alla tua posizione. Attiva l\'accesso di seguito."
+ "%1$s non ha l\'autorizzazione di accedere al microfono. Attiva l\'accesso per registrare un messaggio vocale."
"Alcuni messaggi non sono stati inviati"
"Siamo spiacenti, si è verificato un errore"
+ "🔐️ Unisciti a me su %1$s"
"Ehi, parlami su %1$s: %2$s"
"%1$s Android"
+
+ - "%1$d cifra inserita"
+ - "%1$d cifre inserite"
+
+
+ - "Letto da %1$s e %2$d altro"
+ - "Letto da %1$s e altri %2$d"
+
- "%1$d membro"
- "%1$d membri"
+
+ - "%d voto"
+ - "%d voti"
+
"Scuoti per segnalare un problema"
+ "Selezione del file multimediale fallita, riprova."
+ "Elaborazione del file multimediale da caricare fallita, riprova."
+ "Caricamento del file multimediale fallito, riprova."
+ "Condividi posizione"
+ "Condividi la mia posizione"
+ "Apri in Apple Maps"
+ "Apri in Google Maps"
+ "Apri in OpenStreetMap"
+ "Condividi questa posizione"
+ "Posizione"
"Versione: %1$s (%2$s)"
"it"
"Errore"
diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml
index b6576d988c..5477f03af9 100644
--- a/libraries/ui-strings/src/main/res/values-ru/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml
@@ -57,6 +57,7 @@
"Присоединиться"
"Подробнее"
"Выйти"
+ "Покинуть беседу"
"Покинуть комнату"
"Настройки аккаунта"
"Управление устройствами"
@@ -98,9 +99,9 @@
"Загрузить еще"
"О приложении"
"Политика допустимого использования"
- "Дополнительные параметры"
+ "Дополнительные настройки"
"Аналитика"
- "Оформление"
+ "Внешний вид"
"Аудио"
"Пузыри"
"Резервная копия чатов"
@@ -139,7 +140,7 @@
"Ничего не найдено"
"Не в сети"
"Пароль"
- "Пользователи"
+ "Люди"
"Постоянная ссылка"
"Разрешение"
"Всего голосов: %1$s"
@@ -173,7 +174,7 @@
"Начало чата…"
"Стикер"
"Успешно"
- "Рекомендации"
+ "Предложения"
"Синхронизация"
"Системная"
"Текст"
@@ -232,7 +233,7 @@
- "%d голоса"
- "%d голосов"
- "Rageshake сообщит об ошибке"
+ "Встряхните устройство, чтобы сообщить об ошибке"
"Не удалось выбрать носитель, попробуйте еще раз."
"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."
"Не удалось загрузить медиафайлы, попробуйте еще раз."
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 29ecb56649..343bbbf3ae 100644
--- a/libraries/ui-strings/src/main/res/values-sk/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml
@@ -57,6 +57,7 @@
"Pripojiť sa"
"Zistiť viac"
"Opustiť"
+ "Opustiť konverzáciu"
"Opustiť miestnosť"
"Spravovať účet"
"Spravovať zariadenia"
diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml
index 76cdca0b2b..51d89853f2 100644
--- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml
@@ -48,7 +48,7 @@
"忘記密碼?"
"轉寄"
"邀請"
- "邀請朋友"
+ "邀請夥伴"
"邀請朋友使用 %1$s"
"邀請夥伴使用 %1$s"
"邀請"
diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml
index ab311752f6..26874d4a19 100644
--- a/libraries/ui-strings/src/main/res/values/localazy.xml
+++ b/libraries/ui-strings/src/main/res/values/localazy.xml
@@ -57,6 +57,7 @@
"Join"
"Learn more"
"Leave"
+ "Leave conversation"
"Leave room"
"Manage account"
"Manage devices"
diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt
index a67390c54b..e6b89b2ea5 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 = 2
+private const val versionPatch = 3
object Versions {
val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch
diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt
index 625d2cf7dc..cc254511bd 100644
--- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt
+++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt
@@ -17,13 +17,24 @@
package extension
import org.gradle.accessors.dm.LibrariesForLibs
+import org.gradle.api.Action
+import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.logging.Logger
+import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.DependencyHandlerScope
+import org.gradle.kotlin.dsl.closureOf
+import org.gradle.kotlin.dsl.exclude
import org.gradle.kotlin.dsl.project
import java.io.File
private fun DependencyHandlerScope.implementation(dependency: Any) = dependencies.add("implementation", dependency)
+// Implementation + config block
+private fun DependencyHandlerScope.implementation(
+ dependency: Any,
+ config: Action
+) = dependencies.add("implementation", dependency, closureOf { config.execute(this) })
+
private fun DependencyHandlerScope.androidTestImplementation(dependency: Any) = dependencies.add("androidTestImplementation", dependency)
private fun DependencyHandlerScope.debugImplementation(dependency: Any) = dependencies.add("debugImplementation", dependency)
@@ -87,10 +98,6 @@ fun DependencyHandlerScope.allLibrariesImpl() {
implementation(project(":libraries:permissions:impl"))
implementation(project(":libraries:push:impl"))
implementation(project(":libraries:push:impl"))
- // Comment to not include firebase in the project
- implementation(project(":libraries:pushproviders:firebase"))
- // Comment to not include unified push in the project
- implementation(project(":libraries:pushproviders:unifiedpush"))
implementation(project(":libraries:featureflag:impl"))
implementation(project(":libraries:pushstore:impl"))
implementation(project(":libraries:preferences:impl"))
diff --git a/plugins/src/main/kotlin/extension/KoverExtension.kt b/plugins/src/main/kotlin/extension/KoverExtension.kt
index 42b7a5bc1f..9185bfb1b0 100644
--- a/plugins/src/main/kotlin/extension/KoverExtension.kt
+++ b/plugins/src/main/kotlin/extension/KoverExtension.kt
@@ -97,8 +97,8 @@ fun Project.setupKover() {
defaults {
// add reports of both 'debug' and 'release' Android build variants to default reports
- mergeWith("debug")
- mergeWith("release")
+ mergeWith("gplayDebug")
+ mergeWith("gplayRelease")
verify {
onCheck = true
@@ -202,7 +202,7 @@ fun Project.setupKover() {
}
}
- androidReports("release") {}
+ androidReports("gplayRelease") {}
}
}
diff --git a/plugins/src/main/kotlin/extension/Utils.kt b/plugins/src/main/kotlin/extension/Utils.kt
new file mode 100644
index 0000000000..31fdd4f952
--- /dev/null
+++ b/plugins/src/main/kotlin/extension/Utils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 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 extension
+
+import org.gradle.api.Project
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+
+private fun Project.runCommand(cmd: String): String {
+ val outputStream = ByteArrayOutputStream()
+ val errorStream = ByteArrayOutputStream()
+ project.exec {
+ commandLine = cmd.split(" ")
+ standardOutput = outputStream
+ errorOutput = errorStream
+ }
+ if (errorStream.size() > 0) {
+ println("Error while running command: $cmd")
+ throw IOException(String(errorStream.toByteArray()))
+ }
+ return String(outputStream.toByteArray()).trim()
+}
+
+fun Project.gitRevision() = runCommand("git rev-parse --short=8 HEAD")
+
+fun Project.gitBranchName() = runCommand("git rev-parse --abbrev-ref HEAD")
diff --git a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts
index 015f0d4db8..80bc0f884e 100644
--- a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts
+++ b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts
@@ -17,23 +17,18 @@
/**
* This will generate the plugin "io.element.android-compose-application" to use by app and samples modules
*/
-import extension.koverDependencies
import extension.androidConfig
import extension.commonDependencies
import extension.composeConfig
import extension.composeDependencies
-import extension.setupKover
import org.gradle.accessors.dm.LibrariesForLibs
val libs = the()
plugins {
id("com.android.application")
id("kotlin-android")
- id("com.autonomousapps.dependency-analysis")
}
-setupKover()
-
android {
androidConfig(project)
composeConfig(libs)
@@ -46,5 +41,4 @@ dependencies {
commonDependencies(libs)
composeDependencies(libs)
coreLibraryDesugaring(libs.android.desugar)
- koverDependencies()
}
diff --git a/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts
index 3176856339..3194505e4e 100644
--- a/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts
+++ b/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts
@@ -27,7 +27,6 @@ val libs = the()
plugins {
id("com.android.library")
id("kotlin-android")
- id("com.autonomousapps.dependency-analysis")
}
android {
diff --git a/plugins/src/main/kotlin/io.element.android-library.gradle.kts b/plugins/src/main/kotlin/io.element.android-library.gradle.kts
index 572168b0c4..f3a84031e6 100644
--- a/plugins/src/main/kotlin/io.element.android-library.gradle.kts
+++ b/plugins/src/main/kotlin/io.element.android-library.gradle.kts
@@ -25,7 +25,6 @@ val libs = the()
plugins {
id("com.android.library")
id("kotlin-android")
- id("com.autonomousapps.dependency-analysis")
}
android {
diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt
index 4d5c9aa216..30f5d2afe9 100644
--- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt
+++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt
@@ -55,7 +55,9 @@ class MainActivity : ComponentActivity() {
sessionStore = sessionStore,
userAgentProvider = userAgentProvider,
clock = DefaultSystemClock(),
- )
+ ),
+ passphraseGenerator = NullPassphraseGenerator(),
+ buildMeta = Singleton.buildMeta,
)
}
diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.kt
new file mode 100644
index 0000000000..ab0117fd38
--- /dev/null
+++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.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
+ *
+ * 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.samples.minimal
+
+import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator
+
+class NullPassphraseGenerator : PassphraseGenerator {
+ override fun generatePassphrase(): String? = null
+}
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 7706cdbb5d..286a80d71d 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
@@ -27,6 +27,7 @@ import io.element.android.features.roomlist.impl.RoomListPresenter
import io.element.android.features.roomlist.impl.RoomListView
import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
+import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.dateformatter.impl.DateFormatters
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
@@ -76,12 +77,20 @@ class RoomListScreen(
leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
roomListDataSource = RoomListDataSource(
roomListService = matrixClient.roomListService,
- lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
- roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
- sp = stringProvider,
- roomMembershipContentFormatter = RoomMembershipContentFormatter(matrixClient, stringProvider),
- profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
- stateContentFormatter = StateContentFormatter(stringProvider),
+ roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
+ lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(
+ localDateTimeProvider = dateTimeProvider,
+ dateFormatters = dateFormatters
+ ),
+ roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
+ sp = stringProvider,
+ roomMembershipContentFormatter = RoomMembershipContentFormatter(
+ matrixClient = matrixClient,
+ sp = stringProvider
+ ),
+ profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
+ stateContentFormatter = StateContentFormatter(stringProvider),
+ ),
),
coroutineDispatchers = coroutineDispatchers,
notificationSettingsService = matrixClient.notificationSettingsService(),
diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt
index cc54fd051f..06e8fb02f4 100644
--- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt
+++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt
@@ -29,7 +29,7 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.plus
object Singleton {
- private val buildMeta = BuildMeta(
+ val buildMeta = BuildMeta(
isDebuggable = true,
buildType = BuildType.DEBUG,
applicationName = "EAX-Minimal",
@@ -37,16 +37,10 @@ object Singleton {
lowPrivacyLoggingEnabled = false,
versionName = "0.1.0",
versionCode = 1,
- // BuildConfig.GIT_REVISION,
- gitRevision = "TODO",
- // BuildConfig.GIT_REVISION_DATE,
- gitRevisionDate = "TODO",
- // BuildConfig.GIT_BRANCH_NAME,
- gitBranchName = "TODO",
- // BuildConfig.FLAVOR_DESCRIPTION,
- flavorDescription = "TODO",
- // BuildConfig.SHORT_FLAVOR_DESCRIPTION,
- flavorShortDescription = "TODO",
+ gitRevision = "",
+ gitBranchName = "",
+ flavorDescription = "NA",
+ flavorShortDescription = "NA",
)
init {
diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt
new file mode 100644
index 0000000000..e49dc0317b
--- /dev/null
+++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.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
+ *
+ * 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.testutils
+
+import android.annotation.SuppressLint
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+
+@Stable
+@Composable
+fun withFakeLifecycleOwner(lifecycleOwner: FakeLifecycleOwner = FakeLifecycleOwner(), block: @Composable () -> T): T {
+ var state: T? by remember { mutableStateOf(null) }
+ CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
+ state = block()
+ }
+ return state!!
+}
+
+@SuppressLint("VisibleForTests")
+class FakeLifecycleOwner : LifecycleOwner {
+ override val lifecycle: Lifecycle = LifecycleRegistry.createUnsafe(this)
+
+ fun givenState(state: Lifecycle.State) {
+ (lifecycle as LifecycleRegistry).currentState = state
+ }
+}
diff --git a/tests/uitests/build.gradle.kts b/tests/uitests/build.gradle.kts
index ec17dcb601..b141b85be9 100644
--- a/tests/uitests/build.gradle.kts
+++ b/tests/uitests/build.gradle.kts
@@ -43,6 +43,21 @@ dependencies {
testImplementation(libs.test.junit)
testImplementation(libs.test.parameter.injector)
testImplementation(projects.libraries.designsystem)
+
+ // Paparazzi 1.3.2 workaround (see https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md#132---2024-01-13)
+ constraints.add("testImplementation", "com.google.guava:guava") {
+ attributes {
+ attribute(
+ TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
+ objects.named(TargetJvmEnvironment::class.java, TargetJvmEnvironment.STANDARD_JVM)
+ )
+ }
+ because(
+ "LayoutLib and sdk-common depend on Guava's -jre published variant." +
+ "See https://github.com/cashapp/paparazzi/issues/906."
+ )
+ }
+
ksp(libs.showkase.processor)
kspTest(libs.showkase.processor)
diff --git a/tests/uitests/src/test/kotlin/ui/ComponentTestPreview.kt b/tests/uitests/src/test/kotlin/ui/ComponentTestPreview.kt
index 0c25f29031..ddb730f2fc 100644
--- a/tests/uitests/src/test/kotlin/ui/ComponentTestPreview.kt
+++ b/tests/uitests/src/test/kotlin/ui/ComponentTestPreview.kt
@@ -17,6 +17,7 @@
package ui
import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.Dp
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
class ComponentTestPreview(
@@ -27,6 +28,10 @@ class ComponentTestPreview(
override val name: String = showkaseBrowserComponent.componentName
+ override fun customHeightDp(): Dp? {
+ return showkaseBrowserComponent.heightDp?.let { Dp(it.toFloat()) }
+ }
+
override fun toString(): String = showkaseBrowserComponent.componentKey
// Strip common package beginning
.replace("io.element.android.features.", "f.")
diff --git a/tests/uitests/src/test/kotlin/ui/S.kt b/tests/uitests/src/test/kotlin/ui/S.kt
index 95a759e4ae..6baa7dc1a7 100644
--- a/tests/uitests/src/test/kotlin/ui/S.kt
+++ b/tests/uitests/src/test/kotlin/ui/S.kt
@@ -94,6 +94,8 @@ class S {
) {
val locale = localeStr.toLocale()
Locale.setDefault(locale) // Needed for regional settings, as first day of week
+ val densityScale = baseDeviceConfig.deviceConfig.density.dpiValue / 160f
+ val customScreenHeight = componentTestPreview.customHeightDp()?.value?.let { it * densityScale }?.toInt()
paparazzi.unsafeUpdateConfig(
deviceConfig = baseDeviceConfig.deviceConfig.copy(
softButtons = false,
@@ -104,6 +106,7 @@ class S {
false -> NightMode.NOTNIGHT
}
},
+ screenHeight = customScreenHeight ?: baseDeviceConfig.deviceConfig.screenHeight,
),
)
paparazzi.snapshot {
diff --git a/tests/uitests/src/test/kotlin/ui/TestPreview.kt b/tests/uitests/src/test/kotlin/ui/TestPreview.kt
index 868b310b55..9238607dc5 100644
--- a/tests/uitests/src/test/kotlin/ui/TestPreview.kt
+++ b/tests/uitests/src/test/kotlin/ui/TestPreview.kt
@@ -18,6 +18,7 @@ package ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import com.airbnb.android.showkase.models.ShowkaseElementsMetadata
import io.element.android.libraries.designsystem.preview.NIGHT_MODE_NAME
@@ -26,6 +27,8 @@ interface TestPreview {
fun Content()
val name: String
+
+ fun customHeightDp(): Dp? = null
}
/**
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Day-0_1_null_0,NEXUS_5,1.0,en].png
index 5790bbb52e..aae2beab88 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Day-0_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Day-0_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5fc43706603ce52fa3495fa4e6dfa9aa684540a9dec996a5f705427d34ffb55c
-size 23274
+oid sha256:6e66e3395d8ce9b9b98ba21c99a82111f3e007e31d5845524f3c19284f478a8c
+size 23232
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Night-0_2_null_0,NEXUS_5,1.0,en].png
index acac72afff..7f6c3ded31 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Night-0_2_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_AnalyticsPreferencesView_null_AnalyticsPreferencesView-Night-0_2_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:753cfd0885800b622e119565ac98d46d0d339d8440cf445fcb45d4af866884b9
-size 22371
+oid sha256:9e0fee8d19a8522b34ec4ec3bbf8473284a01ec827aeb520f58b8df67079df53
+size 22367
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_SearchMultipleUsersResultItem_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_SearchMultipleUsersResultItem_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png
index 2bd50fb9bc..7199f240fb 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_SearchMultipleUsersResultItem_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_SearchMultipleUsersResultItem_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:748900111b5b9cb1235f979d889098366ed66a88e8078224ec2780feac47a2e8
-size 86365
+oid sha256:0334ad25584ed508739fefb75a44d419d3904620e957c9ffa125985532d0bc10
+size 86328
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..00ac5ac158
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:925d6129884511860505ed7ba3cec9f2ddb50105aa1f74d9ff2dc8bbed22f8d4
+size 37706
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..c6ddfe89fc
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a46f95f72addd2775c6dfd4dcbfe2bfb2f68eb0eaa4fe2676c3d17b285390e27
+size 32718
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_0,NEXUS_5,1.0,en].png
index cef3fc6c73..1618c3e638 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7eca99bfe466baa273ce4b485eb534b25d765b7409c9636d322603e96b9e3419
-size 313750
+oid sha256:4595089f2c885b73cdbd79d626c3eab79da657fef1a2ce2390e761e2469a4e0b
+size 313738
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_1,NEXUS_5,1.0,en].png
index 3606f7454a..df970a3ce3 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:84bdea70c97fd350a5465632bdfb480f5cc441e8cdda4420b73e6f96a2a9846c
-size 309062
+oid sha256:452876b1189da12691bca146c63202324593c791e854a1f8918a71c556a2c454
+size 305809
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_2,NEXUS_5,1.0,en].png
index e5c16e58e6..ac4bd00348 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2fc68b4e7b66cb0ee9ce86445c522de1c248173331e38291cc3f9c0a4d1dbb02
-size 315615
+oid sha256:f496246dd0186280943dbf3057956eef19fdfd64ccd8458c49edb32cb97f24d3
+size 315968
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_3,NEXUS_5,1.0,en].png
index 15721984cb..254fa69e4f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6a47c109da395d2ee7b4237c06b8169e5dc9e587b13fa18e520d9769d50ef904
-size 309218
+oid sha256:a127838495bdd790e1eeb6632d9fcfb256edd8ebe5d83f222434458b9687679c
+size 307141
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_4,NEXUS_5,1.0,en].png
index b84f054f96..148df5143d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Day-0_1_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ae89eb426653b9fb34a309904e1b1d6f8502689a8712470a3d4f388c35219c20
-size 314617
+oid sha256:c5c86f4f08460382392eb9404fecdc93b6ff056e0d4116f39fc6308d1061c163
+size 314537
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_0,NEXUS_5,1.0,en].png
index 595ebbc34d..7cd11d4937 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:59bb85b2846fc6554715ba06f7aa30b35843ff494c0178bb815ce1a5939112e1
-size 408754
+oid sha256:9df131c8d7482b7b00df587cca8b385d0cd57954240368f349a24e8e6ae36322
+size 405021
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_1,NEXUS_5,1.0,en].png
index f9d5628574..b5ca95ff71 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:98437f10ee4ae9c51606e07e15918c67fa72ac2e78f581c7cb0cdeb672f51bbd
-size 394881
+oid sha256:d5070637e034d7ecc1db4944cb05ec056e0cbea342769a32ee030b47110f8e4b
+size 386301
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_2,NEXUS_5,1.0,en].png
index a57a330934..a6d71eb418 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:19e49c236cd452f1d65bced61a7f7e56ee55ae508a3d9be95531f1ba4c2ea0b1
-size 401190
+oid sha256:662e5beadd1baea7825ce047df87c4429f720ea7b4dd53f8ea576ada91814568
+size 399093
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_3,NEXUS_5,1.0,en].png
index 7d985f2111..8791e1399c 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cba5c736930368a47a265a841e53072cd58aecd10a49982581c46ab19ee5f901
-size 379887
+oid sha256:3a76b73d9e5e94b8dd8b27addd07593bae43423b92048110b213413e1185100e
+size 373027
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_4,NEXUS_5,1.0,en].png
index 82e5112fdb..36442edf97 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_OnBoardingScreen_null_OnBoardingScreen-Night-0_2_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:38e645b8dcba672383b58435524a7ef188a2e0891234dcea7249e0b163330ab1
-size 409549
+oid sha256:d1a672fea8d9f8b6a1c9646982438bf3dc66874a472c0c2269d74fccffed3a98
+size 405810
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_0,NEXUS_5,1.0,en].png
index 42cc37bc7e..d634450d0f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a79a90406a98deaf70fd3234dd1f2bbd51060b1074a5a285d3315bc90191bcd6
-size 34775
+oid sha256:95deebee51ef59f77316e0cd1310e0c80083d40d033ac19c8e81dadb930457bb
+size 34703
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_1,NEXUS_5,1.0,en].png
index d369206aa9..721238e375 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2a638452c7d6554a534325b4141bf35aaeec817eec8ebbe50a4efab077164b88
-size 39183
+oid sha256:870b59448a52edbca99ccf63981f259d096652f84a745b2341a8e8e47f15fbfc
+size 39135
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_2,NEXUS_5,1.0,en].png
index 44fe8a5840..45c130db54 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d97257fbbd85d70a319795af9738a3773892d96c6b64e9e8793e693d70929e33
-size 44178
+oid sha256:2fe96d9c6a48832f2045972d1ecd4ca87e56094e9fa0ef863185657e4e4b395a
+size 44156
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_3,NEXUS_5,1.0,en].png
index 78f1085afe..38f96907dc 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:70c14eee23c6654149623900aa1edea45ad52e09d21a602af56804f6b8c6d8fc
-size 47182
+oid sha256:dd38023674b5f480a27edfc2dcf8df5fcd20678e3203244b17b7add08a203ed4
+size 47161
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_4,NEXUS_5,1.0,en].png
index a32c442bfb..7e19472bcf 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2539cd3bb3a178fa3e64a7832811e693ebe5529c4923edb185af09f49d7f00ad
-size 29604
+oid sha256:77f0d0a20a90d68adcf35a62fa5ed807e74f9820a7550ddf44ac969a79f2566d
+size 29467
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_5,NEXUS_5,1.0,en].png
index 9d35b240a3..682e334a9c 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1b133f65f39ef16d52f4f92e33cb1f38e2c7ac22d61a146ba54937b2d903d54b
-size 124269
+oid sha256:0a4fb6ff3a6202d9b3a86e19d1a44811b57fd7c7cb160713afdc672cd0dc7916
+size 123668
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_6,NEXUS_5,1.0,en].png
index dd72111031..b654a85479 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8e545dca45645bf7874d183697250ca6f8f916121e5a72a07409917a62d1dc94
-size 35440
+oid sha256:d4437d93f3343005397a4ae3e37c7c49b626efcd68febb19e8c0e9021a634f69
+size 35379
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_7,NEXUS_5,1.0,en].png
index 6f8432a888..7307585945 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Day-0_1_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:362bcdd3b7e32613da7b9f1a6a2dc23a097e8e9b671d185220d9a243326c5b4f
-size 37556
+oid sha256:f12dee5614338df53f433f5c81f759096aef93ed7adee2144159c69018b19e9c
+size 37864
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_0,NEXUS_5,1.0,en].png
index 93b2003a01..1d9ee8da83 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bcb1ac2705ac87789c3fde971958bd4b719ce2e303ea80b93e3a1ca56959b880
-size 32882
+oid sha256:136344130e78e56c2bee0157ac2ede86daa5a4ff3817b792f4f49894ddc2a1d5
+size 32741
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_1,NEXUS_5,1.0,en].png
index b90a1f6af0..724d5dac7b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5bda72b3a7befebd49870718c7bfe305ea6fb9b9b96079ad481dd104d0ef05d4
-size 36429
+oid sha256:395c15bcdda264a05b22d63d17fc8e08a47f56fbb199e18beb11da412ffc4dcc
+size 36353
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_2,NEXUS_5,1.0,en].png
index 42aa83f958..e028c19801 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:80d6f893a6d3d01518c381eb5e7a2ccd261d000ee64fa8eff81a6805f24f5bf5
-size 39383
+oid sha256:77e17ef33a5c3aabfa3ffcf4481173a353c3d6eaea65506f4925e9712d1e7fdf
+size 39476
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_3,NEXUS_5,1.0,en].png
index 386d8efc61..eef510b459 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2b5e4dc970dae62a44231387a2c77b355546553cb0147ca86d2e62fba3795009
-size 44321
+oid sha256:bc56d3a9c61f93d5d2606dda97e11025d8b95abc65c2bad28de5bddcfb2e0620
+size 44270
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_4,NEXUS_5,1.0,en].png
index 7934af8a68..f10763d63d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8b0e5e5d17571b37b764d699831a44d461aa4237ac3232bf9b1541351c0bdf29
-size 27871
+oid sha256:fed8b346d4d40f9b319d887ec10d012422ff5b40ea1bbd466e5534523d0edbfe
+size 27537
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_5,NEXUS_5,1.0,en].png
index 6f1ad447a9..f1f4698620 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:be7fbaed8c51b8aaa13875db6b73eed0939beffd62565470dbd6a291fdcbbca7
-size 108688
+oid sha256:4df29f963b0bfed1a33839dcff1bfd679109bba1da0f96e853e1a87efe7eec33
+size 108056
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_6,NEXUS_5,1.0,en].png
index 6b1952a412..fd249f1d3b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0ccb4cce1db9083561047204aa570727c870a2d1974534fbfc8482b3eaf4fa53
-size 33628
+oid sha256:93d81771457c2a23f59bfe636ff7cc3a55a12e80cce54093c03ccca60df26318
+size 33491
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_7,NEXUS_5,1.0,en].png
index e6c69623e8..a23bc22fd5 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_CreatePollView_null_CreatePollView-Night-0_2_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9fd2419fbcfbf5df4847e47416f3717c1176c5e9e5b529503916f6413c47e5cf
-size 32712
+oid sha256:b3334f14c4efc92702cdbcbd866ef6a12b9671c7b397e1db1fb4c6be1304d635
+size 33131
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png
index df29aa76c6..da1d8ddbfd 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:592c1cc5bc0e8ac2912d0cc9d0fa2aaeec1ba5f49a23415ff4abe16634f7c9f7
-size 39896
+oid sha256:da3f8614862ddacfd9b54afcd3bf091bec335e1489d7cbffc826059a89ae5478
+size 58453
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png
index 06bad1718a..c99349ac0c 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:504c3ab78b9e10fe8c26d003f487397835d1c548ec11e624bb1317500282d6a0
-size 39415
+oid sha256:1fd599f2e93432d5d5555f086b60059e84452a0da3c2a69d932042b7ee4d3bac
+size 57922
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png
index 6c581d5c97..b228d561bd 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:22939cc1060893e6440f755701303be0fb8c8c1c39256af1ce7b12a1289f7f4d
-size 39391
+oid sha256:9e9641228e0482c8e5500bb999b180e79cbfcb6a13e121f3be68aae5c58a6839
+size 57953
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png
index 48b42a7ee4..3df61a1ef4 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9e08bcbb2d09af6d796068a4c64ff5f03d73aadefe3a20f0a2793333f73291f4
-size 35414
+oid sha256:1c1c5b00098e5860d4c69ef9a652483ea1192d9fa10f5449fb817769ab054183
+size 36128
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..50da3b5d6c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4fe6625641cf06c45a817e9d10bc0d7296aa59160fc5a3e9dc28304e5b794125
+size 57943
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png
index 9652e4df09..47fbdb6c0f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fa224e2d1273a69ea0a3d56765d2dd149b7d295f576c43c09c1eceee92a5c528
-size 37182
+oid sha256:e85b252d0d6c166f987974ebcf9a6b58b924316733b868ad0ed5a61413bda381
+size 54577
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png
index cf646c4351..10d7a6c5e1 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9a03a4ba4a0a52dfb0c90f4fde244776222df5d00b76f4a41259f13f7847d81e
-size 36850
+oid sha256:4f246ce9f60b25d9739d73e2aed98132dfb3a0a694acbf320ff796f35f805fdc
+size 54273
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png
index 48ba4870ab..4b03ace56b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:adc64ea11ab2a5ac4e1533e24ba1c7a75cc05d1722b26f949a9d97853b75450b
-size 36795
+oid sha256:d0403a4bb8da3d36922cb5362977af9b3052624525def05e5236f4f922384c31
+size 54297
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png
index 49dfb9077a..01e0e907a6 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cf35986b43c0565658655241243d0704000e79131eb4f2291c2da8d66f014eaa
-size 31009
+oid sha256:824774a1e19974090de4cb32e06a7f984b5db238c3dc8d04a35d3457e647ac84
+size 31999
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..449fb69524
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0820b462a0dc103b66aef78528ae5ce13a55e2614a7a197e06713366c422893
+size 54313
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Day-2_3_null_0,NEXUS_5,1.0,en].png
index c8c9509170..445b507065 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Day-2_3_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Day-2_3_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:23f5026bb468e841c9647d6ccc2cdc6eac52b18e5ad21b2c45192f6519141ff0
-size 26376
+oid sha256:c3128ee234cf48f37fa0432f904d2e791614d20c82c16dc639edb849b42c4fe4
+size 26348
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Night-2_4_null_0,NEXUS_5,1.0,en].png
index e8127ff0ec..ab1f6f8645 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Night-2_4_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_AnalyticsSettingsView_null_AnalyticsSettingsView-Night-2_4_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a8923a5a2b15b5cca3c9c77ebd587b60c7dacd7d68d04d9f8f671de2175d997f
-size 25180
+oid sha256:4ceb2fe1dd526d36f90efe2bdcc4e4c5d407709fee053937ff8e53b760f85e0a
+size 25186
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Day-4_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Day-4_5_null_0,NEXUS_5,1.0,en].png
index 95be5a188b..618fea5b46 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Day-4_5_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Day-4_5_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:093a2b69dfa7bacf90a2cd7e1d6643f008539166563bf19e3e544560d8c5c345
-size 35370
+oid sha256:89c50b83f37d5850baaf523ee2741b1b685dafe5a31f738f323ddf97ac14a642
+size 35529
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Night-4_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Night-4_6_null_0,NEXUS_5,1.0,en].png
index 78ec8a14c5..27721e2069 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Night-4_6_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_ConfigureTracingView_null_ConfigureTracingView-Night-4_6_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7cf27bfc83479f38722ad245a48fb5f0d64d47fe58f703c639cd3a9732e7d588
-size 31637
+oid sha256:fe4cd184987d4b0a545a8f3ce82b6bdb86d3a93038c0b4728008125cc40c5c77
+size 31818
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png
index dc66dbf5c9..c67cb249df 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:decba5aecbaf17385c6b92396b48ba7e8cb1d68c8319bef3334415cd42fee828
-size 36034
+oid sha256:1e55a3501da2a4217a245b7e6a9527ab5963a018dfc70aa2bce62c558f88f75b
+size 36138
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png
index d0bf3ae9aa..d47de6ed61 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:97aec529c58486f1686e6c15f0cb9ed5286ed169115d8053b734bc491f724a28
-size 33309
+oid sha256:4c5baa96c59c7e61f84be5e730c56a6f18b0f180fa4c2c7b9bd64e53b20d52b5
+size 33392
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png
index af36db4a67..bd1f36da91 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b927c63f95092c5345ceb35506e160c9ce6ce0348bba33d258c390cd8bd158de
-size 35825
+oid sha256:d659a2a7be6a053aa8c4468f2e833687de20248a6b2fd9dcd4a5d47ea54df20c
+size 35913
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png
index f52f17c794..f8484d174b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6886af0b4f9e674aafd2674c6b77e707a08fb4a0f626169581ed3245b4241972
-size 35751
+oid sha256:4ddd2c2a6bec2701d8ca8ae755d31713d5dbfcbbe30642f636f0a8ce22712996
+size 35843
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png
index aa0300ea79..9d718720b8 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ef4a2ee8e94f57164b5834fa331c37a3bf7bee15aae17fa3c5fb2a9c82e3e3a9
-size 33508
+oid sha256:6cacbc497b8211b3c18cec602e24ecca799c07790c1c6766b91c2c84556161f5
+size 32322
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png
index ab746a0a59..8511338d8a 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c1acca39f67a574b9b467827515b2f3442b4bb532a6d2cf3ad611ae79098815d
-size 36659
+oid sha256:b8cee12ff95d1ccfce0f4445a8e32cea154d8f956cf05888129c23f3d9fe43d5
+size 36865
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png
index 5455f6dd66..e421d4cb14 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:78eab361860e4b4153a680fad703dc61c69c80a28af0a90b0a6add85178eea4f
-size 49828
+oid sha256:e08c52be5c9c77c5d3743522f4b2ac4d5dad5cebbaba11245202a9f0e2ca1ccb
+size 50275
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png
index a199e849e1..65fd53c320 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:99b99dd9caaecaf34f5a354251833375456c43e4c31bb2dc020b8caa396fe7e3
-size 33088
+oid sha256:c54ea0c5ffdb094bea1fe07208a5ed9683f7104ca9d7c85a452c5f0983aeff47
+size 33119
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png
index b20052234e..9460689a1b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c427f18e4dcb5539069bcb6cb2d81160208a21fa5d1ca0c4086aed27b92013e8
-size 33043
+oid sha256:bf6f825427bab3e344617e78971521a1cbbd2daaaf132334b8bd32335111c605
+size 33075
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png
index 5c1fbe5dfd..15f32e9239 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5d3d53b374e9b59ec860951fbcd1ddb566c90b3debe335c5cba8f2d9e55f508d
-size 30346
+oid sha256:144fe1c105acf228ddf28d29f141cff5eca275529e11e38877bdfc8c5a37065f
+size 29042
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png
index a7f1ffde07..546f033245 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0c85782934a98034a75e507f9c4e8316c1aa00e5628ae7fe3092b7f58ae3540c
-size 32286
+oid sha256:e7e8b50a6e456b812b0ae5a58442361d85051951979634f89148e09812c0bada
+size 32312
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png
index f731a246d9..949f04d314 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:29160e672d2bda2cdb69b7a2c2357316696637db23dc1de0665cc80308303399
-size 47051
+oid sha256:3105639c450f6f867cf5592c2cc5c029f20d01c31273ac2eb7680d8d9548f1c0
+size 47447
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png
index b2db57cdb8..6f1f8d7ab5 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:781e626db4e9491d1fd9d8098a6e62ece4d7034e1447d168583bdc5a308f371d
-size 45249
+oid sha256:18a6da77975c5748f650fdfc11d05bdb6db28df4fba132a314edd8fbb0354d90
+size 36950
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png
index 5dfbf46f31..1ab4b1804c 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3d158b7003c5f84d9aab2ca85bcdce6011ce7ce7c1eede42a343d460610eddeb
-size 44573
+oid sha256:5dc4cc6825afe60718eb4c19ba2a7269296089e0050e0a361545613913ca3be4
+size 36594
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png
index 8c46bc7976..b6f407d705 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c9d7de855fc1442535920061110c4b7dd71535f90dd77e55469fdec1941b5d9f
-size 48266
+oid sha256:2b21a97c05098dfda4e23b670ff122ed2a60040536d599cd8e6921ed9ed4c45f
+size 38825
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png
index 65f2b0c30e..00b1b344b1 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bab34cb8a11b8056c5f4a0ad10003eec4c2f5dbb128b4339e12c5687c8ade2e5
-size 48167
+oid sha256:95fdb43e8761a19fed0ff146af3ed87968c78268c37933d014d15afe1401e67d
+size 38773
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_0,NEXUS_5,1.0,en].png
index 85063d3c6b..1c8b28b5ad 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9ae7fef08fe8f5d08892406cc4c979cebdc8e7751dfda78bac85f4bb0956c245
-size 70071
+oid sha256:b80e48cadbb6e76fc6675eb0a2c49d6495be6a8145d649ad90c2bce10e8695a6
+size 72701
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_1,NEXUS_5,1.0,en].png
index e02ea695e0..d43f744d13 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3fc0cdabd97d39da8369f1945b9c3d7c9c05baa6da83c029c8ff99ddd8c8b300
-size 206672
+oid sha256:52caeed04914fee9ee74c28427b3a51215eca02104f544dd53dc280c3c1c5f98
+size 145307
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_2,NEXUS_5,1.0,en].png
index 0ed580ca73..db397c15a6 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e4e898a358de8246f7a835805994f9865e519a894b426f9a1234a998d13c54ab
-size 61356
+oid sha256:350ae21a44e27292bb1ea44fbad0e9cea8dc695997482c9fa3ad6cafa6330a4c
+size 63792
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_3,NEXUS_5,1.0,en].png
index 85063d3c6b..1c8b28b5ad 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_1_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9ae7fef08fe8f5d08892406cc4c979cebdc8e7751dfda78bac85f4bb0956c245
-size 70071
+oid sha256:b80e48cadbb6e76fc6675eb0a2c49d6495be6a8145d649ad90c2bce10e8695a6
+size 72701
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_0,NEXUS_5,1.0,en].png
index a8be95376f..6ae1a1f2ed 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f421bfb415b006742fc889588b983f682b82b63d4c28c8b896e94709672ca671
-size 67100
+oid sha256:7696307abd3ca96ae3c036cbf3088c2be08b4e4aabb861b0c7472460ab795303
+size 69558
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_1,NEXUS_5,1.0,en].png
index c4f73feb5d..542fc8d94c 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0c853ca824f30bb99ab2032e87d9f7ebf8ffb187aae769062381db6afce64a9a
-size 202452
+oid sha256:f7409716efc9323aafb52eeb498330029753ad9ec319afa1027d22feb238e589
+size 141187
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_2,NEXUS_5,1.0,en].png
index 2f9f29cafb..58120c675c 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ca56f3993bc4713aa16e54bdcff24f96804f84dc8e9557247c56caf53fd14831
-size 56251
+oid sha256:98368df14dcc182d759ac104189ebf43aff4e1a0445b0af1d6909a614cd2102b
+size 58283
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_3,NEXUS_5,1.0,en].png
index a8be95376f..6ae1a1f2ed 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_2_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f421bfb415b006742fc889588b983f682b82b63d4c28c8b896e94709672ca671
-size 67100
+oid sha256:7696307abd3ca96ae3c036cbf3088c2be08b4e4aabb861b0c7472460ab795303
+size 69558
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png
index 966a6e3d0d..273d91a2e9 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:635af24c8306297ac954aa7ddb4286c36904b763e8f32acbbad841334dda8253
-size 22352
+oid sha256:1d8a2dbbe7f9a2f10cd695b98fc0228e4d77733dd5de6c8428fc0cd1b4007ae4
+size 14486
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png
index 3d6ff401a2..49424709fb 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c8b5d1c152ed4f3334fc5d6fa74546a1fa7f50dd4975f6f108028a34be0db63c
-size 20248
+oid sha256:375d40b416b82cf9eb83bd76ff69d3eb13951b58922bd68c705709eb188c5198
+size 13008
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png
index 201c88ab4a..b7de5bad45 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a7caaac71c2c280fc18036e725d9a64aae77e351b494440bbac6f6776658c856
-size 22544
+oid sha256:89e598cde19e3e51a00324b9effcbeac25f82fc21b349f67da03944c1561210d
+size 14611
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png
index cf98fbcce8..08225a6c1f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:44e2a0e5c2adb1d80c2bf2abfc3ab481ef28a7dd44de8fcdca73d87933219f65
-size 38735
+oid sha256:cb0159f528c7361a9683ddf8ac55d08bc8f0b133c73b31c8268df8d58d0fa53f
+size 23615
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png
index ab06f857b9..5b41e4da88 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:29a24d3f9c01d866f2b3cfdff1460a47f956c2b30865f1c0c74a505a7ee40581
-size 30824
+oid sha256:b2d39a27cbcdad79313ee165bb1cbf7f161c5e8fefb8fe051f376d5e455cd529
+size 20320
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png
index baca4fe9e0..c903e5641a 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:52009b1f8c129638c82568c0d23822cbed4de45f6ce49062aa38a79dfedecbe8
-size 23041
+oid sha256:fab48c418797b4985bf3b92aa11663c484fca0289b16c969c85f402342eed145
+size 14868
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_6,NEXUS_5,1.0,en].png
index 03ff1f7b4b..ba87e217b7 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_3_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:68ceadfa7696c8350c45bc4c47553e8fe8c10e283ae2a3420a4192160bd97ab7
-size 22925
+oid sha256:cabfecdde0ababf6831384589bf0b5bc32629428d61658c60477067d6792140d
+size 16370
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png
index 8da2d0200a..0f3c435241 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:52dcc40a72b799510a4400662b462bba34bbb6be98a35eaa5c3edc3a671aa786
-size 23016
+oid sha256:dd014eb7241ac06417aa8677bdc2e8f995e31e3c27e979256c63a5e9718c2bfa
+size 14856
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png
index 7d7ea4300c..30eddd7e79 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6099039f33757df49245866f6dbcca9576b216b1fe9e1f87c6c4f4e92add153c
-size 20945
+oid sha256:9a4425c47303f82a286e343bd77829793f331de7fb10db50d9c2b7309d98d362
+size 13260
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png
index fbdffe0bb4..172560a9ba 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6f5586f7c60ea5959713a66cb97397c5d834b27100e1603bcbb98c430dc0873e
-size 23539
+oid sha256:2be42690dc43bd5db978cf54ac8182ba4fa009b766528d1141279ee588a2b186
+size 15103
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png
index f76a02a956..3afdae13ca 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:10d2ea969c9cd2bcd809e72cc56d8ae086b1b4d848636c55e9db5ef23783620d
-size 43647
+oid sha256:61973949f719777b9c28b0657798cd0c745635ce90d2fb9eed17b396b0ec3e79
+size 26051
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png
index 350f21eeb0..49a0180da1 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e220d275226ae8820e0119a767b2baf964f2173c430531a379e338bdf9e08adc
-size 35530
+oid sha256:a7a86733318877c78173c9d837c842657661b19ed7a316dd42b9e5ef1839540b
+size 23195
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png
index 86f5985e6f..aa599d2d23 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fd3964a31217ccef0ba53ab1f5341311b52746b20660461220375dcad3befcec
-size 24194
+oid sha256:8056636bf9a039ae6b0d9dac5a40803de19ca31cb0c48863b894a12886a6ddae
+size 15405
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_6,NEXUS_5,1.0,en].png
index b99279289b..022efb22ca 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_2_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1745fbfae1996203b192b07bbbf424eb129944b37b83c0203fcd05c7f332096d
-size 25874
+oid sha256:d3791d6234a15c1deda513593b19149d97bc1062611eedb108839ba2bf1076eb
+size 18636
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_0,NEXUS_5,1.0,en].png
index 7e3f549840..d557a9dbb9 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:695a934529456a82e99371e9b53231ac02f6d235174927fbb346bf2a5375aceb
-size 33962
+oid sha256:8cea32585e950298058338f5a195ac4a4699f04aa60adb5d794da26dbe43a64f
+size 33848
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_1,NEXUS_5,1.0,en].png
index 83a9e17db0..eb40963185 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4e406c5e173add3b29c3ad7a10e968d3bffc4627c7895bd2080826b832652ce1
-size 37783
+oid sha256:96db0b988e47a0e3cbaae3982561053f4c7a68a3d0f475c2723619cfce8f891a
+size 37959
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_2,NEXUS_5,1.0,en].png
index 079518de5a..dc91bf4970 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:52fe76d305e114d5eeab85b608c5d86307ccd6fa0f283f4a806d64163d17c7b0
-size 33985
+oid sha256:c4b244008a0a0946605641e26ac1ee2b2ed0d689af8e45223458cd06d54970cf
+size 33905
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_3,NEXUS_5,1.0,en].png
index 2d44a91eb7..46cdd3df16 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6e91c8c83e1a793bb8e0d306b98a4737187a5a5df7a47c67ac6a716696cff3fc
-size 41208
+oid sha256:60dbf7ac48dc77b79f80c15ea811381ee5885e2c5f702e522e789f16672c694b
+size 41164
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_4,NEXUS_5,1.0,en].png
index 079518de5a..dc91bf4970 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:52fe76d305e114d5eeab85b608c5d86307ccd6fa0f283f4a806d64163d17c7b0
-size 33985
+oid sha256:c4b244008a0a0946605641e26ac1ee2b2ed0d689af8e45223458cd06d54970cf
+size 33905
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_5,NEXUS_5,1.0,en].png
index 2d44a91eb7..46cdd3df16 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6e91c8c83e1a793bb8e0d306b98a4737187a5a5df7a47c67ac6a716696cff3fc
-size 41208
+oid sha256:60dbf7ac48dc77b79f80c15ea811381ee5885e2c5f702e522e789f16672c694b
+size 41164
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_6,NEXUS_5,1.0,en].png
index 7e3f549840..d557a9dbb9 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Day-4_5_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:695a934529456a82e99371e9b53231ac02f6d235174927fbb346bf2a5375aceb
-size 33962
+oid sha256:8cea32585e950298058338f5a195ac4a4699f04aa60adb5d794da26dbe43a64f
+size 33848
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_0,NEXUS_5,1.0,en].png
index c38576522a..fda8ee34bd 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a128bd56c653706f65f20dac1e4110b21259e76821821057fe1e829044a1d36a
-size 31523
+oid sha256:ab4197e309e72fb1e486210ef92655b18814eb1f21e9cbcb1e6e60ec7fc8627a
+size 31463
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_1,NEXUS_5,1.0,en].png
index 3f54ca9fa5..876b8499fd 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:65bd05d8176f7d65f10ee3183f221afc73a759962a2e6b95067d3830a93d876c
-size 34766
+oid sha256:103e9412a0a04a5c98290ef51670fbcfaf64b6d76e62fcecf486cb777c4373fa
+size 35088
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_2,NEXUS_5,1.0,en].png
index 1b92bf8947..da1bde323f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f00202b17ca6c89dfdefc616888ceeb7272cfdb452745e8e034d556764983f7d
-size 30717
+oid sha256:41b7d2e99dc922af5990a87bea479cefa1da0a274e373d88b3505df0b13ff05a
+size 30622
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_3,NEXUS_5,1.0,en].png
index 52d4323c1b..2be1daa946 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fec18df4d767d669a62d9adcef3d919332fb3af72f5bf2df349acc3e964fa78e
-size 36634
+oid sha256:052a42e15095538d05231b73fd7f5259bd3782d1b280ef0726a0445a9dd719ad
+size 36640
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_4,NEXUS_5,1.0,en].png
index 1b92bf8947..da1bde323f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f00202b17ca6c89dfdefc616888ceeb7272cfdb452745e8e034d556764983f7d
-size 30717
+oid sha256:41b7d2e99dc922af5990a87bea479cefa1da0a274e373d88b3505df0b13ff05a
+size 30622
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_5,NEXUS_5,1.0,en].png
index 52d4323c1b..2be1daa946 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fec18df4d767d669a62d9adcef3d919332fb3af72f5bf2df349acc3e964fa78e
-size 36634
+oid sha256:052a42e15095538d05231b73fd7f5259bd3782d1b280ef0726a0445a9dd719ad
+size 36640
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_6,NEXUS_5,1.0,en].png
index c38576522a..fda8ee34bd 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomNotificationSettings_null_RoomNotificationSettings-Night-4_6_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a128bd56c653706f65f20dac1e4110b21259e76821821057fe1e829044a1d36a
-size 31523
+oid sha256:ab4197e309e72fb1e486210ef92655b18814eb1f21e9cbcb1e6e60ec7fc8627a
+size 31463
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Day-3_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Day-3_4_null,NEXUS_5,1.0,en].png
index a0944fea6b..eeef333d47 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Day-3_4_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Day-3_4_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:aa7de6ce541afcabc79fc1554809c217fd4f8bc83ed2c324342a9d28b9f34717
-size 31001
+oid sha256:1ed4d3d6c3148ea14e1680c9f7c3af86bc5158a3ffa8ce3f2b62283983bc91f4
+size 31166
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Night-3_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Night-3_5_null,NEXUS_5,1.0,en].png
index d9f5079c1a..02479078b6 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Night-3_5_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_RoomPrivacyOption_null_RoomPrivacyOption-Night-3_5_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9c7bdc732cc76ffcded6903e533ccb64d57d671f02a62093e63d0375fe0c680c
-size 29016
+oid sha256:1a5a64aab614f944cef77887b0c9e848e4273d8e2acf0561aafd28a064767937
+size 29319
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_6_null_0,NEXUS_5,1.0,en].png
index 4833897e83..05521df511 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_6_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_6_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:78cb940e6edfe0591071224fc7a8dcf32472633f56c38d55647dff40b042894b
-size 24303
+oid sha256:ca0abf8c339aeb7ccaf6e6dd4482288bae180319dbf9f5c25b5ec65011c6838b
+size 24211
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_7_null_0,NEXUS_5,1.0,en].png
index edb45cf46d..016645f55d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_7_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_7_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:52992127004c26595f2bfeb50c657a5628b18ffc10b8e6b260203504f44756fa
-size 22787
+oid sha256:aa8dfdcdacf72109ab7b0944e3af0f7cbdd6ae2ad42429128f0885483baed011
+size 22630
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png
index 7306748ca0..66bcf77813 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d21e45a40de75a8ffc30e574f3f9a1f308616c5b9bf25de36d5277db5911c373
-size 52396
+oid sha256:3f92b41af4156789be97ae4e6089e072e6fef5323b9900746ae196ab73d81c9d
+size 41991
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png
index e4e301ae45..e53b225fc9 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:55ffd8384b2b9bb21530512b0794afae3312a3344522973c200009f22b319a1b
-size 48550
+oid sha256:0cef80f10d1c5830c6e21da832016d94126add970ccf10adc6f64097184ac6a7
+size 30360
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png
index c0a735b901..305245d071 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:998426a2ef42b3fcba4d833a94192ff84cdfca078e4e4beae76054677a517a20
-size 32722
+oid sha256:83f7126092b50b8149936f68618bc49ac7af7cadf0ba1179ccf14b70ff93a6cd
+size 32625
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png
index 7306748ca0..c8bdac2550 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d21e45a40de75a8ffc30e574f3f9a1f308616c5b9bf25de36d5277db5911c373
-size 52396
+oid sha256:960d3a6a7a47920f9720fac04ee624a102206bdaa763531fd5f7d7aa59914a54
+size 31932
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png
index 429e87b82f..3f6a1bc28e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4bf9b92e7f604738b91e09975c5ebfaee171a1c5aee7dce9f1b0a21ddac5cbc4
-size 50201
+oid sha256:debd7796dd77a861df31f8420fc6f7857da7750f8b301fd1e331c4e6c2250461
+size 39568
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png
index 7d584650e5..08df3036e3 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fda8d3e0eb21cb9754f93699569acbfc79de6c5ba187528601342a0311f20aa1
-size 47079
+oid sha256:3931be400c2610a408686d115cbeac328aa1ac33fe868176e66de460b4182b31
+size 39791
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png
index 7d584650e5..08df3036e3 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fda8d3e0eb21cb9754f93699569acbfc79de6c5ba187528601342a0311f20aa1
-size 47079
+oid sha256:3931be400c2610a408686d115cbeac328aa1ac33fe868176e66de460b4182b31
+size 39791
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png
index 7306748ca0..b4e29a9dfd 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d21e45a40de75a8ffc30e574f3f9a1f308616c5b9bf25de36d5277db5911c373
-size 52396
+oid sha256:467e88bdbaf0df59ab010487f11eb211f7a61a324344cd310b0f44b3ce57e09b
+size 43412
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png
index 534f9f2a3a..c476c33d7e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9992ccb1e020cb09e29c36285fc6acde477ccb2c53b5ab36d8fd6c19fbaf392c
-size 52236
+oid sha256:fb982a239d8d3e5357c1c9e5247cd3a0ddf510f8c2741d9cd02571c53d60bc45
+size 41833
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png
index 6bb74c767d..35dbfeeebe 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8a9cbc71ab3bc94c2bf38d89f453f9c14ed94fba96ab2c1cba5abe8169e4157c
-size 53609
+oid sha256:f3397f850c19ffdb387aaec22fe694077b98e3837571adb03d04dbed1bbab81d
+size 43153
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png
index 88c9263e98..8c613c3cc4 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4f32621601d9acf2db4072cc990e655b2c6733a0429107e0c3525ceaa5ef18a4
-size 51222
+oid sha256:7c21afc66bc5536988199efa7f75943f2cd4744548a74b23819a2c3e756fbec9
+size 31453
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png
index b1894d9d16..e9ccf0d5fa 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:47df27bc44852fbf357b45b995365edc29d3d8ecebf1d5c93149416835b71b9a
-size 34381
+oid sha256:d68ef34aa4eadd9219441b8fa762938091861e5a6b54d3d6e0ea0fce0f8cdd03
+size 34007
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png
index 6bb74c767d..a2d021919d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8a9cbc71ab3bc94c2bf38d89f453f9c14ed94fba96ab2c1cba5abe8169e4157c
-size 53609
+oid sha256:82a4be110528c6a3f65b701dd8da370454709c8cdbdb482a5d918c88565e5cfe
+size 32627
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png
index 9832639e4c..ffb21d62c8 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2b59de4767530724d5d4bf6e38f6b198db7fe1c88863efaae28933536de6e13c
-size 51552
+oid sha256:fa05421ae0ac8e5ebd9dbeb3839ad0fe042295e2dc8c28b807b7060089d87680
+size 40740
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png
index 37d9e41a98..212ec2bf2f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:29e8f90bfa5518b8ee35f88a9c40e3cfbd58b3b15e7cf4d94515a50a41ca98fe
-size 48050
+oid sha256:aacd3606f9cc6416038cdf909b4148a2be59e9669230510c18fa7299145799dd
+size 40949
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png
index 37d9e41a98..212ec2bf2f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:29e8f90bfa5518b8ee35f88a9c40e3cfbd58b3b15e7cf4d94515a50a41ca98fe
-size 48050
+oid sha256:aacd3606f9cc6416038cdf909b4148a2be59e9669230510c18fa7299145799dd
+size 40949
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png
index 6bb74c767d..c853e836b0 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8a9cbc71ab3bc94c2bf38d89f453f9c14ed94fba96ab2c1cba5abe8169e4157c
-size 53609
+oid sha256:3ce3125399cdbd8865842478b66586dd4641bbfb40b14af20702d23ea73d13a4
+size 44521
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png
index 7bc1e268c6..8296362379 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:920cbdf29ae6d68182bf0e819878f894d6878f9b347bf6a3945c601ca696f0f8
-size 53391
+oid sha256:1d2acf4cdf6b23ac6837e48fa3f0175b3e7b20b74ac6e8da3271ef2e80d28749
+size 43007
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Day-3_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Day-4_5_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Day-3_4_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Day-4_5_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Night-3_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Night-4_6_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Night-3_5_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_ConfirmRecoveryKeyBanner_null_ConfirmRecoveryKeyBanner-Night-4_6_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-6_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-6_7_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-6_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-7_9_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-6_8_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-7_9_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-5_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-5_6_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-5_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-6_8_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-5_7_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-6_8_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-4_5_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-4_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-4_6_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-8_9_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-7_8_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-8_9_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-8_10_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-7_9_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-8_10_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_0,NEXUS_5,1.0,en].png
deleted file mode 100644
index d80e1cc282..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_0,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fde0cc6b7268856b611db66b66b22c51df155f605988fc54f5c7ba18e5f1f713
-size 11456
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_1,NEXUS_5,1.0,en].png
deleted file mode 100644
index 73ca4a2eb7..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_1,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ca1cb753204502aa929f52e8b222b86bd845086f841fb9ff2936886e1dcea526
-size 8778
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_2,NEXUS_5,1.0,en].png
deleted file mode 100644
index 35364ebadc..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_2,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5315627c3cae4b07d3df50054d9a689148517b8fd0a0e88b160ff3f447513999
-size 12653
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_3,NEXUS_5,1.0,en].png
deleted file mode 100644
index ac31b4c341..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_3,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:28b9ccdc834d9643694f72ce1c01fb988685a7b0300912bada032be1e426b467
-size 13539
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_4,NEXUS_5,1.0,en].png
deleted file mode 100644
index 961345501b..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_4,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:3ada109a4f7b3b3bfae402eaddcdc240fd5f08aad41e7f1e70878baa8e257f2b
-size 13288
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_5,NEXUS_5,1.0,en].png
deleted file mode 100644
index 15fba97e39..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_5,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:0b18e5991be70ab16f983b929d08e566c85483e6d79f4de7a4191a800f4cae35
-size 13305
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_7,NEXUS_5,1.0,en].png
deleted file mode 100644
index 547657fe23..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_7,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:950e80e7e4be904967d35aaffe196548fa4f5ae9afdf2b86e65611ccdb14bf80
-size 22203
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_8,NEXUS_5,1.0,en].png
deleted file mode 100644
index b1778019d8..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_8,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:004de09febcc9962bdc710a258f82083ba4ebca1c2831da48db7a1c9038d2caf
-size 12154
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_0,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_9_null_6,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_0,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ded89c6b2a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:57e0c90fa627974aba0d62363906bcc7911a6ada7d9d7db00db0df1d51fed54c
+size 12911
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..f0c758b53a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a3596d2a24536c5b0614e80f2f69024edacff71c654e50b59b68948d4413a17d
+size 19155
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..bc3ac24996
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce723333b1cac9ea41ef93b92a6bcc0d2fa5eec1344308da18f5103b00216c1b
+size 17412
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..7b2586a782
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5abb560a12f1641ae93423fe88c9b1c9f68414c67b529c72d44f260817f00eb2
+size 11888
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d616106e20
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:12456705abcc0f7efb351a97880d9b0f1cbba9723b74723ff264925004621efd
+size 12986
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..2bcb59f874
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d1cdaa0f4b68c50902eb631789aec93d7f0a5c2570a2230827a55a03f1eb52f3
+size 14345
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..8402f2f222
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f58bf26b7f82401f557b4948abda9202be478058280d7af8fdf0cc917dbe123f
+size 12585
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..dd462b020f
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a02ead0e9c689ac14337dbfb1d238a4082321aa5e9ce5447135df71358dd36b4
+size 14338
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..375e03b6c0
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a4b88a625372bd794e8f18fb572cfc642d5738e36916e5639a8aac3fdddf70f8
+size 15474
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..7ffb8bec52
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ae38bd0b9effd780670034bd0f0d18d686b566be684fbe1a19d5641d9ecbc5e4
+size 17931
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..87025e3b6e
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:91a5a604bf078e6dc1a4161ed00c779ecec5cd0ac01283d38ea201a9bce9207b
+size 15999
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..94306b9d92
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fcf2f25bbcb2f43d70a8a3228ba97426dbb98b533b74dd13ef6459f96a711db5
+size 8877
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..7345bee430
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:215750e3ed728fe17ac8b2a12e3867ef3dbaa1b486276fc73b7a1fd334ee831c
+size 16573
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..f8cb49e06c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b8140db57575dc4f447b12bb1369ad0f1cff549679b707dd3e82e1224470a1d4
+size 17707
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ada41882c5
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9541c448d629db2715864652c16f6de0dd8537ab5be95df3410f139615156efe
+size 20117
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..43b49ae924
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df7e4e982ca275e9318673bb05d5eff0f0c3f4c696cb951faa78ef68fabf0f6e
+size 18233
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..fe69a074ac
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:44087f904de812d58363dd64ed95d3007c13d8c97c93b05afd53da7fb4a6b3a7
+size 12703
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..9c3662f93a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f2f6c42ffcef69b58758a29b037b06a4844e047e06c2bdd7187fa64c6208ad41
+size 13781
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..5844e343c5
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e3d3386f399049e3903413d93b711f430285f19f1a845a5791b4c1fdfd8566ff
+size 15296
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..407c728a58
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8b911a96afcfef14ccfcee87bae860105e4d34c6b06bb5db74334a1494ba0f05
+size 13367
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..feeeb4879c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eeddf38808c98548f550956dd9338a897f51913944bd1e9adac06117bbd002d9
+size 22262
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1dd2438d7f
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d5e32fab6c9f6500bf523a18d10464a7b703649c80e6f1c56bc124cadbd2d248
+size 13512
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..e630578fca
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f6bbad58b0e446703ab76117af2916fbee40196faf8424e36eceac10f625d50c
+size 14642
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..2cf2e92f08
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:275649c8aceae86b0ab6ef633dc3bb9533d443a1ab3125fc16d8a288cdd8e8dc
+size 16937
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..14f9e6a3dd
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:72aa257540b31b448e5eb57610c0d62422bc1d40c2abbdb41da99d6244a316c3
+size 15172
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..c680a1430a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eaa409bac7f41b101207542e7be42b3caacd3039f6918dd1bb1df3105dffa1e0
+size 15747
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ce0baeb017
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:efa3edb3d8f7f071e4b622c4d5dda9ec86741b468c734caffd1b6463294038bb
+size 16882
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_0,NEXUS_5,1.0,en].png
deleted file mode 100644
index 774d5ad126..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_0,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f7d6df9f2573241ad0096a1f93fb682713b8d50ffa4808419e246997027c5d43
-size 11392
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_1,NEXUS_5,1.0,en].png
deleted file mode 100644
index 2920c6453f..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_1,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5d321e5157196e32961e4915466656145299ea4768e5478151b649740c2f1594
-size 8750
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_2,NEXUS_5,1.0,en].png
deleted file mode 100644
index d5bf02373d..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_2,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e86ea02d990c5db670a6aa3d9c17be62dad1192d687c1dc27177758b55cece3c
-size 12503
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_3,NEXUS_5,1.0,en].png
deleted file mode 100644
index f8e1416ce2..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_3,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:584487de266a9b3e9cdefee0378023a946b309e19b09acffc5bf1a08dfc0d390
-size 13335
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_4,NEXUS_5,1.0,en].png
deleted file mode 100644
index 11cdbfc582..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_4,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:8bda8b4f6a84b37548e3926f180ddb282d220b8ff581a60e69e0e309ad3ae06a
-size 13119
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_5,NEXUS_5,1.0,en].png
deleted file mode 100644
index 057f4f914c..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_5,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2c17e87f046bbdd7a0300262737508cd1ffab4b54da9df7e7a7803237e070243
-size 13032
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_7,NEXUS_5,1.0,en].png
deleted file mode 100644
index 58ef0ec934..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_7,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:06df893c35c81609b46317f4de733db0bed4f5888eac189d09078ad7bd411cf0
-size 21340
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_8,NEXUS_5,1.0,en].png
deleted file mode 100644
index f2441b5999..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_8,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:53248c6c1d0a05b8fd08bcd35a43a6463b03b9ad808b24f59d1fa7fe5d444d4f
-size 12051
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_0,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_10_null_6,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_0,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d83d063b86
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1d914ad609cda9e94ba793027c17f3b7289fec71e65e1880b837615203f7718e
+size 12907
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..cea81a1d64
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:33eb8815da0c19c454c8af842ff28cf244d2d09bf32af3789f0e9b52a01cc826
+size 18407
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..be0b0c5319
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:313d37ec6776cbde514c152a3192e001a72f0174eddd728fef5e24b26c0a040e
+size 16713
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..fae7083462
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab320b11e312301da35a906ff9e8591adbd2dde19364fbecc6b16200e43a5a58
+size 11891
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..39e135705f
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4c169aba4adcfd8a6e4680f9ccfc463ce80f6f3e6380e09a66eb0effb5a93a8d
+size 12982
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ae6ef1502a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c11af6c23854ce64d42a922979ea3d19edcd6051601a130aa2d7924dbe45506f
+size 14280
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..5c79bad901
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4332d3590b7961cb28a7e6b586aed182babf31f585e129f0b7a7c901bf321a61
+size 12557
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..0b4bff7f98
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a47196a390720a7321121c934e44fb524f37e4e5f45feaec6a334d0d8faf893a
+size 14151
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..03d159b408
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:619163e820ad38561e31c2c231e8df63ef43b6bb39f324eab9cda1d5d7d0212c
+size 15242
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..3c38a5e87a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:40157d0455ad2e5124bb074106dcaacaa374a4275dd905d6a4c3def3c296a83f
+size 17364
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..de444fa94a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3be9552a7539343fdefc0e5d090cbd1e6438b4821ca0949d542b94a7e2ab7165
+size 15509
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d93b2c752f
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e05f294579b4420e6671f268d10fae8fb49e93e5ae5b0f0d4548f7cdfeb82742
+size 8928
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1e25af7752
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f53362e755ef01ec7eb6d5b5b70d0c55e661d3d0f425f58e396fee9c8df10023
+size 16152
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..c00f359add
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bc020ed69f860405af38a99d9710495fe84bad6e11f89ca174ee352b62615deb
+size 17225
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1ccdd46786
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c44cdfed89219f67b44b33466c06299c1974c3fce567d9e0ae74cfdb0e4fbeb
+size 19365
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ae73549b4a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5e8d9ad1a2a4c9f6ce4a9c57e9628e2759e00cf7ae51a8266e769254a5bdb1b5
+size 17511
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..4e4e9b55b3
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5c7efc5dda976509dea1e8259dbe417f655a036cfdbffeaaba5405bc94d52e1
+size 12702
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..5cd847939c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:54536f0eaa19b20629a0ebebeeed9ff892a54a8009ac6d06d63a742f4e2791e0
+size 13772
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d89d4d6537
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:91437deeb71a9a1e7b8d4f0e343275712d320d3c2f44a3f5cda6efa28a49bdff
+size 15197
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..85105ff9f3
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dbc09a6ae1619e8a870371bf110654ebd288b686001d892116f6c27cdb3c1919
+size 13339
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..af81e76f00
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d40489903e1fa61d91ae44bda36fa2f59f81357edb441a198b27f6f928f6c70
+size 21654
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1288669d86
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca84726aa42db2d961b328d3ba34523f783f215fed36d049da1cf8a503d0dca7
+size 13304
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..4332acdbc2
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75db8e2faba1070c628a79e193679b67438a8ae0d484f72e52e32d8411605baa
+size 14412
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ec77c36679
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5b24a09682a6e23f9fb5ae95bf61a76b2ac711dcb6e61485a3a0de0766062f9
+size 16423
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d134f4b746
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a839e11d5f481cdc3973ee1a863badf44df39cd3c20571e9e475800bff726913
+size 14714
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a25e42f8de
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0ff91c8812358efc55693fb855aa1c7bb5f617ebbf6fad7f57ab5cbe3b8a6123
+size 15323
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..84e9bf7098
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6e7dd7bdb6bf394ae39f75cafc9d9424becb483d61140bbc11eb374eb0173bd5
+size 16402
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-10_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-10_11_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..03e79996e4
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-10_11_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ac45ae9f8aefc681d6f73d46ecd7683ea9f84c646e4b92f9282924aec0d73bcb
+size 29980
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_10_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index 95f61ecfa8..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_10_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:95659c6503ff1def5f3ec59bf96e9b47ea8f539ba90016c06aee3c1ead4d4387
-size 30047
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-10_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-10_12_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a9b938365e
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-10_12_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3010426862d9e56245b96d9a0ad9d4468996bfd1a7436254e984b6cad46ccac4
+size 29874
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_11_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index 32e13103bc..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_11_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:56c5abd33920c390237f2889fceb20f18283c10fd075612ff7abc85bf6e5da52
-size 29887
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d5a64e473f
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1bd5f590fd30facfb007050c26aa567d568ab0d797a27986b6f88a23af76f9dc
+size 14167
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1d1319d036
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e609d908d159cffdc2a17ac8944413dbb52e5cd1ebdd2bd30057dc9865749bd6
+size 13369
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_0,NEXUS_5,1.0,en].png
deleted file mode 100644
index 3c9834d32d..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_0,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f6415c4b16fca9f9c33fa26c320eba7db63414f9e7f80e4be8c06aadf0d761ef
-size 64895
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_1,NEXUS_5,1.0,en].png
deleted file mode 100644
index f0cdb4dc98..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_1,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:eb5cd4ad10a617fdc031c417402c20b8c83c9224ee4e4c40a0803d7399d1d338
-size 86439
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_2,NEXUS_5,1.0,en].png
deleted file mode 100644
index 3c9834d32d..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_2,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f6415c4b16fca9f9c33fa26c320eba7db63414f9e7f80e4be8c06aadf0d761ef
-size 64895
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_3,NEXUS_5,1.0,en].png
deleted file mode 100644
index b9944b4d34..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_3,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:9482d5bdce23a63c4b682fc7ef6c48d3b29c42ae88eb22c0c79876591816ca1c
-size 64880
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_4,NEXUS_5,1.0,en].png
deleted file mode 100644
index 9b6f977aa0..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_4,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:7164dd963d88f12cd5b784d2184e9832b21f4465e42b1b8e687cdfde87e0c853
-size 65990
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_5,NEXUS_5,1.0,en].png
deleted file mode 100644
index 97a6d22a8d..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_5,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:01a3240614334fcacc98cdfe4fcba8d91629f000f6224cda2d2218c8d9fdb7aa
-size 66366
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_7,NEXUS_5,1.0,en].png
deleted file mode 100644
index 95f61ecfa8..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_7,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:95659c6503ff1def5f3ec59bf96e9b47ea8f539ba90016c06aee3c1ead4d4387
-size 30047
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_9,NEXUS_5,1.0,en].png
deleted file mode 100644
index 25388c3a96..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_9,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ff9327bbe5d92c91bfbe23811fa4776b4fc3c73ab26563a303e27cba4056e5e1
-size 89621
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..558a7f6e18
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:21835cc7375230c585465e400338a92230ac164ba5fbb3c08a81935295798739
+size 64818
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..4893ac0e06
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a477aef8b2a641362996cf27c5630ebb0da670c0fc180e2b2b45eb9b0afab3d4
+size 86360
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..558a7f6e18
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:21835cc7375230c585465e400338a92230ac164ba5fbb3c08a81935295798739
+size 64818
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..0df8527c75
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:880db00d69af2c419ea11ec199196b2bd4811c7829ced249a899948e6825b2a2
+size 64785
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..22ff27b03e
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b49b631bb839eb12df9beca2dd48554f128da5decbb1381c9da384020b243f94
+size 65875
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..3feda9a846
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:33b483b3ef695f55c8a153d8e50b81e244f5125235cc3db559efa861756e6b68
+size 66253
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_6,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_6,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_6,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..03e79996e4
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ac45ae9f8aefc681d6f73d46ecd7683ea9f84c646e4b92f9282924aec0d73bcb
+size 29980
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_3_null_8,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..25e01b3339
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cc15def26261d505147ee309d90666a7f9325128332fe6b3d246978044a99f78
+size 89554
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_0,NEXUS_5,1.0,en].png
deleted file mode 100644
index 95e4c489a9..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_0,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:47fce43a17dc5db55f901d43fe2ce6436ad18373129f0ca82ea3aecc07707442
-size 67127
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_1,NEXUS_5,1.0,en].png
deleted file mode 100644
index 194ad7e5cf..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_1,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2d3228cbd10bee0e42b4d6ac09261b6b3d9eb6822f4d6fb8097fbe7ba06b2d8a
-size 88321
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_2,NEXUS_5,1.0,en].png
deleted file mode 100644
index 95e4c489a9..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_2,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:47fce43a17dc5db55f901d43fe2ce6436ad18373129f0ca82ea3aecc07707442
-size 67127
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_3,NEXUS_5,1.0,en].png
deleted file mode 100644
index d4a3208718..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_3,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5871901c9988ce8954758e4b6487f8448b11e9f24984bae8bc4a05336fce99e6
-size 66896
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_4,NEXUS_5,1.0,en].png
deleted file mode 100644
index b7d2192eb8..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_4,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:1bea5791ddb9180750462f17e65733a91bb246c6095f3b87477d3f0406d5a800
-size 68716
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_5,NEXUS_5,1.0,en].png
deleted file mode 100644
index a44c83e6b1..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_5,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:45fdc87068b3eec07720f4d81ed31c7914e3b6ba781dd5bf699a889a859218b1
-size 69069
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_7,NEXUS_5,1.0,en].png
deleted file mode 100644
index 32e13103bc..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_7,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:56c5abd33920c390237f2889fceb20f18283c10fd075612ff7abc85bf6e5da52
-size 29887
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_9,NEXUS_5,1.0,en].png
deleted file mode 100644
index bc6be07737..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_9,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c59880d152c30171ef28b06035067bb8209028eaf66ff77dc0492112a00979e0
-size 91200
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..361db77c69
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:71b8af217423a992e17414a084d4b336cfa6d0701a0029ed93bb227596024e85
+size 67112
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d2705148e2
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d9293afdc38af47fe988d78df044178f845e1e9b5dbf78bfd840711d73bb48b0
+size 88305
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..361db77c69
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:71b8af217423a992e17414a084d4b336cfa6d0701a0029ed93bb227596024e85
+size 67112
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..01a9907758
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:829115185aa5d0c3c2b91107dfad58dff09357223e5f1df82223fb044a19ec5c
+size 66864
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..8fc682c6a2
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bc15f30e62f68dd8ea3d1a17073328eba61e97e24cd63e5ae9a997287b24145c
+size 68682
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..f9dc69fa6c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b58177f669fb5aeb03c7b72697feda03492a3df5ec37ebb3a7588fef7e8130f2
+size 69041
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_6,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_6,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_6,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a9b938365e
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3010426862d9e56245b96d9a0ad9d4468996bfd1a7436254e984b6cad46ccac4
+size 29874
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_4_null_8,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..39d32fc508
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4908718c873903f2566fd953bd947ad012c822ba3289a8dec96793d3ef879e2b
+size 91185
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png
index 35f3978407..8ebe25663e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:157d7e71b29ed9fcf5b9d7169820183f947524ee109fd9c7de73d356405cb1a5
-size 41472
+oid sha256:3a7f593e3909ea3e881cef0ea7a6e25a9a4be3479d0409be55194547161372bf
+size 41391
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png
index 4df26e43d7..df3ddb0ce2 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e6dd323965929e2da99f8f0c2a33db09ecfd89804fc0f09a9852ee70ae5047cb
-size 47989
+oid sha256:a6baa85d5b8b544b53ea864739cc77f3bb96d26bbdb6956b74dda70255888086
+size 47999
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png
index 35f3978407..8ebe25663e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:157d7e71b29ed9fcf5b9d7169820183f947524ee109fd9c7de73d356405cb1a5
-size 41472
+oid sha256:3a7f593e3909ea3e881cef0ea7a6e25a9a4be3479d0409be55194547161372bf
+size 41391
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png
index b3ff9c85af..d9cfec35ae 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8c06dfd2c060f6e94dde6b5eabe3f3e64fd36246c5c8b4f2c4472b4e27084bd0
-size 26338
+oid sha256:3bde78253bb21d2d5edc97f80ca6f7ae8cd0a710b5544803c998d3cec5b4fef2
+size 26273
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png
index 35f3978407..8ebe25663e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:157d7e71b29ed9fcf5b9d7169820183f947524ee109fd9c7de73d356405cb1a5
-size 41472
+oid sha256:3a7f593e3909ea3e881cef0ea7a6e25a9a4be3479d0409be55194547161372bf
+size 41391
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png
index 14f108d258..7e29d8a767 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1ed7f0090053318b0051e3cbe8a42a356a6019659460e0ddbd9dbcd7d3b7f397
-size 31937
+oid sha256:32309b5e76458bba374dfaff461f927eb752b316ae88ed8480393d951cc9f08f
+size 31847
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png
index b56733b351..e00972462d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:75372f01fcd2cbad5802a505c1150ec6e14484cf89ab700e6ec2e5c5f9e16a8a
-size 39453
+oid sha256:c9120fdfd75820c68d972ee3f6a01486b844fbfa6b200ee070ac109998fe76a6
+size 39339
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png
index 7eb2c617d0..54686aed66 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8dc4996be5af5a42d91e9f0fb371c821247264d980bde56e870441750fb439e0
-size 45328
+oid sha256:db5e8d9d9b78fb46ca40261fd5abce652b3dd29623205ef3935ced21278812f1
+size 45352
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png
index b56733b351..e00972462d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:75372f01fcd2cbad5802a505c1150ec6e14484cf89ab700e6ec2e5c5f9e16a8a
-size 39453
+oid sha256:c9120fdfd75820c68d972ee3f6a01486b844fbfa6b200ee070ac109998fe76a6
+size 39339
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png
index ad47b678e1..0983cab044 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bb6d0f7866767b529ed58e642d45cde89da0535b2d024921dde19d47808206a2
-size 25094
+oid sha256:ee7588bd823e9d59ee5797d2ce41b64d933ead7fd3ca9c09b99b40ce007f9e7e
+size 25006
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png
index b56733b351..e00972462d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:75372f01fcd2cbad5802a505c1150ec6e14484cf89ab700e6ec2e5c5f9e16a8a
-size 39453
+oid sha256:c9120fdfd75820c68d972ee3f6a01486b844fbfa6b200ee070ac109998fe76a6
+size 39339
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png
index 88c330d340..6c3b95dd8f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4fd29e5953cd9782b7748beae7078011147129cecfcadadd7fc04533f7fdd3f5
-size 30053
+oid sha256:4c70eb14daac4d7d3eceb3c60ad2547ead74a3e5b282f324e9d2e3d503c01d3d
+size 29926
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..18e8edb58d
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2e396772e4b3effda7d9238de4f02dbce8c705be334239e4607dd80043764bce
+size 10087
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..18e8edb58d
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2e396772e4b3effda7d9238de4f02dbce8c705be334239e4607dd80043764bce
+size 10087
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..5ccd73a50a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3fdcaa9120195f28f41284d2a124df6739f4fb0967df6df24a38eb6cf43b6632
+size 10144
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..36edcb8b83
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b8678bfa52be0664119038250fa9b009f5bca7694c13bca346a87f78e1dd00a2
+size 10295
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..862b48dfc9
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:726f55e0bd89f429e351b070bda57be081c01a42240ba53e8d87bdfdcf84a1c6
+size 71385
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1a3a882cc9
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Day-0_1_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:839bdc36f80481351c63b66e358a0f68537a8cc1cdf75ee9dcaed35338573e92
+size 66140
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1ded95367d
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3305677d678c28717a83e745b6c1bc71a2636ad65e09c5efc435c09cc1465dfc
+size 9577
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1ded95367d
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3305677d678c28717a83e745b6c1bc71a2636ad65e09c5efc435c09cc1465dfc
+size 9577
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..cfb85f1f94
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:81e02f3d58a5cea85e9f7f63cab787f3d340fc4ff36ecf4ac5131d4d6d7e9f04
+size 9604
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..f632e7c8c6
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c9b2398f048d9f544a717a7a5f7e6c16f0584403748e76914f4aa2ab69baa13b
+size 9799
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..8bd6eb3b3b
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:684fec6dc2c15767195b79d6c4c5996ab9cdb03386b5fcfc3400622ad4e03008
+size 68115
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..c6eca13a72
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.file_ViewFileView_null_ViewFileView-Night-0_2_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1813c8906a75315d691faf5fe7ede66ab7e56e52e779408a2a4fcd6829c113b1
+size 61642
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Day-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Day-1_2_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..4e8dd75a9c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Day-1_2_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ea29d83cb5184a5490de6b3d4a15a41db7c7cb8431b6dfd0eff873b991a3a9c2
+size 9245
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Day-1_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Day-1_2_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a01c1f03af
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Day-1_2_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:843c33c966df9a1178fea5f319c3d5c313ab4d8f45166fda644650f9aa3c8550
+size 10949
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Night-1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Night-1_3_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..7abe67d050
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Night-1_3_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:27f7fbaba83b994a0baf3b002c95bc27aa6d6097b0af1cb566ccece35cc5f4c5
+size 9010
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Night-1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Night-1_3_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ec410fbb75
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.viewfolder.impl.folder_ViewFolderView_null_ViewFolderView-Night-1_3_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa145b94e5cf20f651d965cfa932163759e3c0dd7f3b8589e53d43b25ff94102
+size 10566
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png
index ec20ff6d63..02d6abe00e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3a0eb132bfaf1c90c6507c12b25150fb29e260ae3bd0a5760f5fb2e9483fda26
-size 14814
+oid sha256:1e933541422476dbb3005b41f15bed9c496994038ca1e184a3ab7e5bd10a1b1a
+size 14840
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemCustomFormattert_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemCustomFormattert_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png
index 3d0c5e4a60..75c35c0aef 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemCustomFormattert_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemCustomFormattert_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:624a00c3098b821482dcdb9529db12cfcf46dc82e136d8b255561125e932862b
-size 19467
+oid sha256:b190eb5cc3b94192df7875a67808923909556b010aca26942e06668cd5da0926
+size 19469
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemSelectedInTrailingContent_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemSelectedInTrailingContent_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png
index 41bc1be58d..e76ecdc0ca 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemSelectedInTrailingContent_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_SingleSelectionListItemSelectedInTrailingContent_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a2ffe9b836ee6dc753f7e6faf10814d7a52611cd1ff9d11a539cc045fdafc1db
-size 17227
+oid sha256:508b80ed156ebc2ccf195470716ef635f2ac539d0d9ea08ea2c7263d51359b2a
+size 17196
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineBothIcons_null_Listitems_Listitem(1line)-BothIcons_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineBothIcons_null_Listitems_Listitem(1line)-BothIcons_0_null,NEXUS_5,1.0,en].png
index 93f7aafb6a..6b25b95fc2 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineBothIcons_null_Listitems_Listitem(1line)-BothIcons_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineBothIcons_null_Listitems_Listitem(1line)-BothIcons_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:72de6bcb547e59838b8ff5b5a990a421303ed3c9d42e792e76ef74e2abfb4f7f
-size 11796
+oid sha256:40dc538cc402212b5773c911b7a745dda517daab6857d40443111b479c4c0e0a
+size 11799
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingCheckBox_null_Listitems_Listitem(1line)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingCheckBox_null_Listitems_Listitem(1line)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
index 901ada774e..4702ca344b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingCheckBox_null_Listitems_Listitem(1line)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingCheckBox_null_Listitems_Listitem(1line)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8c8574785958bacf0eb45b3d135d3caae7c6fe045df773b5f7b6f03dce942130
-size 8584
+oid sha256:591a0a1615045c701ae46a227dc5b4f6ce6de24b02cb8207d6a22f795e481067
+size 8551
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingIcon_null_Listitems_Listitem(1line)-TrailingIcon_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingIcon_null_Listitems_Listitem(1line)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
index 0ae8d95be4..ea2e4b8b8f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingIcon_null_Listitems_Listitem(1line)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingIcon_null_Listitems_Listitem(1line)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:354685b1a042ebcee89e5afbf9c45954d6615242d55fb07ad7a0b6638bee1570
-size 10034
+oid sha256:0dba13e8f7515785485731fa0e6ffe8dcd950a038030970b9d4bf68031e8d781
+size 10035
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingRadioButton_null_Listitems_Listitem(1line)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingRadioButton_null_Listitems_Listitem(1line)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
index 39306b89a3..fc5b11f399 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingRadioButton_null_Listitems_Listitem(1line)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingRadioButton_null_Listitems_Listitem(1line)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8d5d62ab71228dd0042560487fc754066a71309d28884184ee5f0e4383885f07
-size 9990
+oid sha256:270538751aa40a82dc396ea812e39220635e77a9bc98079ff379d979840225e6
+size 9976
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingSwitch_null_Listitems_Listitem(1line)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingSwitch_null_Listitems_Listitem(1line)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
index 946961f399..c1725b1e91 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingSwitch_null_Listitems_Listitem(1line)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemSingleLineTrailingSwitch_null_Listitems_Listitem(1line)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3b956cd168b4698d187236b0a9684f090c1ea4ab02371ac821d167c8b94a7ad2
-size 12351
+oid sha256:927992271bc2538ba03225268eb8173a49faab1b965f8afdcdb69b040e1c855b
+size 12412
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesBothIcons_null_Listitems_Listitem(3lines)-BothIcons_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesBothIcons_null_Listitems_Listitem(3lines)-BothIcons_0_null,NEXUS_5,1.0,en].png
index cc80e94ce4..cdf2625dea 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesBothIcons_null_Listitems_Listitem(3lines)-BothIcons_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesBothIcons_null_Listitems_Listitem(3lines)-BothIcons_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1d291b3f0d64c7d6ca9cf39daf65959aafd6438e3a16392459ea2d65e7e9ac4d
-size 28279
+oid sha256:4e8cd9b62dbf66835ca31488b23ddc0d7eddc24aff989f74f9d08f5a570c51da
+size 28264
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesLeadingSwitch_null_Listitems_Listitem(3lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesLeadingSwitch_null_Listitems_Listitem(3lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png
index c34ecc4fa6..d950af3bbe 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesLeadingSwitch_null_Listitems_Listitem(3lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesLeadingSwitch_null_Listitems_Listitem(3lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3f523d5c94ef05f62337494dfec70cd671bbc30359c4ef403796324c3821e835
-size 28706
+oid sha256:089ddf983b2c0df22caa26ecb486810b90d7c59682b3594fe254fa39fc00f5a4
+size 28643
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingCheckBox_null_Listitems_Listitem(3lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingCheckBox_null_Listitems_Listitem(3lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
index 21b2c1f1ad..ff4fd8adbf 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingCheckBox_null_Listitems_Listitem(3lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingCheckBox_null_Listitems_Listitem(3lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:46f8f65468d81744980f72e9e472c8aae86cfa5ba1ef8b02260aa2c925c0ef31
-size 25848
+oid sha256:d0b99df7aad067aa874e1d15503cf8396f30d07e2eb36975cd82d9207139101b
+size 25812
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingIcon_null_Listitems_Listitem(3lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingIcon_null_Listitems_Listitem(3lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
index d205083740..fff1be641b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingIcon_null_Listitems_Listitem(3lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingIcon_null_Listitems_Listitem(3lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5b2280812773385a3aeee9d090b2ce6a5bb1e25a426d88ed7c9e33be300262be
-size 26942
+oid sha256:83c59bc2e1fd4f37eb4449c4bd5b44894fd8b1b02ea6f8badf907c219d500faa
+size 26927
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingRadioButton_null_Listitems_Listitem(3lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingRadioButton_null_Listitems_Listitem(3lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
index 2831d074c7..4943bb3517 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingRadioButton_null_Listitems_Listitem(3lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingRadioButton_null_Listitems_Listitem(3lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b5b7661174bf47510f3a639030aa85187d7b3c41facadc94cf02e15e976f5923
-size 26836
+oid sha256:3cf0730bf5fb2b58b699f9527663cdb3e772b1fcf26b3410c288d31ec7bf8d59
+size 26777
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingSwitch_null_Listitems_Listitem(3lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingSwitch_null_Listitems_Listitem(3lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
index ff8ce22514..d14ad2d497 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingSwitch_null_Listitems_Listitem(3lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemThreeLinesTrailingSwitch_null_Listitems_Listitem(3lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3dbba64c730837cc376dca1b37a65ae1335199c3209ad1ae7c28aa55377a778e
-size 28588
+oid sha256:684a875d6bb070f49128799f670d2a2fb2852880048c9413ad9a5f0d563c8cf7
+size 28518
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesBothIcons_null_Listitems_Listitem(2lines)-BothIcons_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesBothIcons_null_Listitems_Listitem(2lines)-BothIcons_0_null,NEXUS_5,1.0,en].png
index 821fe8cac7..858cf9a710 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesBothIcons_null_Listitems_Listitem(2lines)-BothIcons_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesBothIcons_null_Listitems_Listitem(2lines)-BothIcons_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d41f40195166fd9fe6d4b20c967d263d8ee3805931d2ec74c6671dd8d02af7b6
-size 21619
+oid sha256:7f34b5d4a735aa4e7480a9d5e5d6fb7fae2df7e189955c76f73e5a9e3b572c34
+size 22119
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingCheckbox_null_Listitems_Listitem(2lines)-LeadingCheckbox_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingCheckbox_null_Listitems_Listitem(2lines)-LeadingCheckbox_0_null,NEXUS_5,1.0,en].png
index 5efbc81b17..be319133b2 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingCheckbox_null_Listitems_Listitem(2lines)-LeadingCheckbox_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingCheckbox_null_Listitems_Listitem(2lines)-LeadingCheckbox_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ca93eef86e2215ccfa04e632589fd90dec23388c393a7c55d757a365c54fd054
-size 19173
+oid sha256:f0756ac69c6a6a69d603fbc6799aea18bdc9d61e13dba138b3d78aca612a243a
+size 19655
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingIcon_null_Listitems_Listitem(2lines)-LeadingIcon_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingIcon_null_Listitems_Listitem(2lines)-LeadingIcon_0_null,NEXUS_5,1.0,en].png
index bbf2b87535..8ca7b0010f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingIcon_null_Listitems_Listitem(2lines)-LeadingIcon_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingIcon_null_Listitems_Listitem(2lines)-LeadingIcon_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1064cc1d43235ceb8b0372e17399cb22269523df8bb1353e1068ed6386c2c4e8
-size 21702
+oid sha256:654aa911d45e015e6d60bd63a5d8e723a22a581c8fe2657299cfce162c765635
+size 21858
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingRadioButton_null_Listitems_Listitem(2lines)-LeadingRadioButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingRadioButton_null_Listitems_Listitem(2lines)-LeadingRadioButton_0_null,NEXUS_5,1.0,en].png
index 5dd9c252a2..71dc274515 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingRadioButton_null_Listitems_Listitem(2lines)-LeadingRadioButton_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingRadioButton_null_Listitems_Listitem(2lines)-LeadingRadioButton_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4f76b18b75b3eff14677c046d725953e86030b113f01fda8bf29cc83705e23f7
-size 20472
+oid sha256:bef0e84ebd7a2ee8f0b906cfd60b4bf998ae31f1a1ae709aa7b301a553ec45f6
+size 20924
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingSwitch_null_Listitems_Listitem(2lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingSwitch_null_Listitems_Listitem(2lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png
index 0d02fc72e6..43a6e2c6c3 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingSwitch_null_Listitems_Listitem(2lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesLeadingSwitch_null_Listitems_Listitem(2lines)-LeadingSwitch_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f1689a328667fa1d7269b827b7e9bef415a642c5cf5163477730a37f95cd3931
-size 22746
+oid sha256:e2b0c050d0ce48eb74b0f3b7a49a696265d900ff4ea947861983329bee3f72f5
+size 23152
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesSimple_null_Listitems_Listitem(2lines)-Simple_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesSimple_null_Listitems_Listitem(2lines)-Simple_0_null,NEXUS_5,1.0,en].png
index 6b27c15e4f..ac818a9450 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesSimple_null_Listitems_Listitem(2lines)-Simple_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesSimple_null_Listitems_Listitem(2lines)-Simple_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d9dec4ea21a07697fccf7d6d5f6f9132089d56d6debec66502754bc88aa9a149
-size 21247
+oid sha256:dcd0e857be61ddce5c9c5a9665af7dd8676c1ff329402cb7f775692ceca0b685
+size 21541
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingCheckBox_null_Listitems_Listitem(2lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingCheckBox_null_Listitems_Listitem(2lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
index 197baadaf0..23dcd5f309 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingCheckBox_null_Listitems_Listitem(2lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingCheckBox_null_Listitems_Listitem(2lines)-TrailingCheckbox_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:14302bebf36302001f4694b3594b5616a3e332be52572051ba55866416fa626b
-size 19313
+oid sha256:1334c03325a6ebe8c0f2d27c7f6626446c743fa3d17af4b2bd6fb5986afe5076
+size 19702
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingIcon_null_Listitems_Listitem(2lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingIcon_null_Listitems_Listitem(2lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
index 6639da3de0..0a613b9b51 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingIcon_null_Listitems_Listitem(2lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingIcon_null_Listitems_Listitem(2lines)-TrailingIcon_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b7f05e42db99e98b190d4b3f85eeb159cdb91cf04f7f0233ac5b6ff34988cb5f
-size 21685
+oid sha256:ed9480916c159c556f3c5220a2c05c006370402ab1f2f3b0fce01cf0c4f0afda
+size 21987
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingRadioButton_null_Listitems_Listitem(2lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingRadioButton_null_Listitems_Listitem(2lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
index cfdf6934db..7a73c1f738 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingRadioButton_null_Listitems_Listitem(2lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingRadioButton_null_Listitems_Listitem(2lines)-TrailingRadioButton_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:383fe0be9d0e110735642e663be3cb8e39960900e306b0eadd836c62cab5a7de
-size 20606
+oid sha256:f43526d34541f6c51b3bb0c837b94cd9a18a38053a95e3975c9c3ec7fd47c23f
+size 20941
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingSwitch_null_Listitems_Listitem(2lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingSwitch_null_Listitems_Listitem(2lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
index d0b15538f0..c063763cf0 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingSwitch_null_Listitems_Listitem(2lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_ListItemTwoLinesTrailingSwitch_null_Listitems_Listitem(2lines)-TrailingSwitch_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:792ca2bab34a395065d912cd6827f0da7b79a2df49b59bf54646c86527e78116
-size 22673
+oid sha256:0cc89eeb8021d9bb63896c15ef0e08d615e3852075afd8d29312f54fc862af09
+size 23084
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Day-2_3_null_0,NEXUS_5,1.0,en].png
deleted file mode 100644
index 28c78fd115..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Day-2_3_null_0,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2d91c6228c80d1d38217e557552e18e64494a94d284de8702d07cc3793150406
-size 29177
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Day-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Day-2_3_null_1,NEXUS_5,1.0,en].png
deleted file mode 100644
index f1abda92fc..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Day-2_3_null_1,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:eeb5b50675e186318458d0900b2d3872ec52710df8e25ba7b633a4be7e13c8da
-size 28196
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Night-2_4_null_0,NEXUS_5,1.0,en].png
deleted file mode 100644
index f7861fa9c7..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Night-2_4_null_0,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:35d63106802f7bce6ddc7ae56a2b206120e9eba1459169480a10bcc5a0d3f9e3
-size 29334
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Night-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Night-2_4_null_1,NEXUS_5,1.0,en].png
deleted file mode 100644
index fcd01bd2aa..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableMatrixUserRow_null_CheckableMatrixUserRow-Night-2_4_null_1,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:3e4c742ac4907f79f5830f2fed5fbacc4ae352e24fcf7e15b75f3f3d8ce85ab2
-size 27151
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableResolvedUserRow_null_CheckableResolvedUserRow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableResolvedUserRow_null_CheckableResolvedUserRow_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..bc072adaf6
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableResolvedUserRow_null_CheckableResolvedUserRow_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9ffaad9291e2a8bb3168b08365faac8417a77dbe1e39e94cd4f1d7dfb12ef7e4
+size 54838
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableUnresolvedUserRow_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableUnresolvedUserRow_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png
index 5d459747a8..69d51f01ad 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableUnresolvedUserRow_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_CheckableUnresolvedUserRow_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3dd3995ea7bdf218f55d2883fade50c29558d2ed2faca318c8d18a0eb7f06a34
-size 116197
+oid sha256:85ead49e9a818ddc9b337e8386b78d80dcb1723e8f77cb8fe4ce5278680eb6a9
+size 116251
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Day-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Day-3_4_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Day-4_5_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Day-3_4_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Night-4_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Night-3_5_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Night-4_6_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeaderPlaceholder_null_MatrixUserHeaderPlaceholder-Night-3_5_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-2_3_null_0,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-3_4_null_0,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-2_3_null_0,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-2_3_null_1,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-3_4_null_1,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Day-2_3_null_1,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-2_4_null_0,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-3_5_null_0,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-2_4_null_0,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-2_4_null_1,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-3_5_null_1,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserHeader_null_MatrixUserHeader-Night-2_4_null_1,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-5_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-4_5_null_0,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-5_6_null_0,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-4_5_null_0,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-5_6_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-4_5_null_1,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-5_6_null_1,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Day-4_5_null_1,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-5_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-4_6_null_0,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-5_7_null_0,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-4_6_null_0,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-5_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-4_6_null_1,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-5_7_null_1,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_MatrixUserRow_null_MatrixUserRow-Night-4_6_null_1,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Day-6_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Day-5_6_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Day-6_7_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Day-5_6_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Night-6_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Night-5_7_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Night-6_8_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedRoom_null_SelectedRoom-Night-5_7_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Day-6_7_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Day-7_8_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Day-6_7_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Night-6_8_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Night-7_9_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUser_null_SelectedUser-Night-6_8_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Day-8_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Day-7_8_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Day-8_9_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Day-7_8_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Night-8_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Night-7_9_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Night-8_10_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_SelectedUsersList_null_SelectedUsersList-Night-7_9_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Day-9_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Day-8_9_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Day-9_10_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Day-8_9_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Night-9_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Night-8_10_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Night-9_11_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_UnsavedAvatar_null_UnsavedAvatar-Night-8_10_null,NEXUS_5,1.0,en].png
diff --git a/tools/localazy/config.json b/tools/localazy/config.json
index 977fe6c889..1bf1e9071b 100644
--- a/tools/localazy/config.json
+++ b/tools/localazy/config.json
@@ -97,7 +97,8 @@
{
"name" : ":features:leaveroom:api",
"includeRegex" : [
- "leave_room_alert_.*"
+ "leave_room_alert_.*",
+ "leave_conversation_alert_.*"
]
},
{
diff --git a/tools/release/release.sh b/tools/release/release.sh
index 26a94cb09e..7bc5ddc0a2 100755
--- a/tools/release/release.sh
+++ b/tools/release/release.sh
@@ -143,7 +143,7 @@ git commit -a -m "Setting version for the release ${version}"
printf "\n================================================================================\n"
printf "Building the bundle locally first...\n"
-./gradlew clean app:bundleRelease
+./gradlew clean app:bundleGplayRelease
printf "\n================================================================================\n"
printf "Running towncrier...\n"
@@ -218,7 +218,7 @@ fi
printf "\n================================================================================\n"
printf "Wait for the GitHub action https://github.com/element-hq/element-x-android/actions/workflows/release.yml?query=branch%%3Amain to build the 'main' branch.\n"
-read -p "After GHA is finished, please enter the artifact URL (for 'elementx-app-bundle-unsigned'): " artifactUrl
+read -p "After GHA is finished, please enter the artifact URL (for 'elementx-app-gplay-bundle-unsigned'): " artifactUrl
printf "\n================================================================================\n"
printf "Downloading the artifact...\n"
@@ -235,10 +235,10 @@ python3 ./tools/github/download_github_artifacts.py \
printf "\n================================================================================\n"
printf "Unzipping the artifact...\n"
-unzip ${targetPath}/elementx-app-bundle-unsigned.zip -d ${targetPath}
+unzip ${targetPath}/elementx-app-gplay-bundle-unsigned.zip -d ${targetPath}
-unsignedBundlePath="${targetPath}/app-release.aab"
-signedBundlePath="${targetPath}/app-release-signed.aab"
+unsignedBundlePath="${targetPath}/app-gplay-release.aab"
+signedBundlePath="${targetPath}/app-gplay-release-signed.aab"
printf "\n================================================================================\n"
printf "Signing file ${unsignedBundlePath} with build-tools version ${buildToolsVersion} for min SDK version ${minSdkVersion}...\n"
@@ -291,6 +291,19 @@ else
printf "APKs will not be generated!\n"
fi
+printf "\n================================================================================\n"
+printf "Create the open testing release on GooglePlay.\n"
+
+printf "On GooglePlay console, go the the open testing section and click on \"Create new release\" button, then:\n"
+printf " - upload the file ${signedBundlePath}.\n"
+printf " - copy the release note from the fastlane file.\n"
+printf " - download the universal APK, to be able to provide it to the GitHub release: click on the right arrow next to the \"App bundle\", then click on the \"Download\" tab, and download the \"Signed, universal APK\".\n"
+printf " - submit the release.\n"
+read -p "Press enter to continue. "
+
+printf "You can then go to \"Publishing overview\" and send the new release for a review by Google.\n"
+read -p "Press enter to continue. "
+
printf "\n================================================================================\n"
githubCreateReleaseLink="https://github.com/element-hq/element-x-android/releases/new?tag=v${version}&title=Element%20X%20Android%20v${version}&body=${changelogUrlEncoded}"
printf "Creating the release on gitHub.\n"
@@ -299,11 +312,12 @@ printf "Then\n"
printf " - copy paste the section of the file CHANGES.md for this release (if not there yet)\n"
printf " - click on the 'Generate releases notes' button\n"
printf " - Add the file ${signedBundlePath} to the GitHub release.\n"
+printf " - Add the universal APK, downloaded from the GooglePlay console to the GitHub release.\n"
read -p ". Press enter to continue. "
printf "\n================================================================================\n"
printf "Message for the Android internal room:\n\n"
-message="@room Element X Android ${version} is ready to be tested. You can get it from https://github.com/element-hq/element-x-android/releases/tag/v${version}. Installation instructions can be found [here](https://github.com/element-hq/element-x-android/blob/develop/docs/install_from_github_release.md). Please report any feedback. Thanks!"
+message="@room Element X Android ${version} is ready to be tested. You can get it from https://github.com/element-hq/element-x-android/releases/tag/v${version}. You can install the universal APK. If you want to install the application from the app bundle, you can follow instructions [here](https://github.com/element-hq/element-x-android/blob/develop/docs/install_from_github_release.md). Please report any feedback. Thanks!"
printf "${message}\n\n"
if [[ -z "${elementBotToken}" ]]; then