diff --git a/.github/workflows/nightly_enterprise.yml b/.github/workflows/nightly_enterprise.yml deleted file mode 100644 index 5e02c68240..0000000000 --- a/.github/workflows/nightly_enterprise.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Build and release Enterprise nightly application - -on: - workflow_dispatch: - schedule: - # Every nights at 4 - - cron: "0 4 * * *" - -env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache - -jobs: - nightly: - name: Build and publish Enterprise nightly bundle to Firebase - runs-on: ubuntu-latest - if: ${{ github.repository == 'element-hq/element-x-android' }} - steps: - - uses: actions/checkout@v4 - - name: Add SSH private keys for submodule repositories - uses: webfactory/ssh-agent@v0.9.1 - with: - ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} - - name: Clone submodules - run: git submodule update --init --recursive - - name: Use JDK 21 - uses: actions/setup-java@v4 - with: - distribution: 'temurin' # See 'Supported distributions' for available options - java-version: '21' - - name: Build and upload Nightly application - run: | - ./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 }} - ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} - ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }} - ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }} - ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }} - ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }} - ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }} - ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }} - ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }} - ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }} - FIREBASE_TOKEN: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_FIREBASE_TOKEN }} - - 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/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/pull_request.yml b/.github/workflows/pull_request.yml index 72a4dfe0a7..929bcb5dcd 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,6 +1,6 @@ name: Pull Request on: - pull_request: + pull_request_target: types: [ opened, edited, labeled, unlabeled, synchronize ] workflow_call: secrets: diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index c22b6fa9ee..131e44d798 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index c3a966e77f..b9fefeaad8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,21 @@ +Changes in Element X v25.03.4 +============================= + + + +## What's Changed +### 🙌 Improvements +* Change : composer suggestions by @ganfra in https://github.com/element-hq/element-x-android/pull/4485 +### 🧱 Build +* Fix flaky incoming verification tests by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4479 +### Dependency upgrades +* fix(deps): update dagger to v2.56.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4472 +* fix(deps): update dependencyanalysis to v2.13.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4473 +* Upgrade embedded EC version to `v0.9.0-rc.4` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4489 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.03.3...v25.03.4 + Changes in Element X v25.03.3 ============================= diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0da59234da..107f9431f2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,7 @@ import extension.allEnterpriseImpl import extension.allFeaturesImpl import extension.allLibrariesImpl import extension.allServicesImpl +import extension.buildConfigFieldStr import extension.koverDependencies import extension.locales import extension.setupAnvil @@ -102,7 +103,7 @@ android { } val baseAppName = BuildTimeConfig.APPLICATION_NAME - logger.warnInBox("Building $baseAppName") + logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName)") buildTypes { getByName("debug") { @@ -170,13 +171,13 @@ android { create("gplay") { dimension = "store" isDefault = true - buildConfigField("String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"") - buildConfigField("String", "FLAVOR_DESCRIPTION", "\"GooglePlay\"") + buildConfigFieldStr("SHORT_FLAVOR_DESCRIPTION", "G") + buildConfigFieldStr("FLAVOR_DESCRIPTION", "GooglePlay") } create("fdroid") { dimension = "store" - buildConfigField("String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"") - buildConfigField("String", "FLAVOR_DESCRIPTION", "\"FDroid\"") + buildConfigFieldStr("SHORT_FLAVOR_DESCRIPTION", "F") + buildConfigFieldStr("FLAVOR_DESCRIPTION", "FDroid") } } } @@ -291,8 +292,8 @@ tasks.withType().configureEach { outputs.upToDateWhen { false } val gitRevision = providers.of(GitRevisionValueSource::class.java) {}.get() val gitBranchName = providers.of(GitBranchNameValueSource::class.java) {}.get() - android.defaultConfig.buildConfigField("String", "GIT_REVISION", "\"$gitRevision\"") - android.defaultConfig.buildConfigField("String", "GIT_BRANCH_NAME", "\"$gitBranchName\"") + android.defaultConfig.buildConfigFieldStr("GIT_REVISION", gitRevision) + android.defaultConfig.buildConfigFieldStr("GIT_BRANCH_NAME", gitBranchName) } licensee { diff --git a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt index a4bfe0c60d..30bef76339 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -9,6 +9,8 @@ package io.element.android.x import android.app.Application import androidx.startup.AppInitializer +import io.element.android.appconfig.RageshakeConfig +import io.element.android.appconfig.isEnabled import io.element.android.features.cachecleaner.api.CacheCleanerInitializer import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.x.di.AppComponent @@ -23,7 +25,9 @@ class ElementXApplication : Application(), DaggerComponentOwner { override fun onCreate() { super.onCreate() AppInitializer.getInstance(this).apply { - initializeComponent(CrashInitializer::class.java) + if (RageshakeConfig.isEnabled) { + initializeComponent(CrashInitializer::class.java) + } initializeComponent(PlatformInitializer::class.java) initializeComponent(CacheCleanerInitializer::class.java) } diff --git a/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt index 650480a0ee..6dd0d69dc0 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt +++ b/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt @@ -37,6 +37,7 @@ class PlatformInitializer : Initializer { writesToFilesConfiguration = defaultWriteToDiskConfiguration(bugReporter), logLevel = logLevel, extraTargets = listOf(ELEMENT_X_TARGET), + traceLogPacks = runBlocking { preferencesStore.getTracingLogPacksFlow().first() }, ) bugReporter.setCurrentTracingLogLevel(logLevel.name) platformService.init(tracingConfiguration) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 884ae0ea71..f61fb33868 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,8 +5,8 @@ ~ Please see LICENSE files in the repository root for full details. --> - + #FF101317 - + #FFFFFFFF diff --git a/appconfig/build.gradle.kts b/appconfig/build.gradle.kts index 99c7e1defa..15b8bd51ba 100644 --- a/appconfig/build.gradle.kts +++ b/appconfig/build.gradle.kts @@ -1,3 +1,6 @@ +import config.BuildTimeConfig +import extension.buildConfigFieldStr + /* * Copyright 2022-2024 New Vector Ltd. * @@ -10,6 +13,37 @@ plugins { android { namespace = "io.element.android.appconfig" + + buildFeatures { + buildConfig = true + } + + defaultConfig { + buildConfigFieldStr( + name = "URL_POLICY", + value = if (isEnterpriseBuild) { + BuildTimeConfig.URL_POLICY ?: "" + } else { + "https://element.io/cookie-policy" + }, + ) + buildConfigFieldStr( + name = "BUG_REPORT_URL", + value = if (isEnterpriseBuild) { + BuildTimeConfig.BUG_REPORT_URL ?: "" + } else { + "https://riot.im/bugreports/submit" + }, + ) + buildConfigFieldStr( + name = "BUG_REPORT_APP_NAME", + value = if (isEnterpriseBuild) { + BuildTimeConfig.BUG_REPORT_APP_NAME ?: "" + } else { + "element-x-android" + }, + ) + } } dependencies { diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt index 4b213db637..346fce4725 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt @@ -8,5 +8,5 @@ package io.element.android.appconfig object AnalyticsConfig { - const val POLICY_LINK = "https://element.io/cookie-policy" + const val POLICY_LINK = BuildConfig.URL_POLICY } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt index 47d94cc497..8c836bc8a2 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt @@ -11,17 +11,23 @@ object RageshakeConfig { /** * The URL to submit bug reports to. */ - const val BUG_REPORT_URL = "https://riot.im/bugreports/submit" + const val BUG_REPORT_URL = BuildConfig.BUG_REPORT_URL /** * As per https://github.com/matrix-org/rageshake: * Identifier for the application (eg 'riot-web'). * Should correspond to a mapping configured in the configuration file for github issue reporting to work. */ - const val BUG_REPORT_APP_NAME = "element-x-android" + const val BUG_REPORT_APP_NAME = BuildConfig.BUG_REPORT_APP_NAME /** * The maximum size of the upload request. Default value is just below CloudFlare's max request size. */ const val MAX_LOG_UPLOAD_SIZE = 50 * 1024 * 1024L } + +/** + * Whether the rageshake feature is enabled. + */ +val RageshakeConfig.isEnabled: Boolean + get() = BUG_REPORT_URL.isNotEmpty() && BUG_REPORT_APP_NAME.isNotEmpty() diff --git a/appicon/element/src/main/ic_launcher-playstore.png b/appicon/element/src/main/ic_launcher-playstore.png index 62af9cf4b2..325bf570f5 100644 Binary files a/appicon/element/src/main/ic_launcher-playstore.png and b/appicon/element/src/main/ic_launcher-playstore.png differ diff --git a/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt b/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt index f55c6f23e8..c25675bffc 100644 --- a/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt +++ b/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt @@ -10,25 +10,28 @@ package io.element.android.appicon.element import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp @Preview @Composable internal fun IconPreview() { Box { - Image(painter = painterResource(id = R.mipmap.ic_launcher_background), contentDescription = null) - Image(painter = painterResource(id = R.mipmap.ic_launcher_foreground), contentDescription = null) + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(id = R.drawable.ic_launcher_background), + contentDescription = null, + ) + Image( + painter = painterResource(id = R.mipmap.ic_launcher_foreground), + contentDescription = null, + ) } } @@ -36,8 +39,15 @@ internal fun IconPreview() { @Composable internal fun RoundIconPreview() { Box(modifier = Modifier.clip(shape = CircleShape)) { - Image(painter = painterResource(id = R.mipmap.ic_launcher_background), contentDescription = null) - Image(painter = painterResource(id = R.mipmap.ic_launcher_foreground), contentDescription = null) + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(id = R.drawable.ic_launcher_background), + contentDescription = null, + ) + Image( + painter = painterResource(id = R.mipmap.ic_launcher_foreground), + contentDescription = null, + ) } } @@ -46,10 +56,7 @@ internal fun RoundIconPreview() { internal fun MonochromeIconPreview() { Box( modifier = Modifier - .size(108.dp) - .background(Color(0xFF2F3133)) - .clip(shape = RoundedCornerShape(32.dp)), - contentAlignment = Alignment.Center + .background(Color(0xFF2F3133)), ) { Image( painter = painterResource(id = R.mipmap.ic_launcher_monochrome), diff --git a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher.webp b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher.webp index 793d5ca60d..2ae0da8d0f 100644 Binary files a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher.webp and b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_background.webp b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_background.webp deleted file mode 100644 index f051ae3c81..0000000000 Binary files a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_background.webp and /dev/null differ diff --git a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index d1ff05833e..e40370b86f 100644 Binary files a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 78a93b86f1..8ad6b74901 100644 Binary files a/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/appicon/element/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher.webp b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher.webp index e8f321ff17..d4e1b90f22 100644 Binary files a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher.webp and b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_background.webp b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_background.webp deleted file mode 100644 index 27d9d1db19..0000000000 Binary files a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_background.webp and /dev/null differ diff --git a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp index f411c1016c..ac2361f8b0 100644 Binary files a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 5380a9e861..3cd52b2182 100644 Binary files a/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/appicon/element/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher.webp b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher.webp index b31de82585..527b23880a 100644 Binary files a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_background.webp b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_background.webp deleted file mode 100644 index 4dbc6db066..0000000000 Binary files a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_background.webp and /dev/null differ diff --git a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp index 5e6654b50c..f8c5c5f218 100644 Binary files a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index a368522d59..1c98f35c9f 100644 Binary files a/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/appicon/element/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 889388eab6..ed524b893c 100644 Binary files a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp deleted file mode 100644 index b635d5cbb5..0000000000 Binary files a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp and /dev/null differ diff --git a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index 9aebd17d21..bb401bcb37 100644 Binary files a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index af59382417..a6b0547ed0 100644 Binary files a/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/appicon/element/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 97afc844cb..359e3921a1 100644 Binary files a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp deleted file mode 100644 index b5cb68c7bb..0000000000 Binary files a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp and /dev/null differ diff --git a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index 92e763d12f..f0f9a63324 100644 Binary files a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index d71ab178fe..36125792fe 100644 Binary files a/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/appicon/element/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/appicon/element/src/release/res/drawable/ic_launcher_background.xml b/appicon/element/src/release/res/drawable/ic_launcher_background.xml index 6ff3e59543..1cbabacb4f 100644 --- a/appicon/element/src/release/res/drawable/ic_launcher_background.xml +++ b/appicon/element/src/release/res/drawable/ic_launcher_background.xml @@ -1,2 +1,10 @@ - + + + diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index fbecfeb60b..2a7403862e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.push.api.PushService @@ -79,7 +78,7 @@ class LoggedInPresenter @Inject constructor( .launchIn(this) } val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() val showSyncSpinner by remember { derivedStateOf { isOnline && syncIndicator == RoomListService.SyncIndicator.Show diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 56f3716ca1..a2016a9171 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -49,7 +49,6 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -211,7 +210,7 @@ class RoomFlowNode @AssistedInject constructor( } private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier -> - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() LoadingRoomNodeView( state = LoadingRoomState.Loading, hasNetworkConnection = isOnline, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt index 49a05c9d36..3c6f30af72 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt @@ -36,7 +36,6 @@ import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -114,7 +113,7 @@ class JoinedRoomFlowNode @AssistedInject constructor( private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier -> val loadingRoomState by loadingRoomStateStateFlow.collectAsState() - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() LoadingRoomNodeView( state = loadingRoomState, hasNetworkConnection = isOnline, diff --git a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt index 8640fb03c8..0d79b5847a 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt @@ -241,7 +241,7 @@ class IntentResolverTest { } private fun createIntentResolver( - permalinkParserResult: () -> PermalinkData = { lambdaError() } + permalinkParserResult: (String) -> PermalinkData = { lambdaError() } ): IntentResolver { return IntentResolver( deeplinkParser = DeeplinkParser(), diff --git a/fastlane/metadata/android/en-US/changelogs/202504000.txt b/fastlane/metadata/android/en-US/changelogs/202504000.txt new file mode 100644 index 0000000000..8955ade680 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202504000.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index f3708552d3..d0b9479886 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,38 +1,28 @@ -Element X brings you both sovereign & seamless collaboration built on Matrix. +Freedom to communicate on your own terms -The collaboration capabilities include chat & video calls with the modern set of features such as: - • public & private channels - • room moderation & access control - • replies, reactions, polls, read receipts, pinned messages, etc. - • simultaneous chat & calls (picture in picture) - • decentralized & federated communication across organizations +For individuals and communities - private communication between family, friends, hobby groups, clubs, etc. -All this comes in a secure & sovereign fashion without compromising responsiveness or overall usability of the app: - • enterprise-grade single sign-on - • easy & secure login & device verification via QR-code - • end to end encryption & zero trust - • protection against MITM & other cyber attacks +Element X gives you fast, secure and private instant messaging and video calls built on Matrix, the open standard for real-time communication. This is a free and open-source app maintained at https://github.com/element-hq/element-x-android. -If you’re a new user, use the new Element X app from the start. Compared to the current Element app you will get: - • greatly enhanced performance, sleek user interface and overall better user experience - • enterprise-grade support for single sign-on (OIDC) - • QR-code based login & device verification - • natively integrated Element Call for video calls - • continuous improvements, bug fixes and new features +Stay in touch with friends, family and communities with: + • Real time messaging & video calls + • Public rooms for open group communication + • Private rooms for closed group communication + • Rich messaging features: emoji reactions, replies, polls, pinned messages and more. + • Video calling while browsing messages. + • Interoperability with other Matrix-based apps such as FluffyChat, Cinny and many more. -If you’re an existing user, using the current Element app - check out the new Element X and start planning your transition. The current Element app will be phased out and will only get critical security updates. +Privacy-first +Unlike some other messengers from Big Tech companies, we don’t mine your data or monitor your communications. -Own your data -Matrix-based, Element X lets you self-host your data or choose from any free public server (the default is matrix.org, but there are plenty of others to choose from). However you host, you have ownership; it’s your data. You’re not the product. You’re in control. +Own your conversations +Choose where to host your data - from any public server (the largest free server is matrix.org, but there are plenty of others to choose from) to creating your own personal server and hosting it on your own domain. This ability to choose a server is a large part of what differentiates us from other real time communication apps. However you host, you have ownership; it’s your data. You’re not the product. You’re in control. -Interoperate natively -Enjoy the freedom of the Matrix open standard! You have native interoperability with any other Matrix-based app. So just like email, it doesn't matter if your friends, partners or customers are on a different Matrix-based app - you can still connect. +Communicate in real time, all the time +Use Element everywhere. Stay in touch wherever you are with fully synchronised message history across all your devices, including on the web at https://app.element.io -Encrypt your data -Enjoy your right to private conversations - free from data mining, ads and all the rest of it - and stay secure. Only the people in your conversation can read your messages. - -Chat across multiple devices -Stay in touch wherever you are with fully synchronized message history across all your devices, even those running Element legacy app, and on the web at https://app.element.io +Element X is our next-generation app +If you’re using the original Element app, it’s time to try Element X! It’s faster, easier to use, and more powerful than the original app. It’s better in every way and we’re adding new features all the time. The application requires the android.permission.REQUEST_INSTALL_PACKAGES permission to enable the installation of applications received as attachments, ensuring seamless and convenient access to new software within the app. diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png index a3107af4b1..325bf570f5 100644 Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt index 8e62bdd305..647b205722 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt @@ -13,12 +13,17 @@ open class AnalyticsPreferencesStateProvider : PreviewParameterProvider get() = sequenceOf( aAnalyticsPreferencesState().copy(isEnabled = true), + aAnalyticsPreferencesState().copy(isEnabled = true, policyUrl = ""), ) } -fun aAnalyticsPreferencesState() = AnalyticsPreferencesState( - applicationName = "Element X", - isEnabled = false, - policyUrl = "https://element.io", +fun aAnalyticsPreferencesState( + applicationName: String = "Element X", + isEnabled: Boolean = false, + policyUrl: String = "https://element.io", +) = AnalyticsPreferencesState( + applicationName = applicationName, + isEnabled = isEnabled, + policyUrl = policyUrl, eventSink = {} ) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 2e7ee47736..70461f22d3 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -36,11 +36,6 @@ fun AnalyticsPreferencesView( id = R.string.screen_analytics_settings_help_us_improve, state.applicationName ) - val linkText = buildAnnotatedStringWithStyledPart( - R.string.screen_analytics_settings_read_terms, - R.string.screen_analytics_settings_read_terms_content_link, - tagAndLink = LINK_TAG to state.policyUrl, - ) Column(modifier) { ListItem( headlineContent = { @@ -57,7 +52,14 @@ fun AnalyticsPreferencesView( onEnabledChanged(!state.isEnabled) } ) - ListSupportingText(annotatedString = linkText) + if (state.policyUrl.isNotEmpty()) { + val linkText = buildAnnotatedStringWithStyledPart( + R.string.screen_analytics_settings_read_terms, + R.string.screen_analytics_settings_read_terms_content_link, + tagAndLink = LINK_TAG to state.policyUrl, + ) + ListSupportingText(annotatedString = linkText) + } } } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt index d545d00951..39b99a9257 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt @@ -9,6 +9,7 @@ package io.element.android.features.analytics.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.appconfig.AnalyticsConfig import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta @@ -36,6 +37,7 @@ class AnalyticsOptInPresenter @Inject constructor( return AnalyticsOptInState( applicationName = buildMeta.applicationName, + hasPolicyLink = AnalyticsConfig.POLICY_LINK.isNotEmpty(), eventSink = ::handleEvents ) } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt index d7e99e56c1..a0913bdb4f 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt @@ -11,5 +11,6 @@ import io.element.android.features.analytics.api.AnalyticsOptInEvents data class AnalyticsOptInState( val applicationName: String, + val hasPolicyLink: Boolean, val eventSink: (AnalyticsOptInEvents) -> Unit ) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt index c159b738a7..e6917c237e 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt @@ -14,10 +14,14 @@ open class AnalyticsOptInStateProvider @Inject constructor() : PreviewParameterP override val values: Sequence get() = sequenceOf( aAnalyticsOptInState(), + aAnalyticsOptInState(hasPolicyLink = false), ) } -fun aAnalyticsOptInState() = AnalyticsOptInState( +fun aAnalyticsOptInState( + hasPolicyLink: Boolean = true, +) = AnalyticsOptInState( applicationName = "Element X", + hasPolicyLink = hasPolicyLink, eventSink = {} ) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 3e8b9d98f9..a6ae754944 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -95,25 +95,27 @@ private fun AnalyticsOptInHeader( subtitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve), iconStyle = BigIcon.Style.Default(CompoundIcons.Chart()) ) - val text = buildAnnotatedStringWithStyledPart( - R.string.screen_analytics_prompt_read_terms, - R.string.screen_analytics_prompt_read_terms_content_link, - color = Color.Unspecified, - underline = false, - bold = true, - tagAndLink = LINK_TAG to AnalyticsConfig.POLICY_LINK, - ) - ClickableLinkText( - annotatedString = text, - onClick = { onClickTerms() }, - modifier = Modifier - .padding(8.dp), - style = ElementTheme.typography.fontBodyMdRegular - .copy( - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) - ) + if (state.hasPolicyLink) { + val text = buildAnnotatedStringWithStyledPart( + R.string.screen_analytics_prompt_read_terms, + R.string.screen_analytics_prompt_read_terms_content_link, + color = Color.Unspecified, + underline = false, + bold = true, + tagAndLink = LINK_TAG to AnalyticsConfig.POLICY_LINK, + ) + ClickableLinkText( + annotatedString = text, + onClick = { onClickTerms() }, + modifier = Modifier + .padding(8.dp), + style = ElementTheme.typography.fontBodyMdRegular + .copy( + color = ElementTheme.colors.textSecondary, + textAlign = TextAlign.Center, + ) + ) + } } } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt index 8212cc6c88..6904734f26 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt @@ -27,8 +27,7 @@ class AnalyticsPreferencesPresenter @Inject constructor( @Composable override fun present(): AnalyticsPreferencesState { val localCoroutineScope = rememberCoroutineScope() - val isEnabled = analyticsService.getUserConsent() - .collectAsState(initial = false) + val isEnabled = analyticsService.userConsentFlow.collectAsState(initial = false) fun handleEvents(event: AnalyticsOptInEvents) { when (event) { diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt index 1353d5ab5f..e9319fd7ba 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -35,10 +35,10 @@ class AnalyticsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(analyticsService.didAskUserConsent().first()).isFalse() + assertThat(analyticsService.didAskUserConsentFlow.first()).isFalse() initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true)) - assertThat(analyticsService.didAskUserConsent().first()).isTrue() - assertThat(analyticsService.getUserConsent().first()).isTrue() + assertThat(analyticsService.didAskUserConsentFlow.first()).isTrue() + assertThat(analyticsService.userConsentFlow.first()).isTrue() } } @@ -53,10 +53,10 @@ class AnalyticsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(analyticsService.didAskUserConsent().first()).isFalse() + assertThat(analyticsService.didAskUserConsentFlow.first()).isFalse() initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false)) - assertThat(analyticsService.didAskUserConsent().first()).isTrue() - assertThat(analyticsService.getUserConsent().first()).isFalse() + assertThat(analyticsService.didAskUserConsentFlow.first()).isTrue() + assertThat(analyticsService.userConsentFlow.first()).isFalse() } } } diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt index 30b64eb8ab..8ba915f03c 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -11,6 +11,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.appconfig.AnalyticsConfig import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService @@ -35,7 +36,7 @@ class AnalyticsPreferencesPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.isEnabled).isTrue() - assertThat(initialState.policyUrl).isNotEmpty() + assertThat(initialState.policyUrl).isEqualTo(AnalyticsConfig.POLICY_LINK) } } diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt index 9229681966..53e1b8c846 100644 --- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt @@ -32,7 +32,7 @@ interface ElementCallEntryPoint { * @param notificationChannelId The id of the notification channel to use for the call notification. * @param textContent The text content of the notification. If null the default content from the system will be used. */ - fun handleIncomingCall( + suspend fun handleIncomingCall( callType: CallType.RoomCall, eventId: EventId, senderId: UserId, diff --git a/features/call/impl/build.gradle.kts b/features/call/impl/build.gradle.kts index be332ec4fe..f0a9eedca0 100644 --- a/features/call/impl/build.gradle.kts +++ b/features/call/impl/build.gradle.kts @@ -1,3 +1,4 @@ +import extension.buildConfigFieldStr import extension.readLocalProperty import extension.setupAnvil @@ -26,45 +27,35 @@ android { } defaultConfig { - buildConfigField( - type = "String", + buildConfigFieldStr( name = "SENTRY_DSN", - value = (System.getenv("ELEMENT_CALL_SENTRY_DSN") + value = System.getenv("ELEMENT_CALL_SENTRY_DSN") ?: readLocalProperty("features.call.sentry.dsn") ?: "" - ).let { "\"$it\"" } ) - buildConfigField( - type = "String", + buildConfigFieldStr( name = "POSTHOG_USER_ID", - value = (System.getenv("ELEMENT_CALL_POSTHOG_USER_ID") + value = System.getenv("ELEMENT_CALL_POSTHOG_USER_ID") ?: readLocalProperty("features.call.posthog.userid") ?: "" - ).let { "\"$it\"" } ) - buildConfigField( - type = "String", + buildConfigFieldStr( name = "POSTHOG_API_HOST", - value = (System.getenv("ELEMENT_CALL_POSTHOG_API_HOST") + value = System.getenv("ELEMENT_CALL_POSTHOG_API_HOST") ?: readLocalProperty("features.call.posthog.api.host") ?: "" - ).let { "\"$it\"" } ) - buildConfigField( - type = "String", + buildConfigFieldStr( name = "POSTHOG_API_KEY", - value = (System.getenv("ELEMENT_CALL_POSTHOG_API_KEY") + value = System.getenv("ELEMENT_CALL_POSTHOG_API_KEY") ?: readLocalProperty("features.call.posthog.api.key") ?: "" - ).let { "\"$it\"" } ) - buildConfigField( - type = "String", + buildConfigFieldStr( name = "RAGESHAKE_URL", - value = (System.getenv("ELEMENT_CALL_RAGESHAKE_URL") + value = System.getenv("ELEMENT_CALL_RAGESHAKE_URL") ?: readLocalProperty("features.call.regeshake.url") ?: "" - ).let { "\"$it\"" } ) } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt index 290bfe0824..009840743c 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt @@ -34,7 +34,7 @@ class DefaultElementCallEntryPoint @Inject constructor( context.startActivity(IntentProvider.createIntent(context, callType)) } - override fun handleIncomingCall( + override suspend fun handleIncomingCall( callType: CallType.RoomCall, eventId: EventId, senderId: UserId, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt index ab6f07ce49..bbc0611083 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt @@ -16,6 +16,8 @@ import io.element.android.features.call.impl.di.CallBindings import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.utils.ActiveCallManager import io.element.android.libraries.architecture.bindings +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject /** @@ -27,10 +29,16 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() { } @Inject lateinit var activeCallManager: ActiveCallManager + + @Inject + lateinit var appCoroutineScope: CoroutineScope + override fun onReceive(context: Context, intent: Intent?) { val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) } ?: return context.bindings().inject(this) - activeCallManager.hungUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) + appCoroutineScope.launch { + activeCallManager.hungUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) + } } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt index 47f5682f4c..6baffd8143 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt @@ -62,6 +62,7 @@ class CallScreenPresenter @AssistedInject constructor( private val activeCallManager: ActiveCallManager, private val languageTagProvider: LanguageTagProvider, private val appForegroundStateService: AppForegroundStateService, + private val appCoroutineScope: CoroutineScope, ) : Presenter { @AssistedFactory interface Factory { @@ -87,7 +88,7 @@ class CallScreenPresenter @AssistedInject constructor( coroutineScope.launch { // Sets the call as joined activeCallManager.joinedCall(callType) - loadUrl( + fetchRoomCallUrl( inputs = callType, urlState = urlState, callWidgetDriver = callWidgetDriver, @@ -96,7 +97,7 @@ class CallScreenPresenter @AssistedInject constructor( ) } onDispose { - activeCallManager.hungUpCall(callType) + appCoroutineScope.launch { activeCallManager.hungUpCall(callType) } } } @@ -187,7 +188,7 @@ class CallScreenPresenter @AssistedInject constructor( ) } - private suspend fun loadUrl( + private suspend fun fetchRoomCallUrl( inputs: CallType, urlState: MutableState>, callWidgetDriver: MutableState, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index 9669285ce0..c5162d9a9e 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -24,9 +24,11 @@ import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject /** @@ -55,6 +57,9 @@ class IncomingCallActivity : AppCompatActivity() { @Inject lateinit var buildMeta: BuildMeta + @Inject + lateinit var appCoroutineScope: CoroutineScope + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -102,6 +107,8 @@ class IncomingCallActivity : AppCompatActivity() { private fun onCancel() { val activeCall = activeCallManager.activeCall.value ?: return - activeCallManager.hungUpCall(callType = activeCall.callType) + appCoroutineScope.launch { + activeCallManager.hungUpCall(callType = activeCall.callType) + } } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 2a12117a48..fe266d1cf6 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -8,8 +8,11 @@ package io.element.android.features.call.impl.utils import android.annotation.SuppressLint +import android.content.Context +import android.os.PowerManager import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationManagerCompat +import androidx.core.content.getSystemService import com.squareup.anvil.annotations.ContributesBinding import io.element.android.appconfig.ElementCallConfig import io.element.android.features.call.api.CallType @@ -17,6 +20,7 @@ import io.element.android.features.call.api.CurrentCall import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator 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.MatrixClientProvider import io.element.android.libraries.push.api.notifications.ForegroundServiceType @@ -38,6 +42,8 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber import javax.inject.Inject import kotlin.time.Duration.Companion.seconds @@ -55,25 +61,26 @@ interface ActiveCallManager { * Registers an incoming call if there isn't an existing active call and posts a [CallState.Ringing] notification. * @param notificationData The data for the incoming call notification. */ - fun registerIncomingCall(notificationData: CallNotificationData) + suspend fun registerIncomingCall(notificationData: CallNotificationData) /** * Called when the active call has been hung up. It will remove any existing UI and the active call. * @param callType The type of call that the user hung up, either an external url one or a room one. */ - fun hungUpCall(callType: CallType) + suspend fun hungUpCall(callType: CallType) /** * Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall]. * * @param callType The type of call that the user joined, either an external url one or a room one. */ - fun joinedCall(callType: CallType) + suspend fun joinedCall(callType: CallType) } @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class DefaultActiveCallManager @Inject constructor( + @ApplicationContext context: Context, private val coroutineScope: CoroutineScope, private val onMissedCallNotificationHandler: OnMissedCallNotificationHandler, private val ringingCallNotificationCreator: RingingCallNotificationCreator, @@ -83,33 +90,47 @@ class DefaultActiveCallManager @Inject constructor( ) : ActiveCallManager { private var timedOutCallJob: Job? = null + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal val activeWakeLock: PowerManager.WakeLock? = context.getSystemService() + ?.takeIf { it.isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK) } + ?.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "${context.packageName}:IncomingCallWakeLock") + override val activeCall = MutableStateFlow(null) + private val mutex = Mutex() + init { observeRingingCall() observeCurrentCall() } - override fun registerIncomingCall(notificationData: CallNotificationData) { - if (activeCall.value != null) { - displayMissedCallNotification(notificationData) - Timber.w("Already have an active call, ignoring incoming call: $notificationData") - return - } - activeCall.value = ActiveCall( - callType = CallType.RoomCall( - sessionId = notificationData.sessionId, - roomId = notificationData.roomId, - ), - callState = CallState.Ringing(notificationData), - ) + override suspend fun registerIncomingCall(notificationData: CallNotificationData) { + mutex.withLock { + if (activeCall.value != null) { + displayMissedCallNotification(notificationData) + Timber.w("Already have an active call, ignoring incoming call: $notificationData") + return + } + activeCall.value = ActiveCall( + callType = CallType.RoomCall( + sessionId = notificationData.sessionId, + roomId = notificationData.roomId, + ), + callState = CallState.Ringing(notificationData), + ) - timedOutCallJob = coroutineScope.launch { - showIncomingCallNotification(notificationData) + timedOutCallJob = coroutineScope.launch { + showIncomingCallNotification(notificationData) - // Wait for the ringing call to time out - delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds) - incomingCallTimedOut(displayMissedCallNotification = true) + // Wait for the ringing call to time out + delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds) + incomingCallTimedOut(displayMissedCallNotification = true) + } + + // Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data + if (activeWakeLock?.isHeld == false) { + activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L) + } } } @@ -117,10 +138,13 @@ class DefaultActiveCallManager @Inject constructor( * Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification. */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun incomingCallTimedOut(displayMissedCallNotification: Boolean) { + suspend fun incomingCallTimedOut(displayMissedCallNotification: Boolean) = mutex.withLock { val previousActiveCall = activeCall.value ?: return val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return activeCall.value = null + if (activeWakeLock?.isHeld == true) { + activeWakeLock.release() + } cancelIncomingCallNotification() @@ -129,18 +153,24 @@ class DefaultActiveCallManager @Inject constructor( } } - override fun hungUpCall(callType: CallType) { + override suspend fun hungUpCall(callType: CallType) = mutex.withLock { if (activeCall.value?.callType != callType) { Timber.w("Call type $callType does not match the active call type, ignoring") return } cancelIncomingCallNotification() + if (activeWakeLock?.isHeld == true) { + activeWakeLock.release() + } timedOutCallJob?.cancel() activeCall.value = null } - override fun joinedCall(callType: CallType) { + override suspend fun joinedCall(callType: CallType) = mutex.withLock { cancelIncomingCallNotification() + if (activeWakeLock?.isHeld == true) { + activeWakeLock.release() + } timedOutCallJob?.cancel() activeCall.value = ActiveCall( @@ -201,6 +231,7 @@ class DefaultActiveCallManager @Inject constructor( ?.getRoom(callType.roomId) ?.roomInfoFlow ?.map { + Timber.d("Has room call status changed for ringing call: ${it.hasRoomCall}") it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants) } ?: flowOf() diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt index 97d6dac427..86d9b80d65 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt @@ -20,16 +20,21 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.Shadows.shadowOf +import kotlin.time.Duration.Companion.seconds @RunWith(RobolectricTestRunner::class) class DefaultElementCallEntryPointTest { @Test - fun `startCall - starts ElementCallActivity setup with the needed extras`() { + fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest { val entryPoint = createEntryPoint() entryPoint.startCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID)) @@ -39,8 +44,9 @@ class DefaultElementCallEntryPointTest { assertThat(intent.extras?.containsKey("EXTRA_CALL_TYPE")).isTrue() } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `handleIncomingCall - registers the incoming call using ActiveCallManager`() { + fun `handleIncomingCall - registers the incoming call using ActiveCallManager`() = runTest { val registerIncomingCallLambda = lambdaRecorder {} val activeCallManager = FakeActiveCallManager(registerIncomingCallResult = registerIncomingCallLambda) val entryPoint = createEntryPoint(activeCallManager = activeCallManager) @@ -57,10 +63,12 @@ class DefaultElementCallEntryPointTest { textContent = "textContent", ) + advanceTimeBy(1.seconds) + registerIncomingCallLambda.assertions().isCalledOnce() } - private fun createEntryPoint( + private fun TestScope.createEntryPoint( activeCallManager: FakeActiveCallManager = FakeActiveCallManager(), ) = DefaultElementCallEntryPoint( context = InstrumentationRegistry.getInstrumentation().targetContext, diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt index ce185bcdfc..5ec48ce6a1 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt @@ -44,12 +44,14 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import kotlin.time.Duration.Companion.seconds -class CallScreenPresenterTest { +@OptIn(ExperimentalCoroutinesApi::class) class CallScreenPresenterTest { @get:Rule val warmUpRule = WarmUpRule() @@ -66,7 +68,8 @@ class CallScreenPresenterTest { presenter.present() }.test { // Wait until the URL is loaded - skipItems(1) + advanceTimeBy(1.seconds) + skipItems(2) val initialState = awaitItem() assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io")) assertThat(initialState.webViewError).isNull() @@ -101,16 +104,23 @@ class CallScreenPresenterTest { presenter.present() }.test { // Wait until the URL is loaded + advanceTimeBy(1.seconds) skipItems(1) + joinedCallLambda.assertions().isCalledOnce() val initialState = awaitItem() - assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java) + assertThat(initialState.urlState).isInstanceOf(AsyncData.Loading::class.java) assertThat(initialState.isCallActive).isFalse() assertThat(initialState.isInWidgetMode).isTrue() assertThat(widgetProvider.getWidgetCalled).isTrue() assertThat(widgetDriver.runCalledCount).isEqualTo(1) analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall)) sendCallNotificationIfNeededLambda.assertions().isCalledOnce() + + // Wait until the WidgetDriver is loaded + skipItems(1) + + assertThat(awaitItem().urlState).isInstanceOf(AsyncData.Success::class.java) } } @@ -126,6 +136,9 @@ class CallScreenPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + // Give it time to load the URL and WidgetDriver + advanceTimeBy(1.seconds) + val initialState = awaitItem() initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor)) @@ -141,7 +154,6 @@ class CallScreenPresenterTest { } } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - hang up event closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) { val navigator = FakeCallScreenNavigator() @@ -158,11 +170,15 @@ class CallScreenPresenterTest { presenter.present() }.test { val initialState = awaitItem() + + // Give it time to load the URL and WidgetDriver + advanceTimeBy(1.seconds) + initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor)) initialState.eventSink(CallScreenEvents.Hangup) - // Let background coroutines run + // Let background coroutines run and the widget drive be received runCurrent() assertThat(navigator.closeCalled).isTrue() @@ -172,7 +188,6 @@ class CallScreenPresenterTest { } } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - a received close message closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) { val navigator = FakeCallScreenNavigator() @@ -189,11 +204,16 @@ class CallScreenPresenterTest { presenter.present() }.test { val initialState = awaitItem() + + // Give it time to load the URL and WidgetDriver + advanceTimeBy(1.seconds) + initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor)) messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""") // Let background coroutines run + advanceTimeBy(1.seconds) runCurrent() assertThat(navigator.closeCalled).isTrue() @@ -218,7 +238,9 @@ class CallScreenPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) + // Give it time to load the URL and WidgetDriver + advanceTimeBy(1.seconds) + skipItems(2) val initialState = awaitItem() assertThat(initialState.isCallActive).isFalse() initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor)) @@ -235,7 +257,7 @@ class CallScreenPresenterTest { } """.trimIndent() ) - skipItems(1) + skipItems(2) val finalState = awaitItem() assertThat(finalState.isCallActive).isTrue() } @@ -300,7 +322,8 @@ class CallScreenPresenterTest { presenter.present() }.test { // Wait until the URL is loaded - skipItems(1) + advanceTimeBy(1.seconds) + skipItems(2) val initialState = awaitItem() initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error")) val finalState = awaitItem() @@ -329,6 +352,8 @@ class CallScreenPresenterTest { initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error")) val finalState = awaitItem() assertThat(finalState.webViewError).isNull() + + cancelAndIgnoreRemainingEvents() } } @@ -361,6 +386,7 @@ class CallScreenPresenterTest { screenTracker = screenTracker, languageTagProvider = FakeLanguageTagProvider("en-US"), appForegroundStateService = appForegroundStateService, + appCoroutineScope = backgroundScope, ) } } diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt index ea1357c0e9..ff53b1ff77 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt @@ -7,7 +7,9 @@ package io.element.android.features.call.utils +import android.os.PowerManager import androidx.core.app.NotificationManagerCompat +import androidx.core.content.getSystemService import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat import io.element.android.features.call.api.CallType @@ -49,6 +51,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf @RunWith(RobolectricTestRunner::class) class DefaultActiveCallManagerTest { @@ -57,10 +60,12 @@ class DefaultActiveCallManagerTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `registerIncomingCall - sets the incoming call as active`() = runTest { + setupShadowPowerManager() val notificationManagerCompat = mockk(relaxed = true) inCancellableScope { val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat) + assertThat(manager.activeWakeLock?.isHeld).isFalse() assertThat(manager.activeCall.value).isNull() val callNotificationData = aCallNotificationData() @@ -78,6 +83,7 @@ class DefaultActiveCallManagerTest { runCurrent() + assertThat(manager.activeWakeLock?.isHeld).isTrue() verify { notificationManagerCompat.notify(notificationId, any()) } } } @@ -128,6 +134,7 @@ class DefaultActiveCallManagerTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `incomingCallTimedOut - when there is an active call removes it and adds a missed call notification`() = runTest { + setupShadowPowerManager() val notificationManagerCompat = mockk(relaxed = true) val addMissedCallNotificationLambda = lambdaRecorder { _, _, _ -> } inCancellableScope { @@ -138,11 +145,13 @@ class DefaultActiveCallManagerTest { manager.registerIncomingCall(aCallNotificationData()) assertThat(manager.activeCall.value).isNotNull() + assertThat(manager.activeWakeLock?.isHeld).isTrue() manager.incomingCallTimedOut(displayMissedCallNotification = true) advanceTimeBy(1) assertThat(manager.activeCall.value).isNull() + assertThat(manager.activeWakeLock?.isHeld).isFalse() addMissedCallNotificationLambda.assertions().isCalledOnce() verify { notificationManagerCompat.cancel(notificationId) } } @@ -150,6 +159,7 @@ class DefaultActiveCallManagerTest { @Test fun `hungUpCall - removes existing call if the CallType matches`() = runTest { + setupShadowPowerManager() val notificationManagerCompat = mockk(relaxed = true) // Create a cancellable coroutine scope to cancel the test when needed inCancellableScope { @@ -158,9 +168,11 @@ class DefaultActiveCallManagerTest { val notificationData = aCallNotificationData() manager.registerIncomingCall(notificationData) assertThat(manager.activeCall.value).isNotNull() + assertThat(manager.activeWakeLock?.isHeld).isTrue() manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) assertThat(manager.activeCall.value).isNull() + assertThat(manager.activeWakeLock?.isHeld).isFalse() verify { notificationManagerCompat.cancel(notificationId) } } @@ -168,6 +180,7 @@ class DefaultActiveCallManagerTest { @Test fun `hungUpCall - does nothing if the CallType doesn't match`() = runTest { + setupShadowPowerManager() val notificationManagerCompat = mockk(relaxed = true) // Create a cancellable coroutine scope to cancel the test when needed inCancellableScope { @@ -175,9 +188,11 @@ class DefaultActiveCallManagerTest { manager.registerIncomingCall(aCallNotificationData()) assertThat(manager.activeCall.value).isNotNull() + assertThat(manager.activeWakeLock?.isHeld).isTrue() manager.hungUpCall(CallType.ExternalUrl("https://example.com")) assertThat(manager.activeCall.value).isNotNull() + assertThat(manager.activeWakeLock?.isHeld).isTrue() verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) } } @@ -284,12 +299,19 @@ class DefaultActiveCallManagerTest { } } + private fun setupShadowPowerManager() { + shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService()).apply { + setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true) + } + } + private fun CoroutineScope.createActiveCallManager( matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(), notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true), coroutineScope: CoroutineScope = this, ) = DefaultActiveCallManager( + context = InstrumentationRegistry.getInstrumentation().targetContext, coroutineScope = coroutineScope, onMissedCallNotificationHandler = onMissedCallNotificationHandler, ringingCallNotificationCreator = RingingCallNotificationCreator( diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt index 3024ce9f2f..90527370e7 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt @@ -11,6 +11,7 @@ import io.element.android.features.call.api.CallType import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.utils.ActiveCall import io.element.android.features.call.impl.utils.ActiveCallManager +import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.flow.MutableStateFlow class FakeActiveCallManager( @@ -20,15 +21,15 @@ class FakeActiveCallManager( ) : ActiveCallManager { override val activeCall = MutableStateFlow(null) - override fun registerIncomingCall(notificationData: CallNotificationData) { + override suspend fun registerIncomingCall(notificationData: CallNotificationData) = simulateLongTask { registerIncomingCallResult(notificationData) } - override fun hungUpCall(callType: CallType) { + override suspend fun hungUpCall(callType: CallType) = simulateLongTask { hungUpCallResult(callType) } - override fun joinedCall(callType: CallType) { + override suspend fun joinedCall(callType: CallType) = simulateLongTask { joinedCallResult(callType) } diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt index bbd990df27..09a1269259 100644 --- a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt +++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt @@ -30,7 +30,7 @@ class FakeElementCallEntryPoint( startCallResult(callType) } - override fun handleIncomingCall( + override suspend fun handleIncomingCall( callType: CallType.RoomCall, eventId: EventId, senderId: UserId, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt index 5f629d51eb..29b1525f51 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt @@ -39,7 +39,7 @@ class CreateRoomDataStore @Inject constructor( } val createRoomConfigWithInvites: Flow = combine( - selectedUserListDataStore.selectedUsers(), + selectedUserListDataStore.selectedUsers, createRoomConfigFlow, ) { selectedUsers, config -> config.copy(invites = selectedUsers.toImmutableList()) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index fd54b753d9..0025a64df3 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -66,7 +66,9 @@ class ConfigureRoomPresenter @Inject constructor( val cameraPermissionState = cameraPermissionPresenter.present() val createRoomConfig by dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig()) val homeserverName = remember { matrixClient.userIdServerName() } - val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false) + val isKnockFeatureEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) + }.collectAsState(initial = false) val roomAddressValidity = remember { mutableStateOf(RoomAddressValidity.Unknown) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index c05ed770f9..ea8be45e6b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -52,7 +52,9 @@ class CreateRoomRootPresenter @Inject constructor( val localCoroutineScope = rememberCoroutineScope() val startDmActionState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false) + val isRoomDirectorySearchEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch) + }.collectAsState(initial = false) fun handleEvents(event: CreateRoomRootEvents) { when (event) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt index ad9b18f624..32d5767cc6 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt @@ -54,7 +54,7 @@ class DefaultUserListPresenter @AssistedInject constructor( recentDirectRooms = matrixClient.getRecentDirectRooms() } var isSearchActive by rememberSaveable { mutableStateOf(false) } - val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList()) + val selectedUsers by userListDataStore.selectedUsers.collectAsState(emptyList()) var searchQuery by rememberSaveable { mutableStateOf("") } var searchResults: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.Initial()) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt index 3be9868c98..a500e3a05f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt @@ -8,22 +8,22 @@ package io.element.android.features.createroom.impl.userlist import io.element.android.libraries.matrix.api.user.MatrixUser -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject class UserListDataStore @Inject constructor() { - private val selectedUsers: MutableStateFlow> = MutableStateFlow(emptyList()) + private val _selectedUsers: MutableStateFlow> = MutableStateFlow(emptyList()) fun selectUser(user: MatrixUser) { - if (!selectedUsers.value.contains(user)) { - selectedUsers.tryEmit(selectedUsers.value.plus(user)) + if (!_selectedUsers.value.contains(user)) { + _selectedUsers.tryEmit(_selectedUsers.value.plus(user)) } } fun removeUserFromSelection(user: MatrixUser) { - selectedUsers.tryEmit(selectedUsers.value.minus(user)) + _selectedUsers.tryEmit(_selectedUsers.value.minus(user)) } - fun selectedUsers(): Flow> = selectedUsers + val selectedUsers = _selectedUsers.asStateFlow() } diff --git a/features/createroom/impl/src/main/res/values-eu/translations.xml b/features/createroom/impl/src/main/res/values-eu/translations.xml index 514c30e19d..43f67e429c 100644 --- a/features/createroom/impl/src/main/res/values-eu/translations.xml +++ b/features/createroom/impl/src/main/res/values-eu/translations.xml @@ -18,4 +18,6 @@ Gelaren ezarpenetan aldatu dezakezu hobespena." "Mintzagaia (aukerakoa)" "Gelen direktorioa" "Errorea gertatu da txata hasten saiatzean" + "Sartu…" + "Ez da gela aurkitu" diff --git a/features/createroom/impl/src/main/res/values-nb/translations.xml b/features/createroom/impl/src/main/res/values-nb/translations.xml index 98ea87e622..9b79740a97 100644 --- a/features/createroom/impl/src/main/res/values-nb/translations.xml +++ b/features/createroom/impl/src/main/res/values-nb/translations.xml @@ -18,4 +18,7 @@ Du kan endre dette når som helst i rominnstillingene." "Emne (valgfritt)" "Romkatalog" "Det oppstod en feil når du prøvde å starte en chat" + "Ikke en gyldig adresse" + "Rom ikke funnet" + "f.eks. #rom-navn:matrix.org" diff --git a/features/createroom/impl/src/main/res/values-pl/translations.xml b/features/createroom/impl/src/main/res/values-pl/translations.xml index 40d31e710d..3ed917b7bb 100644 --- a/features/createroom/impl/src/main/res/values-pl/translations.xml +++ b/features/createroom/impl/src/main/res/values-pl/translations.xml @@ -21,4 +21,10 @@ Możesz to zmienić w ustawieniach pokoju." "Temat (opcjonalnie)" "Katalog pokoi" "Wystąpił błąd podczas próby rozpoczęcia czatu" + "Dołącz do pokoju za pomocą adresu" + "Nieprawidłowy adres" + "Wprowadź…" + "Znaleziono pasujący pokój" + "Nie znaleziono pokoju" + "np. #room-name:matrix.org" diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 034701fec4..71a27dcaae 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -84,7 +84,7 @@ class FtueFlowNode @AssistedInject constructor( moveToNextStepIfNeeded() }) - analyticsService.didAskUserConsent() + analyticsService.didAskUserConsentFlow .distinctUntilChanged() .onEach { moveToNextStepIfNeeded() } .launchIn(lifecycleScope) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index 0409d73538..744053b976 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -66,7 +66,7 @@ class DefaultFtueService @Inject constructor( .onEach { updateState() } .launchIn(sessionCoroutineScope) - analyticsService.didAskUserConsent() + analyticsService.didAskUserConsentFlow .distinctUntilChanged() .onEach { updateState() } .launchIn(sessionCoroutineScope) @@ -118,7 +118,7 @@ class DefaultFtueService @Inject constructor( } private suspend fun needsAnalyticsOptIn(): Boolean { - return analyticsService.didAskUserConsent().first().not() + return analyticsService.didAskUserConsentFlow.first().not() } private suspend fun shouldAskNotificationPermissions(): Boolean { diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt new file mode 100644 index 0000000000..682970ffe7 --- /dev/null +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.invite.api + +import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.coroutines.flow.Flow + +interface SeenInvitesStore { + /** + * Returns a flow of seen room IDs of invitation. + */ + fun seenRoomIds(): Flow> + + /** + * Mark the invitation as seen. + * Call this when the invitation details are shown to the user. + * @param roomId the room ID of the invitation to mark as seen. + */ + suspend fun markAsSeen(roomId: RoomId) + + /** + * Mark the invitation as unseen. + * Call this when the invitation has been accepted or declined. + * @param roomId the room ID of the invitation to mark as unseen. + */ + suspend fun markAsUnSeen(roomId: RoomId) + + /** + * Delete the store. + */ + suspend fun clear() +} diff --git a/features/invite/impl/build.gradle.kts b/features/invite/impl/build.gradle.kts index 8c00ac3d23..7f052bea09 100644 --- a/features/invite/impl/build.gradle.kts +++ b/features/invite/impl/build.gradle.kts @@ -21,6 +21,7 @@ setupAnvil() dependencies { api(projects.features.invite.api) implementation(libs.androidx.datastore.preferences) + implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) @@ -35,6 +36,7 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.features.invite.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt new file mode 100644 index 0000000000..3accc163b0 --- /dev/null +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.invite.impl + +import android.content.Context +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringSetPreferencesKey +import androidx.datastore.preferences.preferencesDataStoreFile +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.invite.api.SeenInvitesStore +import io.element.android.libraries.androidutils.file.safeDelete +import io.element.android.libraries.androidutils.hash.hash +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder +import io.element.android.libraries.sessionstorage.api.observer.SessionListener +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +private val seenInvitesKey = stringSetPreferencesKey("seenInvites") + +@SingleIn(SessionScope::class) +@ContributesBinding(SessionScope::class) +class DefaultSeenInvitesStore @Inject constructor( + @ApplicationContext context: Context, + currentSessionIdHolder: CurrentSessionIdHolder, + @SessionCoroutineScope sessionCoroutineScope: CoroutineScope, + sessionObserver: SessionObserver, +) : SeenInvitesStore { + private val sessionId: SessionId = currentSessionIdHolder.current + + init { + sessionObserver.addListener(object : SessionListener { + override suspend fun onSessionCreated(userId: String) = Unit + override suspend fun onSessionDeleted(userId: String) { + if (sessionId.value == userId) { + clear() + } + } + }) + } + + private val dataStoreFile = sessionId.value.hash().take(16).let { hashedUserId -> + context.preferencesDataStoreFile("session_${hashedUserId}_seen-invites") + } + + private val store = PreferenceDataStoreFactory.create( + scope = sessionCoroutineScope, + migrations = emptyList(), + ) { + dataStoreFile + } + + override fun seenRoomIds(): Flow> = + store.data.map { prefs -> + prefs[seenInvitesKey] + .orEmpty() + .map { RoomId(it) } + .toSet() + } + + override suspend fun markAsSeen(roomId: RoomId) { + store.edit { prefs -> + prefs[seenInvitesKey] = prefs[seenInvitesKey].orEmpty() + roomId.value + } + } + + override suspend fun markAsUnSeen(roomId: RoomId) { + store.edit { prefs -> + prefs[seenInvitesKey] = prefs[seenInvitesKey].orEmpty() - roomId.value + } + } + + override suspend fun clear() { + dataStoreFile.safeDelete() + } +} diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt index 9f4a7ee848..e02642c32e 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import im.vector.app.features.analytics.plan.JoinedRoom +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.ConfirmingDeclineInvite @@ -34,6 +35,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( private val client: MatrixClient, private val joinRoom: JoinRoom, private val notificationCleaner: NotificationCleaner, + private val seenInvitesStore: SeenInvitesStore, ) : Presenter { @Composable override fun present(): AcceptDeclineInviteState { @@ -107,6 +109,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( ) .onSuccess { notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId) + seenInvitesStore.markAsUnSeen(roomId) } .map { roomId } } @@ -125,6 +128,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( client.ignoreUser(inviteData.senderId).getOrThrow() } notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId) + seenInvitesStore.markAsUnSeen(inviteData.roomId) inviteData.roomId }.runCatchingUpdatingState(declinedAction) } diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt index 870d3bba09..7feaff091e 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt @@ -9,9 +9,11 @@ package io.element.android.features.invite.impl.response import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.JoinedRoom +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.ConfirmingDeclineInvite import io.element.android.features.invite.api.response.InviteData +import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -20,6 +22,8 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_ID_3 import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID @@ -33,6 +37,7 @@ import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -54,7 +59,10 @@ class AcceptDeclineInvitePresenterTest { @Test fun `present - declining invite cancel flow`() = runTest { - val presenter = createAcceptDeclineInvitePresenter() + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) + val presenter = createAcceptDeclineInvitePresenter( + seenInvitesStore = seenInvitesStore, + ) presenter.test { val inviteData = anInviteData() awaitItem().also { state -> @@ -72,6 +80,7 @@ class AcceptDeclineInvitePresenterTest { assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -84,7 +93,11 @@ class AcceptDeclineInvitePresenterTest { Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure)) } ) - val presenter = createAcceptDeclineInvitePresenter(client = client) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) + val presenter = createAcceptDeclineInvitePresenter( + client = client, + seenInvitesStore = seenInvitesStore, + ) presenter.test { val inviteData = anInviteData() awaitItem().also { state -> @@ -111,6 +124,7 @@ class AcceptDeclineInvitePresenterTest { cancelAndConsumeRemainingEvents() } assert(declineInviteFailure).isCalledOnce() + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -129,9 +143,11 @@ class AcceptDeclineInvitePresenterTest { Result.success(FakeRoomPreview(declineInviteResult = declineInviteSuccess)) } ) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) val presenter = createAcceptDeclineInvitePresenter( client = client, notificationCleaner = fakeNotificationCleaner, + seenInvitesStore = seenInvitesStore, ) presenter.test { val inviteData = anInviteData() @@ -156,6 +172,7 @@ class AcceptDeclineInvitePresenterTest { clearMembershipNotificationForRoomLambda.assertions() .isCalledOnce() .with(value(A_SESSION_ID), value(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -174,9 +191,11 @@ class AcceptDeclineInvitePresenterTest { }, ignoreUserResult = ignoreUserSuccess ) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) val presenter = createAcceptDeclineInvitePresenter( client = client, notificationCleaner = fakeNotificationCleaner, + seenInvitesStore = seenInvitesStore, ) presenter.test { val inviteData = anInviteData() @@ -202,6 +221,7 @@ class AcceptDeclineInvitePresenterTest { clearMembershipNotificationForRoomLambda.assertions() .isCalledOnce() .with(value(A_SESSION_ID), value(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -214,7 +234,11 @@ class AcceptDeclineInvitePresenterTest { Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure)) } ) - val presenter = createAcceptDeclineInvitePresenter(client = client) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) + val presenter = createAcceptDeclineInvitePresenter( + client = client, + seenInvitesStore = seenInvitesStore, + ) presenter.test { val inviteData = anInviteData() awaitItem().also { state -> @@ -230,6 +254,7 @@ class AcceptDeclineInvitePresenterTest { } assertThat(awaitItem().declineAction.isLoading()).isTrue() } + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -237,7 +262,11 @@ class AcceptDeclineInvitePresenterTest { val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger -> Result.failure(RuntimeException("Failed to join room $roomIdOrAlias")) } - val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) + val presenter = createAcceptDeclineInvitePresenter( + joinRoomLambda = joinRoomFailure, + seenInvitesStore = seenInvitesStore, + ) presenter.test { val inviteData = anInviteData() awaitItem().also { state -> @@ -266,6 +295,7 @@ class AcceptDeclineInvitePresenterTest { value(emptyList()), value(JoinedRoom.Trigger.Invite) ) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -279,9 +309,11 @@ class AcceptDeclineInvitePresenterTest { val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger -> Result.success(Unit) } + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) val presenter = createAcceptDeclineInvitePresenter( joinRoomLambda = joinRoomSuccess, notificationCleaner = fakeNotificationCleaner, + seenInvitesStore = seenInvitesStore, ) presenter.test { val inviteData = anInviteData() @@ -308,6 +340,7 @@ class AcceptDeclineInvitePresenterTest { clearMembershipNotificationForRoomLambda.assertions() .isCalledOnce() .with(value(A_SESSION_ID), value(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3) } private fun anInviteData( @@ -330,11 +363,13 @@ class AcceptDeclineInvitePresenterTest { Result.success(Unit) }, notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), + seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), ): AcceptDeclineInvitePresenter { return AcceptDeclineInvitePresenter( client = client, joinRoom = FakeJoinRoom(joinRoomLambda), notificationCleaner = notificationCleaner, + seenInvitesStore = seenInvitesStore, ) } } diff --git a/features/invite/test/build.gradle.kts b/features/invite/test/build.gradle.kts new file mode 100644 index 0000000000..dc43ba00c3 --- /dev/null +++ b/features/invite/test/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 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.invite.test" +} + +dependencies { + implementation(libs.coroutines.core) + implementation(projects.libraries.matrix.api) + api(projects.features.invite.api) +} diff --git a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt new file mode 100644 index 0000000000..25db72532e --- /dev/null +++ b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.invite.test + +import io.element.android.features.invite.api.SeenInvitesStore +import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class InMemorySeenInvitesStore( + initialRoomIds: Set = emptySet(), +) : SeenInvitesStore { + private val roomIds = MutableStateFlow(initialRoomIds) + + override fun seenRoomIds(): Flow> = roomIds + + override suspend fun markAsSeen(roomId: RoomId) { + roomIds.value += roomId + } + + override suspend fun markAsUnSeen(roomId: RoomId) { + roomIds.value -= roomId + } + + override suspend fun clear() { + roomIds.value = emptySet() + } +} diff --git a/features/joinroom/impl/build.gradle.kts b/features/joinroom/impl/build.gradle.kts index 65635f1907..ecf3843674 100644 --- a/features/joinroom/impl/build.gradle.kts +++ b/features/joinroom/impl/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.features.invite.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) testImplementation(libs.androidx.compose.ui.test.junit) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 09ec3c9a5f..9914afbded 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -9,6 +9,7 @@ package io.element.android.features.joinroom.impl import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -22,6 +23,7 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.JoinedRoom +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData @@ -67,6 +69,7 @@ class JoinRoomPresenter @AssistedInject constructor( private val forgetRoom: ForgetRoom, private val acceptDeclineInvitePresenter: Presenter, private val buildMeta: BuildMeta, + private val seenInvitesStore: SeenInvitesStore, ) : Presenter { interface Factory { fun create( @@ -82,7 +85,9 @@ class JoinRoomPresenter @AssistedInject constructor( override fun present(): JoinRoomState { val coroutineScope = rememberCoroutineScope() var retryCount by remember { mutableIntStateOf(0) } - val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty()) + val roomInfo by remember { + matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()) + }.collectAsState(initial = Optional.empty()) val joinAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val knockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val cancelKnockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } @@ -147,6 +152,10 @@ class JoinRoomPresenter @AssistedInject constructor( } val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() + LaunchedEffect(contentState) { + contentState.markRoomInviteAsSeen() + } + fun handleEvents(event: JoinRoomEvents) { when (event) { JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction) @@ -234,6 +243,12 @@ class JoinRoomPresenter @AssistedInject constructor( forgetRoom.invoke(roomId) } } + + private suspend fun ContentState.markRoomInviteAsSeen() { + if ((this as? ContentState.Loaded)?.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited != null) { + seenInvitesStore.markAsSeen(roomId) + } + } } private fun RoomPreviewInfo.toContentState(senderMember: RoomMember?, reason: String?): ContentState { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index ff4cbbbc80..6a9bd559af 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides import im.vector.app.features.analytics.plan.JoinedRoom +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.joinroom.impl.JoinRoomPresenter import io.element.android.features.roomdirectory.api.RoomDescription @@ -35,6 +36,7 @@ object JoinRoomModule { forgetRoom: ForgetRoom, acceptDeclineInvitePresenter: Presenter, buildMeta: BuildMeta, + seenInvitesStore: SeenInvitesStore, ): JoinRoomPresenter.Factory { return object : JoinRoomPresenter.Factory { override fun create( @@ -57,6 +59,7 @@ object JoinRoomModule { cancelKnockRoom = cancelKnockRoom, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, buildMeta = buildMeta, + seenInvitesStore = seenInvitesStore, ) } } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 1c146285c2..b226d53706 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -9,9 +9,11 @@ package io.element.android.features.joinroom.impl import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.JoinedRoom +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.anAcceptDeclineInviteState +import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.features.joinroom.impl.di.CancelKnockRoom import io.element.android.features.joinroom.impl.di.ForgetRoom import io.element.android.features.joinroom.impl.di.KnockRoom @@ -52,6 +54,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -111,14 +114,19 @@ class JoinRoomPresenterTest { flowOf(Optional.of(roomSummary)) } } + val seenInvitesStore = InMemorySeenInvitesStore() val presenter = createJoinRoomPresenter( - matrixClient = matrixClient + matrixClient = matrixClient, + seenInvitesStore = seenInvitesStore, ) + assertThat(seenInvitesStore.seenRoomIds().first()).isEmpty() presenter.test { skipItems(1) awaitItem().also { state -> assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsInvited(null)) } + // Check that the roomId is stored in the seen invites store + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(roomSummary.roomId) } } @@ -759,7 +767,8 @@ class JoinRoomPresenterTest { cancelKnockRoom: CancelKnockRoom = FakeCancelKnockRoom(), forgetRoom: ForgetRoom = FakeForgetRoom(), buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"), - acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() } + acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, + seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), ): JoinRoomPresenter { return JoinRoomPresenter( roomId = roomId, @@ -773,7 +782,8 @@ class JoinRoomPresenterTest { cancelKnockRoom = cancelKnockRoom, forgetRoom = forgetRoom, buildMeta = buildMeta, - acceptDeclineInvitePresenter = acceptDeclineInvitePresenter + acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, + seenInvitesStore = seenInvitesStore, ) } diff --git a/features/knockrequests/impl/src/main/res/values-el/translations.xml b/features/knockrequests/impl/src/main/res/values-el/translations.xml index 326227df48..d07b289382 100644 --- a/features/knockrequests/impl/src/main/res/values-el/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-el/translations.xml @@ -4,7 +4,10 @@ "Σίγουρα θες να αποδεχτείς όλα τα αιτήματα συμμετοχής;" "Αποδοχή όλων των αιτημάτων" "Αποδοχή όλων" + "Δεν μπορέσαμε να δεχτούμε όλα τα αιτήματα. Θες να προσπαθήσεις ξανά;" + "Αποτυχία αποδοχής όλων των αιτημάτων" "Αποδοχή όλων των αιτημάτων συμμετοχής" + "Αποτυχία αποδοχής αιτήματος" "Γίνεται αποδοχή αιτήματος συμμετοχής" "Ναι, απόρριψη και αποκλεισμός" "Σίγουρα θες να απορρίψειε και να αποκλείσεις τον χρήστη %1$s; Αυτός ο χρήστης δεν θα μπορεί να ζητήσει πρόσβαση για να συμμετάσχει ξανά σε αυτό το δωμάτιο." @@ -17,6 +20,7 @@ "Γίνεται απόρριψη αιτήματος συμμετοχής" "Όταν κάποιος θα ζητήσει να συμμετάσχει στο δωμάτιο, θα μπορείς να δεις το αίτημά του εδώ." "Δεν υπάρχει εκκρεμές αίτημα συμμετοχής" + "Φόρτωση αιτημάτων συμμετοχής…" "Αιτήματα συμμετοχής" "Οι χρήστες %1$s +%2$d ακόμη θέλουν να συμμετάσχουν σε αυτό το δωμάτιο" diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt index 109878cffa..61e4ace265 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt @@ -7,7 +7,6 @@ package io.element.android.features.licenses.impl.list -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -15,7 +14,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.OutlinedTextField import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,10 +31,11 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun DependencyLicensesListView( state: DependencyLicensesListState, @@ -60,7 +59,7 @@ fun DependencyLicensesListView( ) { if (state.licenses.isSuccess()) { // Search field - OutlinedTextField( + TextField( value = state.filter, onValueChange = { state.eventSink(DependencyLicensesListEvent.SetFilter(it)) }, leadingIcon = { diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index 909a3c978a..d299887165 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -6,6 +6,7 @@ */ import config.BuildTimeConfig +import extension.buildConfigFieldStr import extension.readLocalProperty plugins { @@ -16,10 +17,17 @@ plugins { android { namespace = "io.element.android.features.location.api" + buildFeatures { + buildConfig = true + } + defaultConfig { - resValue( - type = "string", - name = "maptiler_api_key", + buildConfigFieldStr( + name = "MAPTILER_BASE_URL", + value = BuildTimeConfig.SERVICES_MAPTILER_BASE_URL ?: "https://api.maptiler.com/maps" + ) + buildConfigFieldStr( + name = "MAPTILER_API_KEY", value = if (isEnterpriseBuild) { BuildTimeConfig.SERVICES_MAPTILER_APIKEY } else { @@ -28,9 +36,8 @@ android { } ?: "" ) - resValue( - type = "string", - name = "maptiler_light_map_id", + buildConfigFieldStr( + name = "MAPTILER_LIGHT_MAP_ID", value = if (isEnterpriseBuild) { BuildTimeConfig.SERVICES_MAPTILER_LIGHT_MAPID } else { @@ -40,9 +47,8 @@ android { // fall back to maptiler's default light map. ?: "basic-v2" ) - resValue( - type = "string", - name = "maptiler_dark_map_id", + buildConfigFieldStr( + name = "MAPTILER_DARK_MAP_ID", value = if (isEnterpriseBuild) { BuildTimeConfig.SERVICES_MAPTILER_DARK_MAPID } else { diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index e80386a9c5..382a65ae47 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -57,7 +57,7 @@ fun StaticMapView( ) { val context = LocalContext.current var retryHash by remember { mutableIntStateOf(0) } - val builder = remember { StaticMapUrlBuilder(context) } + val builder = remember { StaticMapUrlBuilder() } val painter = rememberAsyncImagePainter( model = if (constraints.isZero) { // Avoid building a URL if any of the size constraints is zero (else it will thrown an exception). diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt deleted file mode 100644 index 9580280d74..0000000000 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.location.api.internal - -import android.content.Context -import io.element.android.features.location.api.R - -internal const val MAPTILER_BASE_URL = "https://api.maptiler.com/maps" - -internal fun Context.mapId(darkMode: Boolean) = when (darkMode) { - true -> getString(R.string.maptiler_dark_map_id) - false -> getString(R.string.maptiler_light_map_id) -} - -internal val Context.apiKey: String - get() = getString(R.string.maptiler_api_key) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt index 20c0466e13..eb8d0ea1b7 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt @@ -7,7 +7,7 @@ package io.element.android.features.location.api.internal -import android.content.Context +import io.element.android.features.location.api.BuildConfig import kotlin.math.roundToInt /** @@ -16,14 +16,16 @@ import kotlin.math.roundToInt * https://docs.maptiler.com/cloud/api/static-maps/ */ internal class MapTilerStaticMapUrlBuilder( + private val baseUrl: String, private val apiKey: String, private val lightMapId: String, private val darkMapId: String, ) : StaticMapUrlBuilder { - constructor(context: Context) : this( - apiKey = context.apiKey, - lightMapId = context.mapId(darkMode = false), - darkMapId = context.mapId(darkMode = true), + constructor() : this( + baseUrl = BuildConfig.MAPTILER_BASE_URL.removeSuffix("/"), + apiKey = BuildConfig.MAPTILER_API_KEY, + lightMapId = BuildConfig.MAPTILER_LIGHT_MAP_ID, + darkMapId = BuildConfig.MAPTILER_DARK_MAP_ID, ) override fun build( @@ -55,7 +57,7 @@ internal class MapTilerStaticMapUrlBuilder( // image smaller than the available space in pixels. // The resulting image will have to be scaled to fit the available space in order // to keep the perceived content size constant at the expense of sharpness. - return "$MAPTILER_BASE_URL/$mapId/static/$lon,$lat,$finalZoom/${finalWidth}x${finalHeight}$scale.webp?key=$apiKey&attribution=bottomleft" + return "$baseUrl/$mapId/static/$lon,$lat,$finalZoom/${finalWidth}x${finalHeight}$scale.webp?key=$apiKey&attribution=bottomleft" } override fun isServiceAvailable() = apiKey.isNotEmpty() diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt index 3146d589e4..db75b5e30a 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt @@ -9,21 +9,23 @@ package io.element.android.features.location.api.internal -import android.content.Context +import io.element.android.features.location.api.BuildConfig internal class MapTilerTileServerStyleUriBuilder( + private val baseUrl: String, private val apiKey: String, private val lightMapId: String, private val darkMapId: String, ) : TileServerStyleUriBuilder { - constructor(context: Context) : this( - apiKey = context.apiKey, - lightMapId = context.mapId(darkMode = false), - darkMapId = context.mapId(darkMode = true), + constructor() : this( + baseUrl = BuildConfig.MAPTILER_BASE_URL.removeSuffix("/"), + apiKey = BuildConfig.MAPTILER_API_KEY, + lightMapId = BuildConfig.MAPTILER_LIGHT_MAP_ID, + darkMapId = BuildConfig.MAPTILER_DARK_MAP_ID, ) override fun build(darkMode: Boolean): String { val mapId = if (darkMode) darkMapId else lightMapId - return "$MAPTILER_BASE_URL/$mapId/style.json?key=$apiKey" + return "$baseUrl/$mapId/style.json?key=$apiKey" } } diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt index e52205c2f3..533ecf8d2d 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt @@ -7,8 +7,6 @@ package io.element.android.features.location.api.internal -import android.content.Context - /** * Builds an URL for a 3rd party service provider static maps API. */ @@ -26,4 +24,4 @@ interface StaticMapUrlBuilder { fun isServiceAvailable(): Boolean } -fun StaticMapUrlBuilder(context: Context): StaticMapUrlBuilder = MapTilerStaticMapUrlBuilder(context = context) +fun StaticMapUrlBuilder(): StaticMapUrlBuilder = MapTilerStaticMapUrlBuilder() diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt index 4d93a8d8bb..051b2448d4 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt @@ -7,10 +7,8 @@ package io.element.android.features.location.api.internal -import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext import io.element.android.compound.theme.ElementTheme /** @@ -24,7 +22,7 @@ interface TileServerStyleUriBuilder { ): String } -fun TileServerStyleUriBuilder(context: Context): TileServerStyleUriBuilder = MapTilerTileServerStyleUriBuilder(context = context) +fun TileServerStyleUriBuilder(): TileServerStyleUriBuilder = MapTilerTileServerStyleUriBuilder() /** * Provides and remembers a style URI for a MapLibre compatible tile server. @@ -33,9 +31,8 @@ fun TileServerStyleUriBuilder(context: Context): TileServerStyleUriBuilder = Map */ @Composable fun rememberTileStyleUrl(): String { - val context = LocalContext.current val darkMode = !ElementTheme.isLightTheme return remember(darkMode) { - TileServerStyleUriBuilder(context).build(darkMode) + TileServerStyleUriBuilder().build(darkMode) } } diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt index b74cebf1e7..ba44426a70 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt @@ -12,6 +12,7 @@ import org.junit.Test class MapTilerStaticMapUrlBuilderTest { private val builder = MapTilerStaticMapUrlBuilder( + baseUrl = "https://base.url", apiKey = "anApiKey", lightMapId = "aLightMapId", darkMapId = "aDarkMapId", @@ -25,6 +26,7 @@ class MapTilerStaticMapUrlBuilderTest { @Test fun `isServiceAvailable returns false if api key is empty`() { val builderWithoutKey = MapTilerStaticMapUrlBuilder( + baseUrl = "https://base.url", apiKey = "", lightMapId = "aLightMapId", darkMapId = "aDarkMapId", @@ -44,7 +46,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 600, density = 1f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") } @Test @@ -59,7 +61,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 900, density = 1.5f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") } @Test @@ -74,7 +76,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 1200, density = 2f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") } @Test @@ -89,7 +91,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 1800, density = 3f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") } @Test @@ -104,7 +106,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 2048, density = 1f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/2048x1024.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/2048x1024.webp?key=anApiKey&attribution=bottomleft") assertThat( builder.build( @@ -116,7 +118,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 4096, density = 1f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x2048.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/1024x2048.webp?key=anApiKey&attribution=bottomleft") assertThat( builder.build( @@ -128,7 +130,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 2048, density = 2f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x512@2x.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/1024x512@2x.webp?key=anApiKey&attribution=bottomleft") assertThat( builder.build( @@ -140,7 +142,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 4096, density = 2f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/512x1024@2x.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/512x1024@2x.webp?key=anApiKey&attribution=bottomleft") assertThat( builder.build( @@ -152,7 +154,7 @@ class MapTilerStaticMapUrlBuilderTest { height = Int.MAX_VALUE, density = 2f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x1024@2x.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/1024x1024@2x.webp?key=anApiKey&attribution=bottomleft") } @Test @@ -167,7 +169,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 0, density = 1f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") assertThat( builder.build( @@ -179,7 +181,7 @@ class MapTilerStaticMapUrlBuilderTest { height = 0, density = 2f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0@2x.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/0x0@2x.webp?key=anApiKey&attribution=bottomleft") assertThat( builder.build( @@ -191,6 +193,6 @@ class MapTilerStaticMapUrlBuilderTest { height = Int.MIN_VALUE, density = 1f, ) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") + ).isEqualTo("https://base.url/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") } } diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt index f65c044540..dc53765836 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt @@ -12,6 +12,7 @@ import org.junit.Test class MapTilerTileServerStyleUriBuilderTest { private val builder = MapTilerTileServerStyleUriBuilder( + baseUrl = "https://base.url", apiKey = "anApiKey", lightMapId = "aLightMapId", darkMapId = "aDarkMapId", @@ -21,13 +22,13 @@ class MapTilerTileServerStyleUriBuilderTest { fun `light map uri`() { assertThat( builder.build(darkMode = false) - ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/style.json?key=anApiKey") + ).isEqualTo("https://base.url/aLightMapId/style.json?key=anApiKey") } @Test fun `dark map uri`() { assertThat( builder.build(darkMode = true) - ).isEqualTo("https://api.maptiler.com/maps/aDarkMapId/style.json?key=anApiKey") + ).isEqualTo("https://base.url/aDarkMapId/style.json?key=anApiKey") } } diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index 8a47e77b1a..4c62bdff1a 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -49,7 +49,6 @@ dependencies { testImplementation(projects.libraries.testtags) testImplementation(projects.services.analytics.test) testImplementation(projects.features.messages.test) - testImplementation(projects.services.toolbox.test) testImplementation(projects.tests.testutils) testImplementation(libs.androidx.compose.ui.test.junit) testReleaseImplementation(libs.androidx.compose.ui.test.manifest) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt index 1a9359b301..e662ef8115 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt @@ -8,17 +8,14 @@ package io.element.android.features.location.impl import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.location.api.BuildConfig import io.element.android.features.location.api.LocationService -import io.element.android.features.location.api.R import io.element.android.libraries.di.AppScope -import io.element.android.services.toolbox.api.strings.StringProvider import javax.inject.Inject @ContributesBinding(AppScope::class) -class DefaultLocationService @Inject constructor( - private val stringProvider: StringProvider, -) : LocationService { +class DefaultLocationService @Inject constructor() : LocationService { override fun isServiceAvailable(): Boolean { - return stringProvider.getString(R.string.maptiler_api_key).isNotEmpty() + return BuildConfig.MAPTILER_API_KEY.isNotEmpty() } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt index b059a88dbb..304f8eb1aa 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt @@ -11,12 +11,14 @@ import android.content.Context import android.content.Intent import android.net.Uri import androidx.annotation.VisibleForTesting +import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.location.api.Location import io.element.android.libraries.androidutils.system.openAppSettingsPage import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import timber.log.Timber +import java.util.Locale import javax.inject.Inject @ContributesBinding(AppScope::class) @@ -25,7 +27,7 @@ class AndroidLocationActions @Inject constructor( ) : LocationActions { override fun share(location: Location, label: String?) { runCatching { - val uri = Uri.parse(buildUrl(location, label)) + val uri = buildUrl(location, label).toUri() val showMapsIntent = Intent(Intent.ACTION_VIEW).setData(uri) val chooserIntent = Intent.createChooser(showMapsIntent, null) chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -42,17 +44,14 @@ class AndroidLocationActions @Inject constructor( } } +// Ref: https://developer.android.com/guide/components/intents-common#ViewMap @VisibleForTesting internal fun buildUrl( location: Location, label: String?, urlEncoder: (String) -> String = Uri::encode ): String { - // Ref: https://developer.android.com/guide/components/intents-common#ViewMap - val base = "geo:0,0?q=%.6f,%.6f".format(location.lat, location.lon) - return if (label == null) { - base - } else { - "%s (%s)".format(base, urlEncoder(label)) - } + // This is needed so the coordinates are formatted with a dot as decimal separator + val locale = Locale.ENGLISH + return "geo:0,0?q=%.6f,%.6f (%s)".format(locale, location.lat, location.lon, urlEncoder(label.orEmpty())) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt index 213ce52ac5..fd687dd938 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt @@ -8,30 +8,15 @@ package io.element.android.features.location.impl import com.google.common.truth.Truth.assertThat -import io.element.android.features.location.api.R -import io.element.android.services.toolbox.test.strings.FakeStringProvider +import io.element.android.features.location.api.BuildConfig import org.junit.Test class DefaultLocationServiceTest { @Test - fun `if apiKey is empty, isServiceAvailable should return false`() { - val fakeStringProvider = FakeStringProvider( - defaultResult = "" + fun `isServiceAvailable should return value depending on BuildConfig MAPTILER_API_KEY`() { + val locationService = DefaultLocationService() + assertThat(locationService.isServiceAvailable()).isEqualTo( + BuildConfig.MAPTILER_API_KEY.isNotEmpty() ) - val locationService = DefaultLocationService( - stringProvider = fakeStringProvider, - ) - assertThat(locationService.isServiceAvailable()).isFalse() - assertThat(fakeStringProvider.lastResIdParam).isEqualTo(R.string.maptiler_api_key) - } - - @Test - fun `if apiKey is not empty, isServiceAvailable should return true`() { - val locationService = DefaultLocationService( - stringProvider = FakeStringProvider( - defaultResult = "aKey" - ) - ) - assertThat(locationService.isServiceAvailable()).isTrue() } } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt index 4a98e22167..5b584a39f8 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt @@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.location.api.Location import org.junit.Test import java.net.URLEncoder +import java.util.Locale internal class AndroidLocationActionsTest { // We use an Android-native encoder in the actual app, switch to an equivalent JVM one for the tests @@ -25,7 +26,7 @@ internal class AndroidLocationActionsTest { ) val actual = buildUrl(location, null, ::urlEncoder) - val expected = "geo:0,0?q=1.234568,123.456789" + val expected = "geo:0,0?q=1.234568,123.456789 ()" assertThat(actual).isEqualTo(expected) } @@ -57,4 +58,20 @@ internal class AndroidLocationActionsTest { assertThat(actual).isEqualTo(expected) } + + @Test + fun `buildUrl - URL encodes coordinates in locale with comma decimal separator`() { + val location = Location( + lat = 1.000001, + lon = 2.000001, + accuracy = 0f + ) + // Set a locale with comma as decimal separator + Locale.setDefault(Locale.Category.FORMAT, Locale("pt", "BR")) + + val actual = buildUrl(location, "(weird/stuff here)", ::urlEncoder) + val expected = "geo:0,0?q=1.000001,2.000001 (%28weird%2Fstuff+here%29)" + + assertThat(actual).isEqualTo(expected) + } } diff --git a/features/lockscreen/impl/build.gradle.kts b/features/lockscreen/impl/build.gradle.kts index 808abf9150..ad77b20e60 100644 --- a/features/lockscreen/impl/build.gradle.kts +++ b/features/lockscreen/impl/build.gradle.kts @@ -14,6 +14,10 @@ plugins { android { namespace = "io.element.android.features.lockscreen.impl" + + testOptions { + unitTests.isIncludeAndroidResources = true + } } setupAnvil() @@ -30,6 +34,8 @@ dependencies { implementation(projects.libraries.featureflag.api) implementation(projects.libraries.cryptography.api) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.testtags) + implementation(projects.libraries.uiUtils) implementation(projects.features.logout.api) implementation(projects.libraries.uiStrings) implementation(projects.libraries.sessionStorage.api) @@ -42,6 +48,9 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(libs.test.robolectric) + testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(libs.androidx.test.ext.junit) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) testImplementation(projects.libraries.cryptography.test) @@ -50,4 +59,5 @@ dependencies { testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.services.appnavstate.test) testImplementation(projects.features.logout.test) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt index 62de1f902d..5c2b1fdd53 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt @@ -87,7 +87,9 @@ class DefaultBiometricAuthenticatorManager @Inject constructor( @Composable override fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator { - val isBiometricAllowed by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false) + val isBiometricAllowed by remember { + lockScreenStore.isBiometricUnlockAllowed() + }.collectAsState(initial = false) val lifecycleState by LocalLifecycleOwner.current.lifecycle.currentStateFlow.collectAsState() val isAvailable by remember(lifecycleState) { derivedStateOf { isBiometricAllowed && hasAvailableAuthenticator } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt index f9a2cd8767..97dd875b01 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt @@ -38,7 +38,9 @@ class LockScreenSettingsPresenter @Inject constructor( value = !lockScreenConfig.isPinMandatory && hasPinCode } } - val isBiometricEnabled by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false) + val isBiometricEnabled by remember { + lockScreenStore.isBiometricUnlockAllowed() + }.collectAsState(initial = false) var showRemovePinConfirmation by remember { mutableStateOf(false) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt index 596c6b1b59..820b557423 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt @@ -27,6 +27,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.coerceIn import androidx.compose.ui.unit.dp @@ -37,6 +43,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.libraries.ui.utils.time.digit import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -60,7 +68,22 @@ fun PinKeypad( val horizontalArrangement = spacedBy(spaceBetweenPinKey, Alignment.CenterHorizontally) val verticalArrangement = spacedBy(spaceBetweenPinKey, Alignment.CenterVertically) Column( - modifier = modifier, + modifier = modifier.onKeyEvent { event -> + if (event.type == KeyEventType.KeyUp) { + val digitChar = event.digit + if (digitChar != null) { + onClick(PinKeypadModel.Number(digitChar)) + true + } else if (event.key == Key.Backspace) { + onClick(PinKeypadModel.Back) + true + } else { + false + } + } else { + false + } + }, verticalArrangement = verticalArrangement, horizontalAlignment = horizontalAlignment, ) { @@ -183,7 +206,7 @@ private fun PinKeypadBackButton( ) { Icon( imageVector = Icons.AutoMirrored.Filled.Backspace, - contentDescription = null, + contentDescription = stringResource(CommonStrings.a11y_delete), ) } } diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt new file mode 100644 index 0000000000..e51b007312 --- /dev/null +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.lockscreen.impl.unlock.keypad + +import android.view.KeyEvent +import androidx.activity.ComponentActivity +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasContentDescription +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isRoot +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performKeyInput +import androidx.compose.ui.test.pressKey +import androidx.compose.ui.test.requestFocus +import androidx.compose.ui.unit.dp +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.EventsRecorder +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class PinKeypadTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on a number emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setPinKeyPad(onClick = eventsRecorder) + rule.onNode(hasText("1")).performClick() + eventsRecorder.assertSingle(PinKeypadModel.Number('1')) + } + + @Test + fun `clicking on the delete previous character button emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setPinKeyPad(onClick = eventsRecorder) + rule.onNode(hasContentDescription(rule.activity.getString(CommonStrings.a11y_delete))).performClick() + eventsRecorder.assertSingle(PinKeypadModel.Back) + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun `typing using the hardware keyboard emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setPinKeyPad(onClick = eventsRecorder) + rule.onNodeWithText("1").requestFocus() + rule.onAllNodes(isRoot())[0].performKeyInput { + val keys = listOf( + Key.A, + Key.NumPad1, + Key.NumPad2, + Key.NumPad3, + Key.NumPad4, + Key.NumPad5, + Key.NumPad6, + Key.NumPad7, + Key.NumPad8, + Key.NumPad9, + Key.NumPad0, + Key(KeyEvent.KEYCODE_1), + Key(KeyEvent.KEYCODE_2), + Key(KeyEvent.KEYCODE_3), + Key(KeyEvent.KEYCODE_4), + Key(KeyEvent.KEYCODE_5), + Key(KeyEvent.KEYCODE_6), + Key(KeyEvent.KEYCODE_7), + Key(KeyEvent.KEYCODE_8), + Key(KeyEvent.KEYCODE_9), + Key(KeyEvent.KEYCODE_0), + Key.Backspace, + ) + for (key in keys) { + pressKey(key) + } + } + eventsRecorder.assertList( + listOf( + // Note that the first key is not a number, but a letter so it's ignored as input + // Then we have the numpad keys + PinKeypadModel.Number('1'), + PinKeypadModel.Number('2'), + PinKeypadModel.Number('3'), + PinKeypadModel.Number('4'), + PinKeypadModel.Number('5'), + PinKeypadModel.Number('6'), + PinKeypadModel.Number('7'), + PinKeypadModel.Number('8'), + PinKeypadModel.Number('9'), + PinKeypadModel.Number('0'), + // And the normal keys from the number row in the keyboard + PinKeypadModel.Number('1'), + PinKeypadModel.Number('2'), + PinKeypadModel.Number('3'), + PinKeypadModel.Number('4'), + PinKeypadModel.Number('5'), + PinKeypadModel.Number('6'), + PinKeypadModel.Number('7'), + PinKeypadModel.Number('8'), + PinKeypadModel.Number('9'), + PinKeypadModel.Number('0'), + PinKeypadModel.Back, + ) + ) + } + + private fun AndroidComposeTestRule.setPinKeyPad( + onClick: (PinKeypadModel) -> Unit = EnsureNeverCalledWithParam(), + ) { + setContent { + PinKeypad( + onClick = onClick, + maxWidth = 1000.dp, + maxHeight = 1000.dp, + ) + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index 3f9c368a1d..3e1fc1853c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -33,9 +33,7 @@ class AccountProviderDataSource @Inject constructor( defaultAccountProvider ) - fun flow(): StateFlow { - return accountProvider.asStateFlow() - } + val flow: StateFlow = accountProvider.asStateFlow() fun reset() { accountProvider.tryEmit(defaultAccountProvider) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index eb5a8fee67..99b029a928 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -52,7 +52,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( @Composable override fun present(): ConfirmAccountProviderState { - val accountProvider by accountProviderDataSource.flow().collectAsState() + val accountProvider by accountProviderDataSource.flow.collectAsState() val localCoroutineScope = rememberCoroutineScope() val loginFlowAction: MutableState> = remember { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt index 027036059d..c937cf9d48 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt @@ -30,7 +30,7 @@ class DefaultMessageParser @Inject constructor( val parser = Json { ignoreUnknownKeys = true } val response = parser.decodeFromString(MobileRegistrationResponse.serializer(), message) val userId = response.userId ?: error("No user ID in response") - val homeServer = response.homeServer ?: accountProviderDataSource.flow().value.url + val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url val accessToken = response.accessToken ?: error("No access token in response") val deviceId = response.deviceId ?: error("No device ID in response") return ExternalSession( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index ea60dc66f0..3a12715f6e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -40,7 +40,7 @@ class LoginPasswordPresenter @Inject constructor( val formState = rememberSaveable { mutableStateOf(LoginFormState.Default) } - val accountProvider by accountProviderDataSource.flow().collectAsState() + val accountProvider by accountProviderDataSource.flow.collectAsState() fun handleEvents(event: LoginPasswordEvents) { when (event) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt index b0570cee93..e9a8595f3b 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt @@ -23,7 +23,7 @@ class AccountProviderDataSourceTest { @Test fun `present - initial state`() = runTest { val sut = AccountProviderDataSource(FakeEnterpriseService()) - sut.flow().test { + sut.flow.test { val initialState = awaitItem() assertThat(initialState).isEqualTo( AccountProvider( @@ -43,7 +43,7 @@ class AccountProviderDataSourceTest { val sut = AccountProviderDataSource(FakeEnterpriseService( defaultHomeserverResult = { AuthenticationConfig.MATRIX_ORG_URL } )) - sut.flow().test { + sut.flow.test { val initialState = awaitItem() assertThat(initialState).isEqualTo( AccountProvider( @@ -61,7 +61,7 @@ class AccountProviderDataSourceTest { @Test fun `present - user change and reset`() = runTest { val sut = AccountProviderDataSource(FakeEnterpriseService()) - sut.flow().test { + sut.flow.test { val initialState = awaitItem() assertThat(initialState.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER) sut.userSelection(AccountProvider(url = "https://example.com")) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt index c26f3a7407..3d1cacd89c 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt @@ -15,6 +15,7 @@ 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 io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -25,6 +26,7 @@ import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -44,6 +46,16 @@ class LogoutPresenter @Inject constructor( } .collectAsState(initial = BackupUploadState.Unknown) + var waitingForALongTime by remember { mutableStateOf(false) } + LaunchedEffect(backupUploadState) { + if (backupUploadState is BackupUploadState.Waiting) { + delay(2_000) + waitingForALongTime = true + } else { + waitingForALongTime = false + } + } + val isLastDevice by encryptionService.isLastDevice.collectAsState() val backupState by encryptionService.backupStateStateFlow.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() @@ -79,6 +91,7 @@ class LogoutPresenter @Inject constructor( doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(), recoveryState = recoveryState, backupUploadState = backupUploadState, + waitingForALongTime = waitingForALongTime, logoutAction = logoutAction.value, eventSink = ::handleEvents ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt index 2f84c837c6..584d7a0ac9 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt @@ -18,6 +18,7 @@ data class LogoutState( val doesBackupExistOnServer: Boolean, val recoveryState: RecoveryState, val backupUploadState: BackupUploadState, + val waitingForALongTime: Boolean, val logoutAction: AsyncAction, val eventSink: (LogoutEvents) -> Unit, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt index 12e83fc713..a55a9afb96 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt @@ -29,6 +29,15 @@ open class LogoutStateProvider : PreviewParameterProvider { aLogoutState(isLastDevice = true, recoveryState = RecoveryState.DISABLED), // Last session no backup aLogoutState(isLastDevice = true, backupState = BackupState.UNKNOWN, doesBackupExistOnServer = false), + aLogoutState( + isLastDevice = false, + backupUploadState = BackupUploadState.Waiting, + ), + aLogoutState( + isLastDevice = false, + backupUploadState = BackupUploadState.Waiting, + waitingForALongTime = true, + ), ) } @@ -38,6 +47,7 @@ fun aLogoutState( doesBackupExistOnServer: Boolean = true, recoveryState: RecoveryState = RecoveryState.ENABLED, backupUploadState: BackupUploadState = BackupUploadState.Unknown, + waitingForALongTime: Boolean = false, logoutAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (LogoutEvents) -> Unit = {}, ) = LogoutState( @@ -46,6 +56,7 @@ fun aLogoutState( doesBackupExistOnServer = doesBackupExistOnServer, recoveryState = recoveryState, backupUploadState = backupUploadState, + waitingForALongTime = waitingForALongTime, logoutAction = logoutAction, eventSink = eventSink, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt index f8e5a428c2..321879b009 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt @@ -143,24 +143,41 @@ private fun ColumnScope.Buttons( @Composable private fun Content( state: LogoutState, + modifier: Modifier = Modifier, ) { - if (state.backupUploadState is BackupUploadState.Uploading) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 60.dp, start = 20.dp, end = 20.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth(), - progress = { state.backupUploadState.backedUpCount.toFloat() / state.backupUploadState.totalCount.toFloat() }, - trackColor = ElementTheme.colors.progressIndicatorTrackColor, - ) - Text( - modifier = Modifier.align(Alignment.End), - text = "${state.backupUploadState.backedUpCount} / ${state.backupUploadState.totalCount}", - style = ElementTheme.typography.fontBodySmRegular, - ) + Column( + modifier = modifier + .fillMaxWidth() + .padding(top = 60.dp, start = 20.dp, end = 20.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + when (state.backupUploadState) { + is BackupUploadState.Uploading -> { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = { state.backupUploadState.backedUpCount.toFloat() / state.backupUploadState.totalCount.toFloat() }, + trackColor = ElementTheme.colors.progressIndicatorTrackColor, + ) + Text( + modifier = Modifier.align(Alignment.End), + text = "${state.backupUploadState.backedUpCount} / ${state.backupUploadState.totalCount}", + style = ElementTheme.typography.fontBodySmRegular, + ) + } + BackupUploadState.Waiting -> { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + trackColor = ElementTheme.colors.progressIndicatorTrackColor, + ) + if (state.waitingForALongTime) { + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = stringResource(CommonStrings.common_please_check_internet_connection), + style = ElementTheme.typography.fontBodySmRegular, + ) + } + } + else -> Unit } } } diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index d054556f87..61a1f1371d 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -44,6 +44,7 @@ class LogoutPresenterTest { assertThat(initialState.doesBackupExistOnServer).isTrue() assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN) assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) + assertThat(initialState.waitingForALongTime).isFalse() assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -66,6 +67,34 @@ class LogoutPresenterTest { } } + @Test + fun `present - initial state - waiting a long time`() = runTest { + val encryptionService = FakeEncryptionService() + encryptionService.givenWaitForBackupUploadSteadyStateFlow( + flow { + emit(BackupUploadState.Waiting) + delay(3_000) + } + ) + val presenter = createLogoutPresenter( + encryptionService = encryptionService + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.waitingForALongTime).isFalse() + assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) + val waitingState = awaitItem() + assertThat(waitingState.backupUploadState).isEqualTo(BackupUploadState.Waiting) + assertThat(initialState.waitingForALongTime).isFalse() + skipItems(1) + val waitingALongTimeState = awaitItem() + assertThat(waitingALongTimeState.backupUploadState).isEqualTo(BackupUploadState.Waiting) + assertThat(waitingALongTimeState.waitingForALongTime).isTrue() + } + } + @Test fun `present - initial state - backing up`() = runTest { val encryptionService = FakeEncryptionService() diff --git a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt index 5071613e58..607283792f 100644 --- a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt +++ b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt @@ -8,12 +8,11 @@ package io.element.android.features.messages.api.timeline import androidx.compose.runtime.Composable -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.wysiwyg.utils.HtmlConverter interface HtmlConverterProvider { @Composable - fun Update(currentUserId: UserId) + fun Update() fun provide(): HtmlConverter } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 09bc3a287f..6feaa23b62 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -57,6 +57,7 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.Overlay import io.element.android.libraries.architecture.overlay.operation.hide import io.element.android.libraries.architecture.overlay.operation.show +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.dateformatter.api.toHumanReadableDuration @@ -73,17 +74,19 @@ import io.element.android.libraries.matrix.api.room.alias.matches import io.element.android.libraries.matrix.api.room.joinedRoomMembers import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.matrix.ui.messages.RoomNamesCache import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionSpanUpdater import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @ContributesNode(RoomScope::class) @@ -100,11 +103,14 @@ class MessagesFlowNode @AssistedInject constructor( private val locationService: LocationService, private val room: MatrixRoom, private val roomMemberProfilesCache: RoomMemberProfilesCache, + private val roomNamesCache: RoomNamesCache, + private val mentionSpanUpdater: MentionSpanUpdater, private val mentionSpanTheme: MentionSpanTheme, private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider, private val timelineController: TimelineController, private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, private val dateFormatter: DateFormatter, + private val coroutineDispatchers: CoroutineDispatchers, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialTarget.toNavTarget(), @@ -172,13 +178,29 @@ class MessagesFlowNode @AssistedInject constructor( timelineController.close() } ) + setupCacheUpdaters() + + pinnedEventsTimelineProvider.launchIn(lifecycleScope) + } + + private fun setupCacheUpdaters() { room.membersStateFlow .onEach { membersState -> - roomMemberProfilesCache.replace(membersState.joinedRoomMembers()) + withContext(coroutineDispatchers.computation) { + roomMemberProfilesCache.replace(membersState.joinedRoomMembers()) + } } .launchIn(lifecycleScope) - pinnedEventsTimelineProvider.launchIn(lifecycleScope) + matrixClient.roomListService + .allRooms + .summaries + .onEach { + withContext(coroutineDispatchers.computation) { + roomNamesCache.replace(it) + } + } + .launchIn(lifecycleScope) } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -465,10 +487,9 @@ class MessagesFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - mentionSpanTheme.updateStyles(currentUserId = room.sessionId) + mentionSpanTheme.updateStyles() CompositionLocalProvider( - LocalRoomMemberProfilesCache provides roomMemberProfilesCache, - LocalMentionSpanTheme provides mentionSpanTheme, + LocalMentionSpanUpdater provides mentionSpanUpdater ) { BackstackWithOverlayBox(modifier) } 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 405bc2c536..b5a1842212 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 @@ -75,7 +75,6 @@ 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 import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.model.getAvatarData @@ -128,7 +127,7 @@ class MessagesPresenter @AssistedInject constructor( @Composable override fun present(): MessagesState { - htmlConverterProvider.Update(currentUserId = room.sessionId) + htmlConverterProvider.Update() val roomInfo by room.roomInfoFlow.collectAsState() val localCoroutineScope = rememberCoroutineScope() @@ -183,7 +182,7 @@ class MessagesPresenter @AssistedInject constructor( showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L } } - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() 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 b8fb44dca2..87134ac7d0 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 @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredWidthIn import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape @@ -484,14 +483,15 @@ private fun MessagesViewTopBar( BackButton(onClick = onBackClick) }, title = { + val roundedCornerShape = RoundedCornerShape(8.dp) Row( + modifier = Modifier + .clip(roundedCornerShape) + .clickable { onRoomDetailsClick() }, horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, ) { - val roundedCornerShape = RoundedCornerShape(8.dp) - val titleModifier = Modifier - .clip(roundedCornerShape) - .clickable { onRoomDetailsClick() } + val titleModifier = Modifier.weight(1f, fill = false) if (roomName != null && roomAvatar != null) { RoomAvatarAndNameRow( roomName = roomName, @@ -509,7 +509,6 @@ private fun MessagesViewTopBar( when (dmUserIdentityState) { IdentityState.Verified -> { Icon( - modifier = Modifier.requiredWidthIn(min = 16.dp), imageVector = CompoundIcons.Verified(), tint = ElementTheme.colors.iconSuccessPrimary, contentDescription = null, @@ -517,7 +516,6 @@ private fun MessagesViewTopBar( } IdentityState.VerificationViolation -> { Icon( - modifier = Modifier.requiredWidthIn(min = 16.dp), imageVector = CompoundIcons.ErrorSolid(), tint = ElementTheme.colors.iconCriticalPrimary, contentDescription = null, 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 5274dcbc22..2ca03e09ea 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 @@ -84,7 +84,9 @@ class DefaultActionListPresenter @AssistedInject constructor( mutableStateOf(ActionListState.Target.None) } - val isDeveloperModeEnabled by appPreferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false) + val isDeveloperModeEnabled by remember { + appPreferencesStore.isDeveloperModeEnabledFlow() + }.collectAsState(initial = false) val isPinnedEventsEnabled = isPinnedMessagesFeatureEnabled() val pinnedEventIds by remember { room.roomInfoFlow.map { it.pinnedEventIds } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index e7160d26dc..90bc3d0427 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -32,6 +32,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.api.allFiles @@ -78,8 +79,12 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( val ongoingSendAttachmentJob = remember { mutableStateOf(null) } - val allowCaption by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation).collectAsState(initial = false) - val showCaptionCompatibilityWarning by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning).collectAsState(initial = false) + val allowCaption by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation) + }.collectAsState(initial = false) + val showCaptionCompatibilityWarning by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning) + }.collectAsState(initial = false) var useSendQueue by remember { mutableStateOf(false) } var preprocessMediaJob by remember { mutableStateOf(null) } @@ -123,6 +128,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( caption = caption, sendActionState = sendActionState, dismissAfterSend = !useSendQueue, + replyParameters = null, ) } } @@ -233,6 +239,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( caption: String?, sendActionState: MutableState, dismissAfterSend: Boolean, + replyParameters: ReplyParameters?, ) = runCatching { val context = coroutineContext val progressCallback = object : ProgressCallback { @@ -247,7 +254,8 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( mediaUploadInfo = mediaUploadInfo, caption = caption, formattedCaption = null, - progressCallback = progressCallback + progressCallback = progressCallback, + replyParameters = replyParameters, ).getOrThrow() }.fold( onSuccess = { 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 ca9faddfee..8b66fd9e62 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 @@ -196,6 +196,7 @@ private fun AttachmentsPreviewBottomActions( onDeleteVoiceMessage = {}, onReceiveSuggestion = {}, resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, + resolveAtRoomMentionDisplay = { TextDisplay.Plain }, onError = {}, onTyping = {}, onSelectRichContent = {}, 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 8489139bb1..32142734e1 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 @@ -47,16 +47,15 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder -import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId -import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.mediapickers.api.PickerProvider @@ -65,7 +64,6 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.preferences.api.store.SessionPreferencesStore -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState @@ -119,7 +117,6 @@ class MessageComposerPresenter @AssistedInject constructor( private val draftService: ComposerDraftService, private val mentionSpanProvider: MentionSpanProvider, private val pillificationHelper: TextPillificationHelper, - private val roomMemberProfilesCache: RoomMemberProfilesCache, private val suggestionsProcessor: SuggestionsProcessor, ) : Presenter { @AssistedFactory @@ -181,7 +178,9 @@ class MessageComposerPresenter @AssistedInject constructor( } var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) } - val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true) + val sendTypingNotifications by remember { + sessionPreferencesStore.isSendTypingNotificationsEnabled() + }.collectAsState(initial = true) LaunchedEffect(cameraPermissionState.permissionGranted) { if (cameraPermissionState.permissionGranted) { @@ -331,7 +330,6 @@ class MessageComposerPresenter @AssistedInject constructor( markdownTextEditorState.insertSuggestion( resolvedSuggestion = event.resolvedSuggestion, mentionSpanProvider = mentionSpanProvider, - permalinkBuilder = permalinkBuilder, ) suggestionSearchTrigger.value = null } @@ -344,23 +342,24 @@ class MessageComposerPresenter @AssistedInject constructor( } } - val mentionSpanTheme = LocalMentionSpanTheme.current - val resolveMentionDisplay = remember(mentionSpanTheme) { + val resolveMentionDisplay = remember { { text: String, url: String -> - val permalinkData = permalinkParser.parse(url) - if (permalinkData is PermalinkData.UserLink) { - val displayNameOrId = roomMemberProfilesCache.getDisplayName(permalinkData.userId) ?: permalinkData.userId.value - val mentionSpan = mentionSpanProvider.getMentionSpanFor(displayNameOrId, url) - mentionSpan.update(mentionSpanTheme) + val mentionSpan = mentionSpanProvider.getMentionSpanFor(text, url) + if (mentionSpan != null) { TextDisplay.Custom(mentionSpan) } else { - val mentionSpan = mentionSpanProvider.getMentionSpanFor(text, url) - mentionSpan.update(mentionSpanTheme) - TextDisplay.Custom(mentionSpan) + TextDisplay.Plain } } } + val resolveAtRoomMentionDisplay = remember { + { + val mentionSpan = mentionSpanProvider.createEveryoneMentionSpan() + TextDisplay.Custom(mentionSpan) + } + } + return MessageComposerState( textEditorState = textEditorState, isFullScreen = isFullScreen.value, @@ -371,6 +370,7 @@ class MessageComposerPresenter @AssistedInject constructor( canCreatePoll = canCreatePoll.value, suggestions = suggestions.toPersistentList(), resolveMentionDisplay = resolveMentionDisplay, + resolveAtRoomMentionDisplay = resolveAtRoomMentionDisplay, eventSink = { handleEvents(it) }, ) } @@ -400,16 +400,16 @@ class MessageComposerPresenter @AssistedInject constructor( .stateIn(this, SharingStarted.Lazily, emptyList()) combine(mentionTriggerFlow, room.membersStateFlow, roomAliasSuggestionsFlow) { suggestion, roomMembersState, roomAliasSuggestions -> - val result = suggestionsProcessor.process( - suggestion = suggestion, - roomMembersState = roomMembersState, - roomAliasSuggestions = roomAliasSuggestions, - currentUserId = currentUserId, - canSendRoomMention = ::canSendRoomMention, - ) - suggestions.clear() - suggestions.addAll(result) - } + val result = suggestionsProcessor.process( + suggestion = suggestion, + roomMembersState = roomMembersState, + roomAliasSuggestions = roomAliasSuggestions, + currentUserId = currentUserId, + canSendRoomMention = ::canSendRoomMention, + ) + suggestions.clear() + suggestions.addAll(result) + } .collect() } } @@ -453,7 +453,19 @@ class MessageComposerPresenter @AssistedInject constructor( } is MessageComposerMode.Reply -> { timelineController.invokeOnCurrentTimeline { - replyMessage(capturedMode.eventId, message.markdown, message.html, message.intentionalMentions) + with(capturedMode) { + replyMessage( + body = message.markdown, + htmlBody = message.html, + intentionalMentions = message.intentionalMentions, + replyParameters = ReplyParameters( + inReplyToEventId = eventId, + enforceThreadReply = inThread, + // This should be false until we add a way to make a reply in a thread an explicit reply to the provided eventId + replyWithinThread = false, + ), + ) + } } } } @@ -640,8 +652,8 @@ class MessageComposerPresenter @AssistedInject constructor( analyticsService.captureInteraction(Interaction.Name.MobileRoomComposerFormattingEnabled) } else { val markdown = richTextEditorState.messageMarkdown - val pilliefiedMarkdown = pillificationHelper.pillify(markdown) - markdownTextEditorState.text.update(pilliefiedMarkdown, true) + val markdownWithMentions = pillificationHelper.pillify(markdown, false) + markdownTextEditorState.text.update(markdownWithMentions, true) // Give some time for the focus of the previous editor to be cleared delay(100) markdownTextEditorState.requestFocusAction() @@ -709,7 +721,7 @@ class MessageComposerPresenter @AssistedInject constructor( if (content.isEmpty()) { markdownTextEditorState.selection = IntRange.EMPTY } - val pillifiedContent = pillificationHelper.pillify(content) + val pillifiedContent = pillificationHelper.pillify(content, false) markdownTextEditorState.text.update(pillifiedContent, true) if (requestFocus) { markdownTextEditorState.requestFocusAction() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index d86ba8d98c..1f1f80b203 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -25,5 +25,6 @@ data class MessageComposerState( val canCreatePoll: Boolean, val suggestions: ImmutableList, val resolveMentionDisplay: (String, String) -> TextDisplay, + val resolveAtRoomMentionDisplay: () -> TextDisplay, val eventSink: (MessageComposerEvents) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index c0ea895618..590cfd20cf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -43,5 +43,6 @@ fun aMessageComposerState( canCreatePoll = canCreatePoll, suggestions = suggestions, resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, + resolveAtRoomMentionDisplay = { TextDisplay.Plain }, eventSink = eventSink, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index bfb51f4a87..42c68255e8 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 @@ -113,6 +113,7 @@ internal fun MessageComposerView( onDeleteVoiceMessage = onDeleteVoiceMessage, onReceiveSuggestion = ::onSuggestionReceived, resolveMentionDisplay = state.resolveMentionDisplay, + resolveAtRoomMentionDisplay = state.resolveAtRoomMentionDisplay, onError = ::onError, onTyping = ::onTyping, onSelectRichContent = ::sendUri, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt index 8cdf5a1626..78e763fcc2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt @@ -16,11 +16,9 @@ import androidx.compose.ui.platform.LocalInspectionMode import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.wysiwyg.compose.StyledHtmlConverter import io.element.android.wysiwyg.display.MentionDisplayHandler @@ -29,42 +27,45 @@ import io.element.android.wysiwyg.utils.HtmlConverter import uniffi.wysiwyg_composer.newMentionDetector import javax.inject.Inject -@ContributesBinding(SessionScope::class) -@SingleIn(SessionScope::class) +@ContributesBinding(RoomScope::class) +@SingleIn(RoomScope::class) class DefaultHtmlConverterProvider @Inject constructor( private val mentionSpanProvider: MentionSpanProvider, ) : HtmlConverterProvider { private val htmlConverter: MutableState = mutableStateOf(null) @Composable - override fun Update(currentUserId: UserId) { + override fun Update() { val isInEditMode = LocalInspectionMode.current val mentionDetector = remember(isInEditMode) { if (isInEditMode) null else newMentionDetector() } val editorStyle = ElementRichTextEditorStyle.textStyle() - val mentionSpanTheme = LocalMentionSpanTheme.current val context = LocalContext.current - htmlConverter.value = remember(editorStyle, mentionSpanTheme) { + htmlConverter.value = remember(editorStyle) { StyledHtmlConverter( context = context, mentionDisplayHandler = object : MentionDisplayHandler { override fun resolveAtRoomMentionDisplay(): TextDisplay { - val mentionSpan = mentionSpanProvider.getMentionSpanFor(text = "@room", url = "#") - mentionSpan.update(mentionSpanTheme) + val mentionSpan = mentionSpanProvider.createEveryoneMentionSpan() return TextDisplay.Custom(mentionSpan) } override fun resolveMentionDisplay(text: String, url: String): TextDisplay { val mentionSpan = mentionSpanProvider.getMentionSpanFor(text, url) - mentionSpan.update(mentionSpanTheme) - return TextDisplay.Custom(mentionSpan) + return if (mentionSpan != null) { + TextDisplay.Custom(mentionSpan) + } else { + TextDisplay.Plain + } } }, isEditor = false, - isMention = { _, url -> mentionDetector?.isMention(url).orFalse() } + isMention = { _, url -> + mentionDetector?.isMention(url).orFalse() + } ).apply { configureWith(editorStyle) } 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 4061be78c1..6ac01c990b 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 @@ -109,9 +109,15 @@ class TimelinePresenter @AssistedInject constructor( val messageShield: MutableState = remember { mutableStateOf(null) } val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present() - val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true) - val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true) - val isLive by timelineController.isLive().collectAsState(initial = true) + val isSendPublicReadReceiptsEnabled by remember { + sessionPreferencesStore.isSendPublicReadReceiptsEnabled() + }.collectAsState(initial = true) + val renderReadReceipts by remember { + sessionPreferencesStore.isRenderReadReceiptsEnabled() + }.collectAsState(initial = true) + val isLive by remember { + timelineController.isLive() + }.collectAsState(initial = true) fun handleEvents(event: TimelineEvents) { when (event) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index ae17f26898..1b0a4df559 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -23,8 +23,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -37,6 +37,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.ViewConfiguration +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -93,6 +94,7 @@ import io.element.android.libraries.matrix.ui.messages.reply.eventId import io.element.android.libraries.matrix.ui.messages.sender.SenderName import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.wysiwyg.link.Link import kotlinx.coroutines.launch @@ -314,6 +316,7 @@ private fun TimelineItemEventRowContent( event.senderId, event.senderProfile, event.senderAvatar, + onUserDataClick, Modifier .constrainAs(sender) { top.linkTo(parent.top) @@ -321,13 +324,7 @@ private fun TimelineItemEventRowContent( start.linkTo(parent.start) } .padding(horizontal = 16.dp) - .zIndex(1f) - .clickable(onClick = onUserDataClick) - // This is redundant when using talkback - .clearAndSetSemantics { - invisibleToUser() - testTag = TestTags.timelineItemSenderInfo.value - } + .zIndex(1f), ) } @@ -425,13 +422,31 @@ private fun MessageSenderInformation( senderId: UserId, senderProfile: ProfileTimelineDetails, senderAvatar: AvatarData, + onClick: () -> Unit, modifier: Modifier = Modifier ) { val avatarColors = AvatarColorsProvider.provide(senderAvatar.id) - Row(modifier = modifier) { - Avatar(senderAvatar) - Spacer(modifier = Modifier.width(4.dp)) + Row( + modifier = modifier + // Add external clickable modifier with no indicator so the touch target is larger than just the display name + .clickable(onClick = onClick, enabled = true, interactionSource = remember { MutableInteractionSource() }, indication = null) + .clearAndSetSemantics { + invisibleToUser() + } + ) { + Avatar( + modifier = Modifier + .testTag(TestTags.timelineItemSenderAvatar) + .clip(CircleShape) + .clickable(onClick = onClick), + avatarData = senderAvatar, + ) SenderName( + modifier = Modifier + .testTag(TestTags.timelineItemSenderName) + .clip(RoundedCornerShape(6.dp)) + .clickable(onClick = onClick) + .padding(horizontal = 4.dp), senderId = senderId, senderProfile = senderProfile, senderNameMode = SenderNameMode.Timeline(avatarColors.foreground), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt index 8933538b8a..0555670e46 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt @@ -32,7 +32,7 @@ internal fun TimelineItemEventRowTimestampPreview( event = event.copy( content = event.content.copy( body = str, - pillifiedBody = str, + formattedBody = str, ), reactionsState = aTimelineItemReactions(count = 0), ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt index b8256ba402..fcaac65e82 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt @@ -7,7 +7,6 @@ package io.element.android.features.messages.impl.timeline.components.customreaction -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -41,7 +40,7 @@ import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.coroutines.launch -@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun EmojiPicker( onSelectEmoji: (Emoji) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index aed72254c6..d496b94f3b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -7,16 +7,13 @@ package io.element.android.features.messages.impl.timeline.components.event -import android.text.SpannableString +import android.text.SpannedString import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.Box import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics @@ -32,14 +29,8 @@ import io.element.android.features.messages.impl.utils.containsOnlyEmojis import io.element.android.libraries.androidutils.text.LinkifyHelper import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache -import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme -import io.element.android.libraries.textcomposer.mentions.MentionSpan -import io.element.android.libraries.textcomposer.mentions.getMentionSpans -import io.element.android.libraries.textcomposer.mentions.updateMentionStyles +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater import io.element.android.wysiwyg.compose.EditorStyledText import io.element.android.wysiwyg.link.Link @@ -51,7 +42,7 @@ fun TimelineItemTextView( modifier: Modifier = Modifier, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit = {}, ) { - val emojiOnly = (content.formattedBody == null || content.formattedBody.toString() == content.body) && + val emojiOnly = content.formattedBody.toString() == content.body && content.body.replace(" ", "").containsOnlyEmojis() val textStyle = when { emojiOnly -> ElementTheme.typography.fontHeadingXlRegular @@ -61,10 +52,10 @@ fun TimelineItemTextView( LocalContentColor provides ElementTheme.colors.textPrimary, LocalTextStyle provides textStyle ) { - val body = getTextWithResolvedMentions(content) + val text = getTextWithResolvedMentions(content) Box(modifier.semantics { contentDescription = content.plainText }) { EditorStyledText( - text = body, + text = text, onLinkClickedListener = onLinkClick, onLinkLongClickedListener = onLinkLongClick, style = ElementRichTextEditorStyle.textStyle(), @@ -78,36 +69,9 @@ fun TimelineItemTextView( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @Composable internal fun getTextWithResolvedMentions(content: TimelineItemTextBasedContent): CharSequence { - val userProfileCache = LocalRoomMemberProfilesCache.current - val lastCacheUpdate by userProfileCache.lastCacheUpdate.collectAsState() - val mentionSpanTheme = LocalMentionSpanTheme.current - val formattedBody = content.formattedBody ?: content.pillifiedBody - val textWithMentions = remember(formattedBody, mentionSpanTheme, lastCacheUpdate) { - updateMentionSpans(formattedBody, userProfileCache) - mentionSpanTheme.updateMentionStyles(formattedBody) - formattedBody - } - return SpannableString(textWithMentions) -} - -private fun updateMentionSpans(text: CharSequence, cache: RoomMemberProfilesCache): Boolean { - var changedContents = false - for (mentionSpan in text.getMentionSpans()) { - when (mentionSpan.type) { - MentionSpan.Type.USER -> { - val displayName = cache.getDisplayName(UserId(mentionSpan.rawValue)) ?: mentionSpan.rawValue - if (mentionSpan.text != displayName) { - changedContents = true - mentionSpan.text = displayName - } - } - // There's no need to do anything for `@room` pills - MentionSpan.Type.EVERYONE -> Unit - // Nothing yet for room mentions - MentionSpan.Type.ROOM -> Unit - } - } - return changedContents + val mentionSpanUpdater = LocalMentionSpanUpdater.current + val bodyWithResolvedMentions = mentionSpanUpdater.rememberMentionSpans(content.formattedBody) + return SpannedString.valueOf(bodyWithResolvedMentions) } @PreviewsDayNight @@ -126,7 +90,7 @@ internal fun TimelineItemTextViewPreview( @Composable internal fun TimelineItemTextViewWithLinkifiedUrlPreview() = ElementPreview { val content = aTimelineItemTextContent( - pillifiedBody = LinkifyHelper.linkify("The link should end after the first '?' (url: github.com/element-hq/element-x-android/README?)?.") + formattedBody = LinkifyHelper.linkify("The link should end after the first '?' (url: github.com/element-hq/element-x-android/README?)?.") ) TimelineItemTextView( content = content, @@ -139,7 +103,7 @@ internal fun TimelineItemTextViewWithLinkifiedUrlPreview() = ElementPreview { @Composable internal fun TimelineItemTextViewWithLinkifiedUrlAndNestedParenthesisPreview() = ElementPreview { val content = aTimelineItemTextContent( - pillifiedBody = LinkifyHelper.linkify("The link should end after the '(ME)' ((url: github.com/element-hq/element-x-android/READ(ME)))!") + formattedBody = LinkifyHelper.linkify("The link should end after the '(ME)' ((url: github.com/element-hq/element-x-android/READ(ME)))!") ) TimelineItemTextView( content = content, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt index 12f325a20d..b1aa726628 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt @@ -7,7 +7,6 @@ package io.element.android.features.messages.impl.timeline.components.reactionsummary -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -94,7 +93,6 @@ fun ReactionSummaryView( } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun ReactionSummaryViewContent( summary: ReactionSummaryState.Summary, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt index 701dd93fc7..80d594d4d7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex @@ -42,7 +43,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.testtags.TestTags -import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -60,7 +60,6 @@ fun TimelineItemReadReceiptView( ReadReceiptsAvatars( receipts = state.receipts, modifier = Modifier - .testTag(TestTags.messageReadReceipts) .clip(RoundedCornerShape(4.dp)) .clickable { onReadReceiptsClick() @@ -135,6 +134,7 @@ private fun ReadReceiptsAvatars( Row( modifier = modifier .clearAndSetSemantics { + testTag = TestTags.messageReadReceipts.value contentDescription = receiptDescription }, horizontalArrangement = Arrangement.spacedBy(4.dp - avatarStrokeSize), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index d7d267ce48..f3b54dae53 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -69,13 +69,16 @@ class TimelineItemContentMessageFactory @Inject constructor( return when (val messageType = content.type) { is EmoteMessageType -> { val emoteBody = "* $senderDisambiguatedDisplayName ${messageType.body.trimEnd()}" + val formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisambiguatedDisplayName") ?: textPillificationHelper.pillify( + emoteBody + ).safeLinkify() TimelineItemEmoteContent( body = emoteBody, htmlDocument = messageType.formatted?.toHtmlDocument( permalinkParser = permalinkParser, prefix = "* $senderDisambiguatedDisplayName", ), - formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisambiguatedDisplayName") ?: emoteBody.withLinks(), + formattedBody = formattedBody, isEdited = content.isEdited, ) } @@ -123,10 +126,8 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemTextContent( body = body, - pillifiedBody = textPillificationHelper.pillify(body), htmlDocument = null, - plainText = body, - formattedBody = null, + formattedBody = body, isEdited = content.isEdited, ) } else { @@ -219,20 +220,26 @@ class TimelineItemContentMessageFactory @Inject constructor( } is NoticeMessageType -> { val body = messageType.body.trimEnd() + val formattedBody = parseHtml(messageType.formatted) ?: textPillificationHelper.pillify( + body + ).safeLinkify() + val htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser) TimelineItemNoticeContent( body = body, - htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser), - formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), + htmlDocument = htmlDocument, + formattedBody = formattedBody, isEdited = content.isEdited, ) } is TextMessageType -> { val body = messageType.body.trimEnd() + val formattedBody = parseHtml(messageType.formatted) ?: textPillificationHelper.pillify( + body + ).safeLinkify() TimelineItemTextContent( body = body, - pillifiedBody = textPillificationHelper.pillify(body).safeLinkify(), htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser), - formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), + formattedBody = formattedBody, isEdited = content.isEdited, ) } @@ -240,9 +247,8 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemTextContent( body = body, - pillifiedBody = textPillificationHelper.pillify(body), htmlDocument = null, - formattedBody = body.withLinks(), + formattedBody = textPillificationHelper.pillify(body).safeLinkify(), isEdited = content.isEdited, ) } @@ -263,6 +269,7 @@ class TimelineItemContentMessageFactory @Inject constructor( if (formattedBody == null || formattedBody.format != MessageFormat.HTML) return null val result = htmlConverterProvider.provide() .fromHtmlToSpans(formattedBody.body.trimEnd()) + .let { textPillificationHelper.pillify(it) } .safeLinkify() return if (prefix != null) { buildSpannedString { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt index 82cd5a3f39..dc04e5b269 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt @@ -12,11 +12,10 @@ import org.jsoup.nodes.Document data class TimelineItemEmoteContent( override val body: String, - override val pillifiedBody: CharSequence = body, override val htmlDocument: Document?, - override val plainText: String = htmlDocument?.toPlainText() ?: body, - override val formattedBody: CharSequence?, + override val formattedBody: CharSequence, override val isEdited: Boolean, ) : TimelineItemTextBasedContent { override val type: String = "TimelineItemEmoteContent" + override val plainText: String = htmlDocument?.toPlainText() ?: body } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt index 09484ac7e3..36c32222be 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent +import org.jsoup.nodes.Document class TimelineItemEventContentProvider : PreviewParameterProvider { override val values = sequenceOf( @@ -58,35 +59,46 @@ class TimelineItemTextBasedContentProvider : PreviewParameterProvider { @Composable override fun present(): TimelineProtectionState { - val hideMediaContent by appPreferencesStore.doesHideImagesAndVideosFlow().collectAsState(initial = false) + val hideMediaContent by remember { + appPreferencesStore.doesHideImagesAndVideosFlow() + }.collectAsState(initial = false) var allowedEvents by remember { mutableStateOf>(setOf()) } val protectionState by remember(hideMediaContent) { derivedStateOf { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt index 35759ade74..4dfc02a2a1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt @@ -37,7 +37,9 @@ class TypingNotificationPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): TypingNotificationState { - val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true) + val renderTypingNotifications by remember { + sessionPreferencesStore.isRenderTypingNotificationsEnabled() + }.collectAsState(initial = true) val typingMembersState by produceState(initialValue = persistentListOf(), key1 = renderTypingNotifications) { if (renderTypingNotifications) { observeRoomTypingMembers() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt index c6dc27870b..458c27061c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt @@ -9,6 +9,9 @@ package io.element.android.features.messages.impl.utils import android.text.Spannable import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.URLSpan +import android.util.Patterns import androidx.core.text.getSpans import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.RoomScope @@ -16,63 +19,90 @@ import io.element.android.libraries.matrix.api.core.MatrixPatternType import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkParser -import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache -import io.element.android.libraries.textcomposer.mentions.MentionSpan import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.getMentionSpans +import io.element.android.wysiwyg.view.spans.CodeBlockSpan +import io.element.android.wysiwyg.view.spans.InlineCodeSpan import javax.inject.Inject interface TextPillificationHelper { - fun pillify(text: CharSequence): CharSequence + fun pillify(text: CharSequence, pillifyPermalinks: Boolean = true): CharSequence } @ContributesBinding(RoomScope::class) class DefaultTextPillificationHelper @Inject constructor( private val mentionSpanProvider: MentionSpanProvider, - private val permalinkBuilder: PermalinkBuilder, private val permalinkParser: PermalinkParser, - private val roomMemberProfilesCache: RoomMemberProfilesCache, + private val permalinkBuilder: PermalinkBuilder, ) : TextPillificationHelper { @Suppress("LoopWithTooManyJumpStatements") - override fun pillify(text: CharSequence): CharSequence { - val matches = MatrixPatterns.findPatterns(text, permalinkParser).sortedByDescending { it.end } - if (matches.isEmpty()) return text + override fun pillify(text: CharSequence, pillifyPermalinks: Boolean): CharSequence { + return SpannableStringBuilder(text).apply { + pillifyMatrixPatterns(this) + if (pillifyPermalinks) { + pillifyPermalinks(this) + } + } + } - val spannable = SpannableStringBuilder(text) + private fun pillifyMatrixPatterns(text: SpannableStringBuilder) { + val matches = MatrixPatterns.findPatterns(text, permalinkParser).sortedByDescending { it.end } + if (matches.isEmpty()) return for (match in matches) { + if (!text.canPillify(match.start, match.end)) continue when (match.type) { MatrixPatternType.USER_ID -> { - val mentionSpanExists = spannable.getSpans(match.start, match.end).isNotEmpty() - if (!mentionSpanExists) { - val userId = UserId(match.value) - val permalink = permalinkBuilder.permalinkForUser(userId).getOrNull() ?: continue - val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, permalink) - roomMemberProfilesCache.getDisplayName(userId)?.let { mentionSpan.text = it } - spannable.replace(match.start, match.end, "@ ") - spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + val userId = UserId(match.value) + val mentionSpan = mentionSpanProvider.createUserMentionSpan(userId) + text.replace(match.start, match.end, "@ ") + text.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + permalinkBuilder.permalinkForUser(userId).getOrNull()?.also { permalink -> + // Also add a URLSpan in case of raw user id so it can be clicked + val urlSpan = URLSpan(permalink) + text.setSpan(urlSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } MatrixPatternType.ROOM_ALIAS -> { - val mentionSpanExists = spannable.getSpans(match.start, match.end).isNotEmpty() - if (!mentionSpanExists) { - val permalink = permalinkBuilder.permalinkForRoomAlias(RoomAlias(match.value)).getOrNull() ?: continue - val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, permalink) - spannable.replace(match.start, match.end, "@ ") - spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + val roomAlias = RoomAlias(match.value) + val mentionSpan = mentionSpanProvider.createRoomMentionSpan(roomAlias.toRoomIdOrAlias()) + text.replace(match.start, match.end, "@ ") + text.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + permalinkBuilder.permalinkForRoomAlias(roomAlias).getOrNull()?.also { permalink -> + // Also add a URLSpan in case of raw room alias so it can be clicked + val urlSpan = URLSpan(permalink) + text.setSpan(urlSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } MatrixPatternType.AT_ROOM -> { - val mentionSpanExists = spannable.getSpans(match.start, match.end).isNotEmpty() - if (!mentionSpanExists) { - val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "") - spannable.replace(match.start, match.end, "@ ") - spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } + val mentionSpan = mentionSpanProvider.createEveryoneMentionSpan() + text.replace(match.start, match.end, "@ ") + text.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } else -> Unit } } - return spannable + } + + private fun pillifyPermalinks(text: SpannableStringBuilder) { + for (match in Patterns.WEB_URL.toRegex().findAll(text)) { + val start = match.range.first + val end = match.range.last + 1 + if (!text.canPillify(start, end)) continue + val url = text.substring(match.range) + val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, url) + if (mentionSpan != null) { + text.setSpan(mentionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + } + + private fun Spanned.canPillify(start: Int, end: Int): Boolean { + if (getMentionSpans(start, end).isNotEmpty()) return false + if (getSpans(start, end).isNotEmpty()) return false + if (getSpans(start, end).isNotEmpty()) return false + return true } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt index 1f44d3dd1f..8a05770942 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt @@ -26,6 +26,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent +import io.element.android.libraries.core.extensions.toSafeLength import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.RoomScope import io.element.android.libraries.ui.strings.CommonStrings @@ -35,11 +36,6 @@ import javax.inject.Inject class DefaultMessageSummaryFormatter @Inject constructor( @ApplicationContext private val context: Context, ) : MessageSummaryFormatter { - companion object { - // Max characters to display in the summary message. This works around https://github.com/element-hq/element-x-android/issues/2105 - private const val MAX_SAFE_LENGTH = 500 - } - override fun format(event: TimelineItem.Event): String { return when (event.content) { is TimelineItemTextBasedContent -> event.content.plainText @@ -58,6 +54,8 @@ class DefaultMessageSummaryFormatter @Inject constructor( is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio) is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_unsupported_call) is TimelineItemCallNotifyContent -> context.getString(CommonStrings.common_call_started) - }.take(MAX_SAFE_LENGTH) + } + // Truncate the message to a safe length to avoid crashes in Compose + .toSafeLength() } } diff --git a/features/messages/impl/src/main/res/values-eu/translations.xml b/features/messages/impl/src/main/res/values-eu/translations.xml index cfd41379f8..34d0a114ee 100644 --- a/features/messages/impl/src/main/res/values-eu/translations.xml +++ b/features/messages/impl/src/main/res/values-eu/translations.xml @@ -25,6 +25,7 @@ "Jakinarazi gela osoari" "Guztiak" "Bidali berriro" + "Huts egin du mezuaren bidalketak" "Gehitu emojia" "Hauxe da %1$s(r)en hasiera" "Hau da elkarrizketaren hasiera." diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 9f46637948..5080013d27 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -104,7 +104,7 @@ class MessagesViewTest { state = state, onRoomDetailsClick = callback, ) - rule.onNodeWithText(state.roomName.dataOrNull().orEmpty()).performClick() + rule.onNodeWithText(state.roomName.dataOrNull().orEmpty(), useUnmergedTree = true).performClick() } } @@ -206,6 +206,7 @@ class MessagesViewTest { } @Test + @Config(qualifiers = "h1024dp") fun `clicking on a read receipt list emits the expected Event`() { val eventsRecorder = EventsRecorder() val state = aMessagesState( @@ -229,7 +230,7 @@ class MessagesViewTest { rule.setMessagesView( state = state, ) - rule.onNodeWithTag(TestTags.messageReadReceipts.value).performClick() + rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick() eventsRecorder.assertSingle(ReadReceiptBottomSheetEvents.EventSelected(timelineItem)) } @@ -309,7 +310,7 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") - fun `clicking on the sender of an Event invoke expected callback`() { + fun `clicking on the avatar of the sender of an Event invoke expected callback`() { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder @@ -322,7 +323,26 @@ class MessagesViewTest { state = state, onUserDataClick = callback, ) - rule.onNodeWithTag(TestTags.timelineItemSenderInfo.value).performClick() + rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() + } + } + + @Test + @Config(qualifiers = "h1024dp") + fun `clicking on the display name of the sender of an Event invoke expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + val state = aMessagesState( + eventSink = eventsRecorder + ) + val timelineItem = state.timelineState.timelineItems.first() + ensureCalledOnceWithParam( + param = (timelineItem as TimelineItem.Event).senderId + ) { callback -> + rule.setMessagesView( + state = state, + onUserDataClick = callback, + ) + rule.onNodeWithTag(TestTags.timelineItemSenderName.value, useUnmergedTree = true).performClick() } } 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 8a5b388f72..e5e6ff5ea2 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 @@ -153,7 +153,7 @@ class ActionListPresenterTest { val messageEvent = aMessageEvent( isMine = false, isEditable = false, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -201,7 +201,7 @@ class ActionListPresenterTest { isMine = false, isEditable = false, isThreaded = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -248,7 +248,7 @@ class ActionListPresenterTest { val messageEvent = aMessageEvent( isMine = false, isEditable = false, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -296,7 +296,7 @@ class ActionListPresenterTest { val messageEvent = aMessageEvent( isMine = false, isEditable = false, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -344,7 +344,7 @@ class ActionListPresenterTest { val messageEvent = aMessageEvent( isMine = false, isEditable = false, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -391,7 +391,7 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -439,7 +439,7 @@ class ActionListPresenterTest { val messageEvent = aMessageEvent( isMine = true, isThreaded = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -486,7 +486,7 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -804,7 +804,7 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -852,7 +852,7 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -907,7 +907,7 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( ActionListEvents.ComputeForMessage( @@ -956,7 +956,7 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) val redactedEvent = aMessageEvent( isMine = true, @@ -1006,7 +1006,7 @@ class ActionListPresenterTest { eventId = null, isMine = true, canBeRepliedTo = false, - content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null), + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE), ) initialState.eventSink.invoke( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 63a759bd67..d55ea7c7a0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.test.A_CAPTION import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder @@ -105,7 +106,8 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media success scenario`() = runTest { - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( @@ -142,7 +144,8 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media after pre-processing success scenario`() = runTest { - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( @@ -177,7 +180,8 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media before pre-processing success scenario`() = runTest { - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( @@ -287,7 +291,7 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send image with caption success scenario`() = runTest { val sendImageResult = - lambdaRecorder> { _, _, _, _, _, _ -> + lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? -> Result.success(FakeMediaUploadHandler()) } val mediaPreProcessor = FakeMediaPreProcessor().apply { @@ -320,6 +324,7 @@ class AttachmentsPreviewPresenterTest { value(A_CAPTION), any(), any(), + any(), ) onDoneListener.assertions().isCalledOnce() } @@ -328,7 +333,7 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send video with caption success scenario`() = runTest { val sendVideoResult = - lambdaRecorder> { _, _, _, _, _, _ -> + lambdaRecorder { _: File, _: File?, _: VideoInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? -> Result.success(FakeMediaUploadHandler()) } val mediaPreProcessor = FakeMediaPreProcessor().apply { @@ -361,6 +366,7 @@ class AttachmentsPreviewPresenterTest { value(A_CAPTION), any(), any(), + any(), ) onDoneListener.assertions().isCalledOnce() } @@ -369,7 +375,7 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send audio with caption success scenario`() = runTest { val sendAudioResult = - lambdaRecorder> { _, _, _, _, _ -> + lambdaRecorder> { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } val mediaPreProcessor = FakeMediaPreProcessor().apply { @@ -399,6 +405,7 @@ class AttachmentsPreviewPresenterTest { value(A_CAPTION), any(), any(), + any(), ) onDoneListener.assertions().isCalledOnce() } @@ -407,7 +414,8 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media failure scenario without media queue`() = runTest { val failure = MediaPreProcessor.Failure(null) - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.failure(failure) } val room = FakeMatrixRoom( @@ -435,7 +443,8 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media failure scenario with media queue`() = runTest { val failure = MediaPreProcessor.Failure(null) - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.failure(failure) } val onDoneListenerResult = lambdaRecorder {} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt index c52f01949d..a8dbab2cf2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt @@ -38,7 +38,7 @@ internal fun aMessageEvent( isMine: Boolean = true, isEditable: Boolean = true, canBeRepliedTo: Boolean = true, - content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false), + content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = A_MESSAGE, isEdited = false), inReplyTo: InReplyToDetails? = null, isThreaded: Boolean = false, sendState: LocalEventSendState = LocalEventSendState.Sent(AN_EVENT_ID), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 8328624bf6..a82df0f2aa 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.draft.ComposerDraftService import io.element.android.features.messages.impl.draft.FakeComposerDraftService import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor import io.element.android.features.messages.impl.timeline.TimelineController +import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.libraries.core.mimetype.MimeTypes @@ -46,6 +47,7 @@ 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.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo @@ -67,7 +69,6 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeTimeline -import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.test.FakePickerProvider @@ -82,6 +83,7 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenterFac import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion @@ -610,7 +612,7 @@ class MessageComposerPresenterTest { @Test fun `present - reply message`() = runTest { - val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List, _: Boolean -> + val replyMessageLambda = lambdaRecorder { _: ReplyParameters, _: String, _: String?, _: List, _: Boolean -> Result.success(Unit) } val timeline = FakeTimeline().apply { @@ -1109,7 +1111,7 @@ class MessageComposerPresenterTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - send messages with intentional mentions`() = runTest { - val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List, _: Boolean -> + val replyMessageLambda = lambdaRecorder { _: ReplyParameters, _: String, _: String?, _: List, _: Boolean -> Result.success(Unit) } val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List -> @@ -1535,8 +1537,11 @@ class MessageComposerPresenterTest { permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(), permalinkBuilder: PermalinkBuilder = FakePermalinkBuilder(), permalinkParser: PermalinkParser = FakePermalinkParser(), - mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkParser), - roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), + mentionSpanProvider: MentionSpanProvider = MentionSpanProvider( + permalinkParser = permalinkParser, + mentionSpanFormatter = FakeMentionSpanFormatter(), + mentionSpanTheme = MentionSpanTheme(A_USER_ID) + ), textPillificationHelper: TextPillificationHelper = FakeTextPillificationHelper(), isRichTextEditorEnabled: Boolean = true, draftService: ComposerDraftService = FakeComposerDraftService(), @@ -1562,7 +1567,6 @@ class MessageComposerPresenterTest { draftService = draftService, mentionSpanProvider = mentionSpanProvider, pillificationHelper = textPillificationHelper, - roomMemberProfilesCache = roomMemberProfilesCache, suggestionsProcessor = SuggestionsProcessor(), ).apply { isTesting = true diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index 2f4740dfc9..a5f3417d5d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -11,9 +11,11 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.test.junit4.createComposeRule import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -23,10 +25,16 @@ import org.robolectric.RobolectricTestRunner class DefaultHtmlConverterProviderTest { @get:Rule val composeTestRule = createComposeRule() + private val provider = DefaultHtmlConverterProvider( + mentionSpanProvider = MentionSpanProvider( + permalinkParser = FakePermalinkParser(), + mentionSpanFormatter = FakeMentionSpanFormatter(), + mentionSpanTheme = MentionSpanTheme(A_USER_ID) + ) + ) + @Test fun `calling provide without calling Update first should throw an exception`() { - val provider = DefaultHtmlConverterProvider(mentionSpanProvider = MentionSpanProvider(FakePermalinkParser())) - val exception = runCatching { provider.provide() }.exceptionOrNull() assertThat(exception).isInstanceOf(IllegalStateException::class.java) @@ -34,13 +42,11 @@ class DefaultHtmlConverterProviderTest { @Test fun `calling provide after calling Update first should return an HtmlConverter`() { - val provider = DefaultHtmlConverterProvider(mentionSpanProvider = MentionSpanProvider(FakePermalinkParser())) composeTestRule.setContent { CompositionLocalProvider(LocalInspectionMode provides true) { - provider.Update(currentUserId = A_USER_ID) + provider.Update() } } - val htmlConverter = runCatching { provider.provide() }.getOrNull() assertThat(htmlConverter).isNotNull() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt index fd081ac489..ee52aff578 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.timeline.components.event import android.text.SpannableString +import android.text.SpannedString import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.test.junit4.AndroidComposeTestRule @@ -18,16 +19,22 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent +import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID -import io.element.android.libraries.matrix.test.A_USER_ID_2 -import io.element.android.libraries.matrix.test.A_USER_NAME -import io.element.android.libraries.matrix.test.room.aRoomMember -import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.matrix.ui.messages.RoomNamesCache +import io.element.android.libraries.textcomposer.mentions.DefaultMentionSpanUpdater +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater import io.element.android.libraries.textcomposer.mentions.MentionSpan +import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionSpanUpdater +import io.element.android.libraries.textcomposer.mentions.MentionType import io.element.android.libraries.textcomposer.mentions.getMentionSpans +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.wysiwyg.view.spans.CustomMentionSpan import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest @@ -40,101 +47,100 @@ import org.junit.runner.RunWith class TimelineTextViewTest { @get:Rule val rule = createAndroidComposeRule() + private val mentionSpanTheme = MentionSpanTheme(currentUserId = A_USER_ID) + private val formatLambda = lambdaRecorder { mentionType -> mentionType.toString() } + private val mentionSpanFormatter = FakeMentionSpanFormatter(formatLambda) + @Test fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runTest { val charSequence = "Hello @alice:example.com" - - val result = rule.getText(aTextContentWithFormattedBody(charSequence)) + val mentionSpanUpdater = aMentionSpanUpdater() + val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) assertThat(result.getMentionSpans()).isEmpty() + assert(formatLambda).isNeverCalled() } @Test fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runTest { val charSequence = SpannableString("Hello @alice:example.com") - - val result = rule.getText(aTextContentWithFormattedBody(charSequence)) + val mentionSpanUpdater = aMentionSpanUpdater() + val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) assertThat(result.getMentionSpans()).isEmpty() + assert(formatLambda).isNeverCalled() } @Test fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runTest { val charSequence = "Hello @alice:example.com" - - val result = rule.getText(aTextContentWithFormattedBody(body = charSequence, formattedBody = null)) + val mentionSpanUpdater = aMentionSpanUpdater() + val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null)) assertThat(result.getMentionSpans()).isEmpty() assertThat(result.toString()).isEqualTo(charSequence) + assert(formatLambda).isNeverCalled() } @Test - fun `getTextWithResolvedMentions - with Room mention does nothing`() = runTest { + fun `getTextWithResolvedMentions - with Room mention format correctly`() = runTest { + val mentionType = MentionType.Room(roomIdOrAlias = A_ROOM_ID_2.toRoomIdOrAlias()) val charSequence = buildSpannedString { append("Hello ") - inSpans(aMentionSpan(rawValue = A_ROOM_ID_2.value, type = MentionSpan.Type.ROOM)) { + inSpans(MentionSpan(mentionType)) { append(A_ROOM_ID.value) } } + val mentionSpanUpdater = aMentionSpanUpdater() + val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) - val result = rule.getText(aTextContentWithFormattedBody(charSequence)) - - assertThat(result.getMentionSpans().firstOrNull()?.text).isEmpty() + val expectedDisplayText = mentionType.toString() + assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText) assertThat(result).isEqualTo(charSequence) + assert(formatLambda).isCalledOnce() } @Test fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runTest { + val mentionType = MentionType.User(userId = A_USER_ID) val charSequence = buildSpannedString { append("Hello ") - inSpans(aMentionSpan(rawValue = A_USER_ID.value)) { + inSpans(MentionSpan(mentionType)) { append("@NotAlice") } } + val mentionSpanUpdater = aMentionSpanUpdater() + val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) - val result = rule.getText(aTextContentWithFormattedBody(charSequence)) - - assertThat(result.getMentionSpans().firstOrNull()?.text).isEqualTo("alice") + val expectedDisplayText = mentionType.toString() + assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText) + assert(formatLambda).isCalledOnce() } @Test fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runTest { + val mentionType = MentionType.User(userId = A_USER_ID) val charSequence = buildSpannedString { append("Hello ") - inSpans(CustomMentionSpan(aMentionSpan(rawValue = A_USER_ID.value))) { + inSpans(CustomMentionSpan(MentionSpan(mentionType))) { append("@NotAlice") } } - - val result = rule.getText(aTextContentWithFormattedBody(charSequence)) - - assertThat(result.getMentionSpans().firstOrNull()?.text).isEqualTo("alice") - } - - @Test - fun `getTextWithResolvedMentions - replaces MentionSpan's text with user id if no display name is cached`() = runTest { - val charSequence = buildSpannedString { - append("Hello ") - inSpans(aMentionSpan(rawValue = A_USER_ID_2.value)) { - append("@NotAlice") - } - } - - val result = rule.getText(aTextContentWithFormattedBody(charSequence)) - - assertThat(result.getMentionSpans().firstOrNull()?.text).isEqualTo(A_USER_ID_2.value) + val mentionSpanUpdater = aMentionSpanUpdater() + val expectedDisplayText = mentionType.toString() + val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) + assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText) + assert(formatLambda).isCalledOnce() } private suspend fun AndroidComposeTestRule.getText( + mentionSpanUpdater: MentionSpanUpdater, content: TimelineItemTextBasedContent, ): CharSequence { val completable = CompletableDeferred() setContent { - val roomMemberProfilesCache = RoomMemberProfilesCache().apply { - replace(listOf(aRoomMember(userId = A_USER_ID, displayName = A_USER_NAME))) - } CompositionLocalProvider( - LocalRoomMemberProfilesCache provides roomMemberProfilesCache, + LocalMentionSpanUpdater provides mentionSpanUpdater ) { completable.complete(getTextWithResolvedMentions(content = content)) } @@ -142,21 +148,20 @@ class TimelineTextViewTest { return completable.await() } - private fun aMentionSpan( - rawValue: String, - text: String = "", - type: MentionSpan.Type = MentionSpan.Type.USER - ) = MentionSpan( - text = text, - rawValue = rawValue, - type = type, - ) + private fun aMentionSpanUpdater(): MentionSpanUpdater { + return DefaultMentionSpanUpdater( + formatter = mentionSpanFormatter, + theme = mentionSpanTheme, + roomMemberProfilesCache = RoomMemberProfilesCache(), + roomNamesCache = RoomNamesCache(), + ) + } private fun aTextContentWithFormattedBody(formattedBody: CharSequence?, body: String = "") = TimelineItemTextContent( body = body, htmlDocument = null, - formattedBody = formattedBody, + formattedBody = formattedBody ?: SpannedString(body), isEdited = false ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 51f3f09268..c6c8c7c1e3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -89,9 +89,8 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemTextContent( body = "body", htmlDocument = null, - plainText = "body", isEdited = false, - formattedBody = null, + formattedBody = SpannedString("body"), ) assertThat(result).isEqualTo(expected) } @@ -123,9 +122,8 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemTextContent( body = "body", htmlDocument = null, - plainText = "body", isEdited = false, - formattedBody = null, + formattedBody = "body", ) assertThat(result).isEqualTo(expected) } @@ -141,10 +139,8 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemTextContent( body = "body", htmlDocument = null, - plainText = "body", isEdited = false, - formattedBody = null, - pillifiedBody = SpannableString("body"), + formattedBody = SpannedString("body"), ) assertThat(result).isEqualTo(expected) } @@ -160,7 +156,6 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemTextContent( body = "https://www.example.org", htmlDocument = null, - plainText = "https://www.example.org", isEdited = false, formattedBody = buildSpannedString { inSpans(URLSpan("https://www.example.org")) { @@ -223,7 +218,7 @@ class TimelineItemContentMessageFactoryTest { senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) - assertThat((result as TimelineItemTextContent).formattedBody).isNull() + assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(SpannedString("body")) } @Test @@ -637,8 +632,7 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemNoticeContent( body = "body", htmlDocument = null, - plainText = "body", - formattedBody = null, + formattedBody = SpannedString("body"), isEdited = false, ) assertThat(result).isEqualTo(expected) @@ -671,8 +665,7 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemEmoteContent( body = "* Bob body", htmlDocument = null, - plainText = "* Bob body", - formattedBody = null, + formattedBody = SpannedString("* Bob body"), isEdited = false, ) assertThat(result).isEqualTo(expected) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt index 013ce22cc2..0d571a2459 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt @@ -10,17 +10,21 @@ package io.element.android.features.messages.impl.utils import android.net.Uri import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser -import io.element.android.libraries.matrix.test.room.aRoomMember -import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache -import io.element.android.libraries.textcomposer.mentions.MentionSpan +import io.element.android.libraries.textcomposer.mentions.MentionSpanFormatter import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionType import io.element.android.libraries.textcomposer.mentions.getMentionSpans import org.junit.Test import org.junit.runner.RunWith @@ -30,90 +34,201 @@ class DefaultTextPillificationHelperTest { @Test fun `pillify - adds pills for user ids`() { val text = "A @user:server.com" + val formatter = FakeMentionSpanFormatter() + val userId = UserId("@user:server.com") val helper = aTextPillificationHelper( - permalinkparser = FakePermalinkParser(result = { - PermalinkData.UserLink(UserId("@user:server.com")) + permalinkParser = FakePermalinkParser(result = { + PermalinkData.UserLink(userId) }), permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/@user:server.com") }), + mentionSpanFormatter = formatter, ) val pillified = helper.pillify(text) val mentionSpans = pillified.getMentionSpans() assertThat(mentionSpans).hasSize(1) - val mentionSpan = mentionSpans.firstOrNull() - assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.USER) - assertThat(mentionSpan?.rawValue).isEqualTo("@user:server.com") - assertThat(mentionSpan?.text).isEqualTo("@user:server.com") - } - - @Test - fun `pillify - uses the cached display name for user mentions`() { - val text = "A @user:server.com" - val helper = aTextPillificationHelper( - permalinkparser = FakePermalinkParser(result = { - PermalinkData.UserLink(UserId("@user:server.com")) - }), - permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { - Result.success("https://matrix.to/#/@user:server.com") - }), - roomMemberProfilesCache = RoomMemberProfilesCache().apply { - replace(listOf(aRoomMember(userId = UserId("@user:server.com"), displayName = "Alice"))) - }, - ) - val pillified = helper.pillify(text) - val mentionSpans = pillified.getMentionSpans() - assertThat(mentionSpans).hasSize(1) - val mentionSpan = mentionSpans.firstOrNull() - assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.USER) - assertThat(mentionSpan?.rawValue).isEqualTo("@user:server.com") - assertThat(mentionSpan?.text).isEqualTo("Alice") + val mentionSpan = mentionSpans.first() + assertThat(mentionSpan.type).isInstanceOf(MentionType.User::class.java) + val userType = mentionSpan.type as MentionType.User + assertThat(userType.userId).isEqualTo(userId) + val formatted = formatter.formatDisplayText(MentionType.User(userId)) + assertThat(mentionSpan.displayText.toString()).isEqualTo(formatted) } @Test fun `pillify - adds pills for room aliases`() { val text = "A #room:server.com" + val roomAlias = RoomAlias("#room:server.com") + val formatter = FakeMentionSpanFormatter() val helper = aTextPillificationHelper( - permalinkparser = FakePermalinkParser(result = { - PermalinkData.RoomLink(RoomIdOrAlias.Alias(RoomAlias("#room:server.com"))) + permalinkParser = FakePermalinkParser(result = { + PermalinkData.RoomLink(RoomIdOrAlias.Alias(roomAlias)) }), permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/#room:server.com") }), + mentionSpanFormatter = formatter, ) val pillified = helper.pillify(text) val mentionSpans = pillified.getMentionSpans() assertThat(mentionSpans).hasSize(1) - val mentionSpan = mentionSpans.firstOrNull() - assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.ROOM) - assertThat(mentionSpan?.rawValue).isEqualTo("#room:server.com") - assertThat(mentionSpan?.text).isEqualTo("#room:server.com") + val mentionSpan = mentionSpans.first() + assertThat(mentionSpan.type).isInstanceOf(MentionType.Room::class.java) + val roomType = mentionSpan.type as MentionType.Room + assertThat(roomType.roomIdOrAlias).isEqualTo(roomAlias.toRoomIdOrAlias()) + val formatted = formatter.formatDisplayText(MentionType.Room(roomAlias.toRoomIdOrAlias())) + assertThat(mentionSpan.displayText.toString()).isEqualTo(formatted) } @Test fun `pillify - adds pills for @room mentions`() { val text = "An @room mention" - val helper = aTextPillificationHelper(permalinkparser = FakePermalinkParser(result = { - PermalinkData.FallbackLink(Uri.EMPTY) - })) + val formatter = FakeMentionSpanFormatter() + val helper = aTextPillificationHelper( + permalinkParser = FakePermalinkParser(result = { + PermalinkData.FallbackLink(Uri.EMPTY) + }), + mentionSpanFormatter = formatter, + ) val pillified = helper.pillify(text) val mentionSpans = pillified.getMentionSpans() assertThat(mentionSpans).hasSize(1) - val mentionSpan = mentionSpans.firstOrNull() - assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.EVERYONE) - assertThat(mentionSpan?.rawValue).isEqualTo("@room") - assertThat(mentionSpan?.text).isEqualTo("@room") + val mentionSpan = mentionSpans.first() + assertThat(mentionSpan.type).isEqualTo(MentionType.Everyone) + val formatted = formatter.formatDisplayText(MentionType.Everyone) + assertThat(mentionSpan.displayText.toString()).isEqualTo(formatted) + } + + @Test + fun `pillify - adds pills for message permalinks`() { + val text = "Check this message: https://matrix.to/#/!roomid:server.com/$123" + val roomId = RoomId("!roomid:server.com") + val eventId = EventId("$123") + val formatter = FakeMentionSpanFormatter() + val helper = aTextPillificationHelper( + permalinkParser = FakePermalinkParser(result = { + PermalinkData.RoomLink( + roomIdOrAlias = RoomIdOrAlias.Id(roomId), + eventId = eventId + ) + }), + permalinkBuilder = FakePermalinkBuilder(), + mentionSpanFormatter = formatter, + ) + val pillified = helper.pillify(text) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(1) + val mentionSpan = mentionSpans.first() + assertThat(mentionSpan.type).isInstanceOf(MentionType.Message::class.java) + val messageType = mentionSpan.type as MentionType.Message + assertThat(messageType.roomIdOrAlias).isEqualTo(roomId.toRoomIdOrAlias()) + assertThat(messageType.eventId).isEqualTo(eventId) + val formatted = formatter.formatDisplayText(MentionType.Message(roomId.toRoomIdOrAlias(), eventId)) + assertThat(mentionSpan.displayText.toString()).isEqualTo(formatted) + } + + @Test + fun `pillify - with pillifyPermalinks false does not add pills for permalinks`() { + val text = "Check this message: https://matrix.to/#/!roomid:server.com/$123" + val roomId = RoomId("!roomid:server.com") + val eventId = EventId("$123") + val formatter = FakeMentionSpanFormatter() + val helper = aTextPillificationHelper( + permalinkParser = FakePermalinkParser(result = { + PermalinkData.RoomLink( + roomIdOrAlias = RoomIdOrAlias.Id(roomId), + eventId = eventId + ) + }), + permalinkBuilder = FakePermalinkBuilder(), + mentionSpanFormatter = formatter, + ) + val pillified = helper.pillify(text, pillifyPermalinks = false) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).isEmpty() + } + + @Test + fun `pillify - with pillifyPermalinks false still adds pills for matrix patterns`() { + val text = "A @user:server.com mention and a permalink https://matrix.to/#/!roomid:server.com/$123" + val userId = UserId("@user:server.com") + val formatter = FakeMentionSpanFormatter() + val helper = aTextPillificationHelper( + permalinkParser = FakePermalinkParser(result = { + PermalinkData.UserLink(userId) + }), + permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { + Result.success("https://matrix.to/#/@user:server.com") + }), + mentionSpanFormatter = formatter, + ) + val pillified = helper.pillify(text, pillifyPermalinks = false) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(1) + val mentionSpan = mentionSpans.first() + assertThat(mentionSpan.type).isInstanceOf(MentionType.User::class.java) + val userType = mentionSpan.type as MentionType.User + assertThat(userType.userId).isEqualTo(userId) + } + + @Test + fun `pillify - with pillifyPermalinks true adds pills for both matrix patterns and permalinks`() { + val text = "A @user:server.com mention and a permalink https://matrix.to/#/!roomid:server.com/$123" + val userId = UserId("@user:server.com") + val roomId = RoomId("!roomid:server.com") + val eventId = EventId("$123") + val formatter = FakeMentionSpanFormatter() + val permalinkParser = FakePermalinkParser(result = { url -> + if (url.contains("matrix.to")) { + PermalinkData.RoomLink( + roomIdOrAlias = RoomIdOrAlias.Id(roomId), + eventId = eventId + ) + } else { + PermalinkData.UserLink(userId) + } + }) + val helper = aTextPillificationHelper( + permalinkParser = permalinkParser, + permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { + Result.success("https://matrix.to/#/@user:server.com") + }), + mentionSpanFormatter = formatter, + ) + val pillified = helper.pillify(text, pillifyPermalinks = true) + val mentionSpans = pillified.getMentionSpans() + assertThat(mentionSpans).hasSize(2) + + // Check that we have both a user mention and a message mention + val types = mentionSpans.map { it.type::class.java } + assertThat(types).contains(MentionType.User::class.java) + assertThat(types).contains(MentionType.Message::class.java) + + // Verify the user mention + val userMention = mentionSpans.first { it.type is MentionType.User }.type as MentionType.User + assertThat(userMention.userId).isEqualTo(userId) + + // Verify the message mention + val messageMention = mentionSpans.first { it.type is MentionType.Message }.type as MentionType.Message + assertThat(messageMention.roomIdOrAlias).isEqualTo(roomId.toRoomIdOrAlias()) + assertThat(messageMention.eventId).isEqualTo(eventId) } private fun aTextPillificationHelper( - permalinkparser: PermalinkParser = FakePermalinkParser(), + permalinkParser: PermalinkParser = FakePermalinkParser(), permalinkBuilder: FakePermalinkBuilder = FakePermalinkBuilder(), - mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkparser), - roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), - ) = DefaultTextPillificationHelper( - mentionSpanProvider = mentionSpanProvider, - permalinkBuilder = permalinkBuilder, - permalinkParser = permalinkparser, - roomMemberProfilesCache = roomMemberProfilesCache, - ) + mentionSpanFormatter: MentionSpanFormatter = FakeMentionSpanFormatter(), + ): TextPillificationHelper { + val mentionSpanProvider = MentionSpanProvider( + permalinkParser = permalinkParser, + mentionSpanFormatter = mentionSpanFormatter, + mentionSpanTheme = MentionSpanTheme(A_USER_ID), + ) + return DefaultTextPillificationHelper( + mentionSpanProvider = mentionSpanProvider, + permalinkBuilder = permalinkBuilder, + permalinkParser = permalinkParser, + ) + } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt new file mode 100644 index 0000000000..a8128bf622 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.utils + +import io.element.android.libraries.textcomposer.mentions.MentionSpanFormatter +import io.element.android.libraries.textcomposer.mentions.MentionType + +class FakeMentionSpanFormatter( + private val formatLambda: (MentionType) -> CharSequence = { type -> type.toString() }, +) : MentionSpanFormatter { + override fun formatDisplayText(mentionType: MentionType): CharSequence { + return formatLambda(mentionType) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt index d8c62bca40..bbc7c7a728 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt @@ -8,9 +8,9 @@ package io.element.android.features.messages.impl.utils class FakeTextPillificationHelper( - private val pillifyLambda: (CharSequence) -> CharSequence = { it } + private val pillifyLambda: (CharSequence, Boolean) -> CharSequence = { text, _ -> text } ) : TextPillificationHelper { - override fun pillify(text: CharSequence): CharSequence { - return pillifyLambda(text) + override fun pillify(text: CharSequence, pillifyPermalinks: Boolean): CharSequence { + return pillifyLambda(text, pillifyPermalinks) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index e9ffd00b23..5c3e694062 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -21,6 +21,7 @@ import io.element.android.features.messages.impl.messagecomposer.aReplyMode import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.media.AudioInfo +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer @@ -60,7 +61,7 @@ class VoiceMessageComposerPresenterTest { ) private val analyticsService = FakeAnalyticsService() private val sendVoiceMessageResult = - lambdaRecorder, ProgressCallback?, Result> { _, _, _, _ -> + lambdaRecorder, ProgressCallback?, ReplyParameters?, Result> { _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } private val matrixRoom = FakeMatrixRoom( diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt index 38f10071e9..b9ea0d2416 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt @@ -9,14 +9,13 @@ package io.element.android.features.messages.test.timeline import androidx.compose.runtime.Composable import io.element.android.features.messages.api.timeline.HtmlConverterProvider -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.wysiwyg.utils.HtmlConverter class FakeHtmlConverterProvider( private val transform: (String) -> CharSequence = { it }, ) : HtmlConverterProvider { @Composable - override fun Update(currentUserId: UserId) = Unit + override fun Update() = Unit override fun provide(): HtmlConverter { return object : HtmlConverter { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt index f89d81685f..3d8fc3a0aa 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt @@ -33,7 +33,9 @@ class MigrationPresenter @Inject constructor( @Composable override fun present(): MigrationState { - val migrationStoreVersion by migrationStore.applicationMigrationVersion().collectAsState(initial = null) + val migrationStoreVersion by remember { + migrationStore.applicationMigrationVersion() + }.collectAsState(initial = null) var migrationAction: AsyncData by remember { mutableStateOf(AsyncData.Uninitialized) } // Uncomment this block to run the migration everytime diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index 13e37e0a54..c59cd5684b 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -26,6 +26,7 @@ setupAnvil() dependencies { implementation(projects.appconfig) + implementation(projects.features.rageshake.api) implementation(projects.libraries.core) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt index 8d33f60393..481ad6edc6 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt @@ -10,7 +10,9 @@ package io.element.android.features.onboarding.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember import io.element.android.appconfig.OnBoardingConfig +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -24,16 +26,19 @@ import javax.inject.Inject class OnBoardingPresenter @Inject constructor( private val buildMeta: BuildMeta, private val featureFlagService: FeatureFlagService, + private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, ) : Presenter { @Composable override fun present(): OnBoardingState { val canLoginWithQrCode by produceState(initialValue = false) { value = featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin) } + val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() } return OnBoardingState( productionApplicationName = buildMeta.productionApplicationName, canLoginWithQrCode = canLoginWithQrCode, canCreateAccount = OnBoardingConfig.CAN_CREATE_ACCOUNT, + canReportBug = canReportBug, ) } } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt index 6ffb80c2bd..3a5afb741c 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt @@ -11,4 +11,5 @@ data class OnBoardingState( val productionApplicationName: String, val canLoginWithQrCode: Boolean, val canCreateAccount: Boolean, + val canReportBug: Boolean, ) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt index 25dc697782..d65b6aa2bf 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt @@ -16,15 +16,18 @@ open class OnBoardingStateProvider : PreviewParameterProvider { anOnBoardingState(canLoginWithQrCode = true), anOnBoardingState(canCreateAccount = true), anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true), + anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true), ) } fun anOnBoardingState( productionApplicationName: String = "Element", canLoginWithQrCode: Boolean = false, - canCreateAccount: Boolean = false + canCreateAccount: Boolean = false, + canReportBug: Boolean = false, ) = OnBoardingState( productionApplicationName = productionApplicationName, canLoginWithQrCode = canLoginWithQrCode, - canCreateAccount = canCreateAccount + canCreateAccount = canCreateAccount, + canReportBug = canReportBug, ) 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 95a8e2618d..44ab2d84a5 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 @@ -144,8 +144,8 @@ private fun OnBoardingButtons( text = stringResource(id = signInButtonStringRes), onClick = onSignIn, modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.onBoardingSignIn) + .fillMaxWidth() + .testTag(TestTags.onBoardingSignIn) ) if (state.canCreateAccount) { TextButton( @@ -155,15 +155,17 @@ private fun OnBoardingButtons( .fillMaxWidth() ) } - // Add a report problem text button. Use a Text since we need a special theme here. - Text( - modifier = Modifier + if (state.canReportBug) { + // Add a report problem text button. Use a Text since we need a special theme here. + Text( + modifier = Modifier .padding(16.dp) .clickable(onClick = onReportProblem), - text = stringResource(id = CommonStrings.common_report_a_problem), - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, - ) + text = stringResource(id = CommonStrings.common_report_a_problem), + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } } } diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt index e98fbff3db..b692d291b5 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -16,6 +16,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -38,6 +39,7 @@ class OnBoardingPresenterTest { val presenter = OnBoardingPresenter( buildMeta = buildMeta, featureFlagService = featureFlagService, + rageshakeFeatureAvailability = { true }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -46,7 +48,21 @@ class OnBoardingPresenterTest { assertThat(initialState.canLoginWithQrCode).isFalse() assertThat(initialState.productionApplicationName).isEqualTo("B") assertThat(initialState.canCreateAccount).isEqualTo(OnBoardingConfig.CAN_CREATE_ACCOUNT) + assertThat(initialState.canReportBug).isTrue() assertThat(awaitItem().canLoginWithQrCode).isTrue() } } + + @Test + fun `present - rageshake not available`() = runTest { + val presenter = OnBoardingPresenter( + buildMeta = aBuildMeta(), + featureFlagService = FakeFeatureFlagService(), + rageshakeFeatureAvailability = { false }, + ) + presenter.test { + skipItems(1) + assertThat(awaitItem().canReportBug).isFalse() + } + } } diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt index 25cb075697..955bbea1f1 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt @@ -10,6 +10,7 @@ package io.element.android.features.onboarding.impl import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled @@ -76,13 +77,28 @@ class OnboardingViewTest { fun `clicking on report a problem calls the sign in callback`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = anOnBoardingState(), + state = anOnBoardingState( + canReportBug = true, + ), onReportProblem = callback, ) + val text = rule.activity.getString(CommonStrings.common_report_a_problem) + rule.onNodeWithText(text).assertExists() rule.clickOn(CommonStrings.common_report_a_problem) } } + @Test + fun `cannot report a problem when the feature is disabled`() { + rule.setOnboardingView( + state = anOnBoardingState( + canReportBug = false, + ), + ) + val text = rule.activity.getString(CommonStrings.common_report_a_problem) + rule.onNodeWithText(text).assertDoesNotExist() + } + private fun AndroidComposeTestRule.setOnboardingView( state: OnBoardingState, onSignInWithQrCode: () -> Unit = EnsureNeverCalled(), diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt index 00f68e923b..d4da6a76b4 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt @@ -40,7 +40,7 @@ class PollHistoryPresenter @Inject constructor( @Composable override fun present(): PollHistoryState { val timeline = room.liveTimeline - val paginationState by timeline.paginationStatus(Timeline.PaginationDirection.BACKWARDS).collectAsState() + val paginationState by timeline.backwardPaginationStatus.collectAsState() val pollHistoryItemsFlow = remember { timeline.timelineItems.map { items -> pollHistoryItemFactory.create(items) diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index bbe96ecb80..8d34d55559 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -1,3 +1,5 @@ +import config.BuildTimeConfig +import extension.buildConfigFieldStr import extension.setupAnvil /* @@ -19,6 +21,25 @@ android { isIncludeAndroidResources = true } } + + buildFeatures { + buildConfig = true + } + + defaultConfig { + buildConfigFieldStr( + name = "URL_COPYRIGHT", + value = BuildTimeConfig.URL_COPYRIGHT ?: "https://element.io/copyright", + ) + buildConfigFieldStr( + name = "URL_ACCEPTABLE_USE", + value = BuildTimeConfig.URL_ACCEPTABLE_USE ?: "https://element.io/acceptable-use-policy-terms", + ) + buildConfigFieldStr( + name = "URL_PRIVACY", + value = BuildTimeConfig.URL_PRIVACY ?: "https://element.io/privacy", + ) + } } setupAnvil() @@ -53,6 +74,7 @@ dependencies { implementation(projects.features.licenses.api) implementation(projects.features.logout.api) implementation(projects.features.deactivation.api) + implementation(projects.features.invite.api) implementation(projects.features.roomlist.api) implementation(projects.services.analytics.api) implementation(projects.services.analytics.compose) @@ -82,6 +104,7 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.features.ftue.test) + testImplementation(projects.features.invite.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) testImplementation(projects.features.logout.test) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt index a94e1637ba..a5de31f05d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt @@ -8,11 +8,12 @@ package io.element.android.features.preferences.impl.about import androidx.annotation.StringRes +import io.element.android.features.preferences.impl.BuildConfig import io.element.android.libraries.ui.strings.CommonStrings -private const val COPYRIGHT_URL = "https://element.io/copyright" -private const val USE_POLICY_URL = "https://element.io/acceptable-use-policy-terms" -private const val PRIVACY_URL = "https://element.io/privacy" +private const val COPYRIGHT_URL = BuildConfig.URL_COPYRIGHT +private const val USE_POLICY_URL = BuildConfig.URL_ACCEPTABLE_USE +private const val PRIVACY_URL = BuildConfig.URL_PRIVACY sealed class ElementLegal( @StringRes val titleRes: Int, 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 b256997d1c..80f9a98b0c 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 @@ -29,19 +29,18 @@ class AdvancedSettingsPresenter @Inject constructor( @Composable override fun present(): AdvancedSettingsState { val localCoroutineScope = rememberCoroutineScope() - val isDeveloperModeEnabled by appPreferencesStore - .isDeveloperModeEnabledFlow() - .collectAsState(initial = false) - val isSharePresenceEnabled by sessionPreferencesStore - .isSharePresenceEnabled() - .collectAsState(initial = true) - val doesCompressMedia by sessionPreferencesStore - .doesCompressMedia() - .collectAsState(initial = true) + val isDeveloperModeEnabled by remember { + appPreferencesStore.isDeveloperModeEnabledFlow() + }.collectAsState(initial = false) + val isSharePresenceEnabled by remember { + sessionPreferencesStore.isSharePresenceEnabled() + }.collectAsState(initial = true) + val doesCompressMedia by remember { + sessionPreferencesStore.doesCompressMedia() + }.collectAsState(initial = true) val theme by remember { appPreferencesStore.getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) + }.collectAsState(initial = Theme.System) var showChangeThemeDialog by remember { mutableStateOf(false) } fun handleEvents(event: AdvancedSettingsEvents) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt index 19365e307c..c673276a17 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt @@ -44,17 +44,17 @@ class BlockedUsersPresenter @Inject constructor( mutableStateOf(AsyncAction.Uninitialized) } - val renderBlockedUsersDetail = featureFlagService - .isFeatureEnabledFlow(FeatureFlags.ShowBlockedUsersDetails) - .collectAsState(initial = false) + val renderBlockedUsersDetail by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.ShowBlockedUsersDetails) + }.collectAsState(initial = false) val ignoredUserIds by matrixClient.ignoredUsersFlow.collectAsState() val ignoredMatrixUser by produceState( initialValue = ignoredUserIds.map { MatrixUser(userId = it) }, - key1 = renderBlockedUsersDetail.value, + key1 = renderBlockedUsersDetail, key2 = ignoredUserIds ) { value = ignoredUserIds.map { - if (renderBlockedUsersDetail.value) { + if (renderBlockedUsersDetail) { matrixClient.getProfile(it).getOrNull() } else { null diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt index 4a9437a188..74c673bc8a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt @@ -9,11 +9,13 @@ package io.element.android.features.preferences.impl.developer import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents data class SetHideImagesAndVideos(val value: Boolean) : DeveloperSettingsEvents data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents + data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : DeveloperSettingsEvents data object ClearCache : DeveloperSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index ee57d3b8e7..5159983a6e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshots.SnapshotStateMap @@ -34,9 +35,13 @@ import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.net.URL @@ -66,17 +71,25 @@ class DeveloperSettingsPresenter @Inject constructor( val clearCacheAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - val customElementCallBaseUrl by appPreferencesStore - .getCustomElementCallBaseUrlFlow() - .collectAsState(initial = null) - val hideImagesAndVideos by appPreferencesStore - .doesHideImagesAndVideosFlow() - .collectAsState(initial = false) + val customElementCallBaseUrl by remember { + appPreferencesStore + .getCustomElementCallBaseUrlFlow() + }.collectAsState(initial = null) + val hideImagesAndVideos by remember { + appPreferencesStore + .doesHideImagesAndVideosFlow() + }.collectAsState(initial = false) val tracingLogLevelFlow = remember { appPreferencesStore.getTracingLogLevelFlow().map { AsyncData.Success(it.toLogLevelItem()) } } val tracingLogLevel by tracingLogLevelFlow.collectAsState(initial = AsyncData.Uninitialized) + val tracingLogPacks by produceState(persistentListOf()) { + appPreferencesStore.getTracingLogPacksFlow() + // Sort the entries alphabetically by its title + .map { it.sortedBy { it.title }.toPersistentList() } + .collectLatest { value = it } + } LaunchedEffect(Unit) { FeatureFlags.entries @@ -121,6 +134,15 @@ class DeveloperSettingsPresenter @Inject constructor( is DeveloperSettingsEvents.SetTracingLogLevel -> coroutineScope.launch { appPreferencesStore.setTracingLogLevel(event.logLevel.toLogLevel()) } + is DeveloperSettingsEvents.ToggleTracingLogPack -> coroutineScope.launch { + val currentPacks = tracingLogPacks.toMutableSet() + if (currentPacks.contains(event.logPack)) { + currentPacks.remove(event.logPack) + } else { + currentPacks.add(event.logPack) + } + appPreferencesStore.setTracingLogPacks(currentPacks) + } } } @@ -135,6 +157,7 @@ class DeveloperSettingsPresenter @Inject constructor( ), hideImagesAndVideos = hideImagesAndVideos, tracingLogLevel = tracingLogLevel, + tracingLogPacks = tracingLogPacks, eventSink = ::handleEvents ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index 6bc8743439..efcfcd01d4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -12,6 +12,7 @@ import io.element.android.features.rageshake.api.preferences.RageshakePreference import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import kotlinx.collections.immutable.ImmutableList data class DeveloperSettingsState( @@ -22,6 +23,7 @@ data class DeveloperSettingsState( val customElementCallBaseUrlState: CustomElementCallBaseUrlState, val hideImagesAndVideos: Boolean, val tracingLogLevel: AsyncData, + val tracingLogPacks: ImmutableList, val eventSink: (DeveloperSettingsEvents) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index 064a208f49..18151d32c7 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -13,6 +13,8 @@ import io.element.android.features.rageshake.api.preferences.aRageshakePreferenc import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.aFeatureUiModelList +import io.element.android.libraries.matrix.api.tracing.TraceLogPack +import kotlinx.collections.immutable.toPersistentList open class DeveloperSettingsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -33,6 +35,7 @@ fun aDeveloperSettingsState( clearCacheAction: AsyncAction = AsyncAction.Uninitialized, customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), hideImagesAndVideos: Boolean = false, + traceLogPacks: List = emptyList(), eventSink: (DeveloperSettingsEvents) -> Unit = {}, ) = DeveloperSettingsState( features = aFeatureUiModelList(), @@ -42,6 +45,7 @@ fun aDeveloperSettingsState( customElementCallBaseUrlState = customElementCallBaseUrlState, hideImagesAndVideos = hideImagesAndVideos, tracingLogLevel = AsyncData.Success(LogLevelItem.INFO), + tracingLogPacks = traceLogPacks.toPersistentList(), eventSink = eventSink, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index de368b9a65..e335c0a6cd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -7,6 +7,7 @@ package io.element.android.features.preferences.impl.developer +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.progressSemantics import androidx.compose.foundation.text.KeyboardOptions @@ -16,6 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType 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.preferences.impl.R import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView @@ -32,6 +34,7 @@ import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.featureflag.ui.FeatureListView import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.toPersistentList @@ -56,6 +59,7 @@ fun DeveloperSettingsView( FeatureListContent(state) } ElementCallCategory(state = state) + PreferenceCategory(title = "Rust SDK") { PreferenceDropdown( title = "Tracing log level", @@ -67,6 +71,22 @@ fun DeveloperSettingsView( } ) } + PreferenceCategory(title = "Enable trace logs per SDK feature") { + Text( + text = "Requires app reboot", + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp) + ) + for (logPack in TraceLogPack.entries) { + PreferenceSwitch( + title = logPack.title, + isChecked = state.tracingLogPacks.contains(logPack), + onCheckedChange = { isChecked -> state.eventSink(DeveloperSettingsEvents.ToggleTracingLogPack(logPack, isChecked)) } + ) + } + } + PreferenceCategory(title = "Showkase") { ListItem( headlineContent = { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 317c6e796e..3c1c81e758 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -58,9 +58,9 @@ class NotificationSettingsPresenter @Inject constructor( val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() - val appNotificationsEnabled = userPushStore - .getNotificationEnabledForDevice() - .collectAsState(initial = false) + val appNotificationsEnabled by remember { + userPushStore.getNotificationEnabledForDevice() + }.collectAsState(initial = false) val matrixSettings: MutableState = remember { mutableStateOf(NotificationSettingsState.MatrixSettings.Uninitialized) @@ -158,7 +158,7 @@ class NotificationSettingsPresenter @Inject constructor( matrixSettings = matrixSettings.value, appSettings = NotificationSettingsState.AppSettings( systemNotificationsEnabled = systemNotificationsEnabled.value, - appNotificationsEnabled = appNotificationsEnabled.value + appNotificationsEnabled = appNotificationsEnabled, ), changeNotificationSettingAction = changeNotificationSettingAction.value, currentPushDistributor = currentDistributor, 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 ae5267de0f..15a55f41ca 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 @@ -18,6 +18,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState @@ -44,6 +45,7 @@ class PreferencesRootPresenter @Inject constructor( private val indicatorService: IndicatorService, private val directLogoutPresenter: Presenter, private val showDeveloperSettingsProvider: ShowDeveloperSettingsProvider, + private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, ) : Presenter { @Composable override fun present(): PreferencesRootState { @@ -79,6 +81,7 @@ class PreferencesRootPresenter @Inject constructor( var canDeactivateAccount by remember { mutableStateOf(false) } + val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() } LaunchedEffect(Unit) { canDeactivateAccount = matrixClient.canDeactivateAccount() } @@ -114,6 +117,7 @@ class PreferencesRootPresenter @Inject constructor( accountManagementUrl = accountManagementUrl.value, devicesManagementUrl = devicesManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, + canReportBug = canReportBug, showDeveloperSettings = showDeveloperSettings, canDeactivateAccount = canDeactivateAccount, showNotificationSettings = showNotificationSettings.value, 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 9a16e6f65b..2e5cb4fa14 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 @@ -20,6 +20,7 @@ data class PreferencesRootState( val showSecureBackupBadge: Boolean, val accountManagementUrl: String?, val devicesManagementUrl: String?, + val canReportBug: Boolean, val showAnalyticsSettings: Boolean, val showDeveloperSettings: Boolean, val canDeactivateAccount: 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 e4ca550777..43307e9988 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 @@ -25,6 +25,7 @@ fun aPreferencesRootState( accountManagementUrl = "aUrl", devicesManagementUrl = "anOtherUrl", showAnalyticsSettings = true, + canReportBug = true, showDeveloperSettings = true, showNotificationSettings = true, showLockScreenSettings = 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 236d659f33..b63919cd26 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 @@ -202,11 +202,13 @@ private fun ColumnScope.GeneralSection( leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), onClick = onOpenAbout, ) - ListItem( - headlineContent = { Text(stringResource(id = CommonStrings.common_report_a_problem)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChatProblem())), - onClick = onOpenRageShake - ) + if (state.canReportBug) { + ListItem( + headlineContent = { Text(stringResource(id = CommonStrings.common_report_a_problem)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChatProblem())), + onClick = onOpenRageShake + ) + } if (state.showAnalyticsSettings) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_analytics)) }, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 27763db5f5..8a081c0e41 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -11,6 +11,7 @@ import android.content.Context import coil3.SingletonImageLoader import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.ftue.api.state.FtueService +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.preferences.impl.DefaultCacheService import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.ApplicationContext @@ -35,6 +36,7 @@ class DefaultClearCacheUseCase @Inject constructor( private val okHttpClient: Provider, private val ftueService: FtueService, private val pushService: PushService, + private val seenInvitesStore: SeenInvitesStore, ) : ClearCacheUseCase { override suspend fun invoke() = withContext(coroutineDispatchers.io) { // Clear Matrix cache @@ -50,6 +52,7 @@ class DefaultClearCacheUseCase @Inject constructor( context.cacheDir.deleteRecursively() // Clear some settings ftueService.reset() + seenInvitesStore.clear() // Ensure any error will be displayed again pushService.setIgnoreRegistrationError(matrixClient.sessionId, false) // Ensure the app is restarted diff --git a/features/preferences/impl/src/main/res/values-et/translations.xml b/features/preferences/impl/src/main/res/values-et/translations.xml index bf7c028dd3..6c5c1af22d 100644 --- a/features/preferences/impl/src/main/res/values-et/translations.xml +++ b/features/preferences/impl/src/main/res/values-et/translations.xml @@ -8,8 +8,11 @@ "Element Calli kohandatud teenuseaadress" "Seadista kohandatud teenuseaadress Element Calli jaoks." "Vigane url. Palun vaata, et url algaks protokolliga (http/https) ning aadress ise oleks ka õige." + "Peida jututubade kutsetest tunnuspildid" + "Peida meedia eelvaated ajajoonel" "Sellega laadid fotosid ja videoid kiiremini üles ning vähendad andmemahtu" "Optimeeri meedia kvaliteeti" + "Modereerimine ja ohutus" "Tõuketeavituste pakkuja" "Kui soovid Markdown-vormingut käsitsi lisada, siis lülita vormindatud teksti toimeti välja." "Lugemisteatised" diff --git a/features/preferences/impl/src/main/res/values-eu/translations.xml b/features/preferences/impl/src/main/res/values-eu/translations.xml index 4caeb4f6db..62e4da471c 100644 --- a/features/preferences/impl/src/main/res/values-eu/translations.xml +++ b/features/preferences/impl/src/main/res/values-eu/translations.xml @@ -5,6 +5,7 @@ "Aukeratu jakinarazpenak nola jaso" "Garatzaile modua" "Gaitu garatzaileentzako ezaugarrietarako eta funtzionalitateetarako sarbidea izateko." + "Moderazioa eta Segurtasuna" "Push jakinarazpen hornitzailea" "Desgaitu testu aberatseko editorea Markdown eskuz idazteko." "Irakurketa-agiriak" diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml index e47167e32b..348c54771e 100644 --- a/features/preferences/impl/src/main/res/values-it/translations.xml +++ b/features/preferences/impl/src/main/res/values-it/translations.xml @@ -8,8 +8,11 @@ "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." + "Nascondi gli avatar nelle richieste di invito alle stanze" + "Nascondi le anteprime dei media nelle conversazioni" "Carica foto e video più velocemente e riduci l\'utilizzo dei dati" "Ottimizza la qualità dei contenuti multimediali" + "Moderazione e Sicurezza" "Fornitore di notifiche push" "Disattiva l\'editor di testo avanzato per scrivere manualmente in Markdown" "Ricevute di visualizzazione" diff --git a/features/preferences/impl/src/main/res/values-sk/translations.xml b/features/preferences/impl/src/main/res/values-sk/translations.xml index 118cd7d6a5..0a4cdb7c27 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -8,8 +8,11 @@ "Vlastná Element Call základná URL adresa" "Nastaviť vlastnú základnú URL adresu pre Element Call." "Neplatná adresa URL, uistite sa, že ste uviedli protokol (http/https) a správnu adresu." + "Skrytie profilové obrázky v žiadostiach o pozvánku do miestnosti" + "Skryť ukážky médií na časovej osi" "Nahrávajte fotografie a videá rýchlejšie a znížte spotrebu dát" "Optimalizovať kvalitu médií" + "Moderovanie a bezpečnosť" "Poskytovateľ oznámení Push" "Vypnite rozšírený textový editor na ručné písanie Markdown." "Potvrdenia o prečítaní" diff --git a/features/preferences/impl/src/main/res/values-uk/translations.xml b/features/preferences/impl/src/main/res/values-uk/translations.xml index afbbe97e39..4a92a89832 100644 --- a/features/preferences/impl/src/main/res/values-uk/translations.xml +++ b/features/preferences/impl/src/main/res/values-uk/translations.xml @@ -8,8 +8,11 @@ "Користувацька URL-адреса Element Call" "Встановіть URL-адресу для Element Call." "Неправильна URL-адреса. Переконайтеся, що ви вказали протокол (http/https) та правильну адресу." + "Сховати аватари у запитах на запрошення до кімнат" + "Сховати попередній перегляд медіа у стрічці" "Швидше завантажуйте фотографії та відео та зменшуйте використання даних" "Оптимізуйте медіаякість" + "Модерування й безпека" "Постачальник push-сповіщень" "Вимкніть редактор розширеного тексту, щоб вводити Markdown вручну." "Читати журнали" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index 94b70442d9..255c6442d9 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -68,7 +68,7 @@ class DeveloperSettingsViewTest { eventsRecorder.assertSingle(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.dev")) } - @Config(qualifiers = "h1024dp") + @Config(qualifiers = "h1200dp") @Test fun `clicking on open showkase invokes the expected callback`() { val eventsRecorder = EventsRecorder(expectEvents = false) @@ -97,7 +97,7 @@ class DeveloperSettingsViewTest { eventsRecorder.assertSingle(DeveloperSettingsEvents.SetTracingLogLevel(LogLevelItem.DEBUG)) } - @Config(qualifiers = "h1500dp") + @Config(qualifiers = "h1700dp") @Test fun `clicking on clear cache emits the expected event`() { val eventsRecorder = EventsRecorder() 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 72ae8bdb10..8075a43485 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 @@ -13,6 +13,7 @@ import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService @@ -78,6 +79,7 @@ class PreferencesRootPresenterTest { assertThat(loadedState.showLockScreenSettings).isTrue() assertThat(loadedState.showNotificationSettings).isTrue() assertThat(loadedState.canDeactivateAccount).isTrue() + assertThat(loadedState.canReportBug).isTrue() assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState()) assertThat(loadedState.snackbarMessage).isNull() skipItems(1) @@ -92,6 +94,22 @@ class PreferencesRootPresenterTest { } } + @Test + fun `present - cannot report bug`() = runTest { + val matrixClient = FakeMatrixClient( + canDeactivateAccountResult = { true }, + accountManagementUrlResult = { Result.success("") }, + ) + createPresenter( + matrixClient = matrixClient, + rageshakeFeatureAvailability = { false }, + ).test { + val initialState = awaitItem() + assertThat(initialState.canReportBug).isFalse() + skipItems(1) + } + } + @Test fun `present - can deactivate account is false if the Matrix client say so`() = runTest { createPresenter( @@ -146,6 +164,7 @@ class PreferencesRootPresenterTest { matrixClient: FakeMatrixClient = FakeMatrixClient(), sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), showDeveloperSettingsProvider: ShowDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.DEBUG)), + rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true }, ) = PreferencesRootPresenter( matrixClient = matrixClient, sessionVerificationService = sessionVerificationService, @@ -159,5 +178,6 @@ class PreferencesRootPresenterTest { ), directLogoutPresenter = { aDirectLogoutState() }, showDeveloperSettingsProvider = showDeveloperSettingsProvider, + rageshakeFeatureAvailability = rageshakeFeatureAvailability, ) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt index 778db4a4a8..401477d5fc 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt @@ -11,13 +11,16 @@ import androidx.test.platform.app.InstrumentationRegistry import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.ftue.test.FakeFtueService +import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.features.preferences.impl.DefaultCacheService import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.push.test.FakePushService import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import okhttp3.OkHttpClient import org.junit.Test @@ -41,6 +44,8 @@ class DefaultClearCacheUseCaseTest { val pushService = FakePushService( setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda ) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).isNotEmpty() val sut = DefaultClearCacheUseCase( context = InstrumentationRegistry.getInstrumentation().context, matrixClient = matrixClient, @@ -49,6 +54,7 @@ class DefaultClearCacheUseCaseTest { okHttpClient = { OkHttpClient.Builder().build() }, ftueService = ftueService, pushService = pushService, + seenInvitesStore = seenInvitesStore, ) defaultCacheService.clearedCacheEventFlow.test { sut.invoke() @@ -57,6 +63,7 @@ class DefaultClearCacheUseCaseTest { setIgnoreRegistrationErrorLambda.assertions().isCalledOnce() .with(value(matrixClient.sessionId), value(false)) assertThat(awaitItem()).isEqualTo(matrixClient.sessionId) + assertThat(seenInvitesStore.seenRoomIds().first()).isEmpty() } } } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt new file mode 100644 index 0000000000..34e740d4ab --- /dev/null +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rageshake.api + +fun interface RageshakeFeatureAvailability { + fun isAvailable(): Boolean +} diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt index 9207d5ff38..40a9d0282b 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt @@ -8,6 +8,7 @@ package io.element.android.features.rageshake.api.preferences data class RageshakePreferencesState( + val isFeatureEnabled: Boolean, val isEnabled: Boolean, val isSupported: Boolean, val sensitivity: Float, diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt index d18123cd8d..a98c75a02a 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt @@ -12,14 +12,21 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider open class RageshakePreferencesStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aRageshakePreferencesState().copy(isEnabled = true, isSupported = true, sensitivity = 0.5f), - aRageshakePreferencesState().copy(isEnabled = true, isSupported = false, sensitivity = 0.5f), + aRageshakePreferencesState(isEnabled = true, isSupported = true, sensitivity = 0.5f), + aRageshakePreferencesState(isEnabled = true, isSupported = false, sensitivity = 0.5f), ) } -fun aRageshakePreferencesState() = RageshakePreferencesState( - isEnabled = false, - isSupported = true, - sensitivity = 0.3f, - eventSink = {} +fun aRageshakePreferencesState( + isFeatureEnabled: Boolean = true, + isEnabled: Boolean = false, + isSupported: Boolean = true, + sensitivity: Float = 0.3f, + eventSink: (RageshakePreferencesEvents) -> Unit = {} +) = RageshakePreferencesState( + isFeatureEnabled = isFeatureEnabled, + isEnabled = isEnabled, + isSupported = isSupported, + sensitivity = sensitivity, + eventSink = eventSink, ) diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt index 0841e097ba..86f1c05247 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt @@ -36,28 +36,30 @@ fun RageshakePreferencesView( } Column(modifier = modifier) { - PreferenceCategory(title = stringResource(id = R.string.settings_rageshake)) { - if (state.isSupported) { - PreferenceSwitch( - title = stringResource(id = CommonStrings.preference_rageshake), - isChecked = state.isEnabled, - onCheckedChange = ::onEnabledChanged - ) - PreferenceSlide( - title = stringResource(id = R.string.settings_rageshake_detection_threshold), - // summary = stringResource(id = CommonStrings.settings_rageshake_detection_threshold_summary), - value = state.sensitivity, - enabled = state.isEnabled, - // 5 possible values - steps are in ]0, 1[ - steps = 3, - onValueChange = ::onSensitivityChanged - ) - } else { - ListItem( - headlineContent = { - Text("Rageshaking is not supported by your device") - }, - ) + if (state.isFeatureEnabled) { + PreferenceCategory(title = stringResource(id = R.string.settings_rageshake)) { + if (state.isSupported) { + PreferenceSwitch( + title = stringResource(id = CommonStrings.preference_rageshake), + isChecked = state.isEnabled, + onCheckedChange = ::onEnabledChanged + ) + PreferenceSlide( + title = stringResource(id = R.string.settings_rageshake_detection_threshold), + // summary = stringResource(id = CommonStrings.settings_rageshake_detection_threshold_summary), + value = state.sensitivity, + enabled = state.isEnabled, + // 5 possible values - steps are in ]0, 1[ + steps = 3, + onValueChange = ::onSensitivityChanged + ) + } else { + ListItem( + headlineContent = { + Text("Rageshaking is not supported by your device") + }, + ) + } } } } diff --git a/features/rageshake/api/src/main/res/values-nb/translations.xml b/features/rageshake/api/src/main/res/values-nb/translations.xml index ca37b6e6b6..dfc2d710ad 100644 --- a/features/rageshake/api/src/main/res/values-nb/translations.xml +++ b/features/rageshake/api/src/main/res/values-nb/translations.xml @@ -2,5 +2,6 @@ "%1$s krasjet sist gang den ble brukt. Vil du dele en krasjrapport med oss?" "Du ser ut til å riste på telefonen i frustrasjon. Vil du åpne feilrapportskjermen?" + "Rageshake" "Gjenkjenningsterskel" diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt new file mode 100644 index 0000000000..5f7548e9ec --- /dev/null +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rageshake.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appconfig.RageshakeConfig +import io.element.android.appconfig.isEnabled +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultRageshakeFeatureAvailability @Inject constructor() : RageshakeFeatureAvailability { + override fun isAvailable(): Boolean { + return RageshakeConfig.isEnabled + } +} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index 88bba32f23..455cefdb24 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -16,10 +16,10 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable -import io.element.android.features.rageshake.api.crash.CrashDataStore import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener -import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder +import io.element.android.features.rageshake.impl.crash.CrashDataStore +import io.element.android.features.rageshake.impl.screenshot.ScreenshotHolder import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope @@ -64,9 +64,9 @@ class BugReportPresenter @Inject constructor( screenshotHolder.getFileUri() ) } - val crashInfo: String by crashDataStore - .crashInfo() - .collectAsState(initial = "") + val crashInfo: String by remember { + crashDataStore.crashInfo() + }.collectAsState(initial = "") val sendingProgress = remember { mutableFloatStateOf(0f) diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt similarity index 88% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDataStore.kt rename to features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt index 5a13f44a0a..3d6df9a424 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.api.crash +package io.element.android.features.rageshake.impl.crash import kotlinx.coroutines.flow.Flow diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt index 749ba4255c..fffb87722a 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt @@ -9,15 +9,17 @@ package io.element.android.features.rageshake.impl.crash import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.rageshake.api.crash.CrashDataStore +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.crash.CrashDetectionEvents import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter import io.element.android.features.rageshake.api.crash.CrashDetectionState import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import javax.inject.Inject @@ -25,12 +27,18 @@ import javax.inject.Inject class DefaultCrashDetectionPresenter @Inject constructor( private val buildMeta: BuildMeta, private val crashDataStore: CrashDataStore, -) : - CrashDetectionPresenter { + private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, +) : CrashDetectionPresenter { @Composable override fun present(): CrashDetectionState { val localCoroutineScope = rememberCoroutineScope() - val crashDetected = crashDataStore.appHasCrashed().collectAsState(initial = false) + val crashDetected = remember { + if (rageshakeFeatureAvailability.isAvailable()) { + crashDataStore.appHasCrashed() + } else { + flowOf(false) + } + }.collectAsState(false) fun handleEvents(event: CrashDetectionEvents) { when (event) { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt index 2cdea62b1a..0b86bb4ef0 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt @@ -15,7 +15,6 @@ 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.rageshake.api.crash.CrashDataStore import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt index 29785d34fe..1a8aed7051 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt @@ -20,9 +20,9 @@ import io.element.android.features.rageshake.api.detection.RageshakeDetectionPre import io.element.android.features.rageshake.api.detection.RageshakeDetectionState import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter -import io.element.android.features.rageshake.api.rageshake.RageShake import io.element.android.features.rageshake.api.screenshot.ImageResult -import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder +import io.element.android.features.rageshake.impl.rageshake.RageShake +import io.element.android.features.rageshake.impl.screenshot.ScreenshotHolder import io.element.android.libraries.di.AppScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -75,7 +75,8 @@ class DefaultRageshakeDetectionPresenter @Inject constructor( LaunchedEffect(preferencesState.sensitivity) { rageShake.setSensitivity(preferencesState.sensitivity) } - val shouldStart = preferencesState.isEnabled && + val shouldStart = preferencesState.isFeatureEnabled && + preferencesState.isEnabled && preferencesState.isSupported && isStarted.value && !takeScreenshot.value && diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt index e129bed8d5..b4f45a3bdc 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt @@ -10,15 +10,18 @@ package io.element.android.features.rageshake.impl.preferences import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState -import io.element.android.features.rageshake.api.rageshake.RageShake -import io.element.android.features.rageshake.api.rageshake.RageshakeDataStore +import io.element.android.features.rageshake.impl.rageshake.RageShake +import io.element.android.features.rageshake.impl.rageshake.RageshakeDataStore import io.element.android.libraries.di.AppScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -28,6 +31,7 @@ import javax.inject.Inject class DefaultRageshakePreferencesPresenter @Inject constructor( private val rageshake: RageShake, private val rageshakeDataStore: RageshakeDataStore, + private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, ) : RageshakePreferencesPresenter { @Composable override fun present(): RageshakePreferencesState { @@ -35,13 +39,14 @@ class DefaultRageshakePreferencesPresenter @Inject constructor( val isSupported: MutableState = rememberSaveable { mutableStateOf(rageshake.isAvailable()) } - val isEnabled = rageshakeDataStore - .isEnabled() - .collectAsState(initial = false) + val isFeatureAvailable = remember { rageshakeFeatureAvailability.isAvailable() } + val isEnabled by remember { + rageshakeDataStore.isEnabled() + }.collectAsState(initial = false) - val sensitivity = rageshakeDataStore - .sensitivity() - .collectAsState(initial = 0f) + val sensitivity by remember { + rageshakeDataStore.sensitivity() + }.collectAsState(initial = 0f) fun handleEvents(event: RageshakePreferencesEvents) { when (event) { @@ -51,9 +56,10 @@ class DefaultRageshakePreferencesPresenter @Inject constructor( } return RageshakePreferencesState( - isEnabled = isEnabled.value, + isFeatureEnabled = isFeatureAvailable, + isEnabled = isEnabled, isSupported = isSupported.value, - sensitivity = sensitivity.value, + sensitivity = sensitivity, eventSink = ::handleEvents ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt index 21c5fffdb7..651b71c079 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt @@ -13,7 +13,6 @@ import android.hardware.SensorManager import androidx.core.content.getSystemService import com.squareup.anvil.annotations.ContributesBinding import com.squareup.seismic.ShakeDetector -import io.element.android.features.rageshake.api.rageshake.RageShake import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt index f9379de16d..9d7171b8a0 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt @@ -15,7 +15,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.rageshake.api.rageshake.RageshakeDataStore import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/rageshake/RageShake.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt similarity index 91% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/rageshake/RageShake.kt rename to features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt index 548d11a41d..d75d5e5666 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/rageshake/RageShake.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.api.rageshake +package io.element.android.features.rageshake.impl.rageshake interface RageShake { /** diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/rageshake/RageshakeDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt similarity index 88% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/rageshake/RageshakeDataStore.kt rename to features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt index a59c3670d8..f13419bfb2 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/rageshake/RageshakeDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.api.rageshake +package io.element.android.features.rageshake.impl.rageshake import kotlinx.coroutines.flow.Flow 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 4ec8500417..36ece91408 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 @@ -13,10 +13,10 @@ import androidx.core.net.toFile import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.appconfig.RageshakeConfig -import io.element.android.features.rageshake.api.crash.CrashDataStore import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener -import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder +import io.element.android.features.rageshake.impl.crash.CrashDataStore +import io.element.android.features.rageshake.impl.screenshot.ScreenshotHolder import io.element.android.libraries.androidutils.file.compressFile import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt index 270c5628b3..dd3674ab26 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt @@ -11,7 +11,6 @@ import android.content.Context import android.graphics.Bitmap import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder import io.element.android.libraries.androidutils.bitmap.writeBitmap import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.di.AppScope diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/ScreenshotHolder.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt similarity index 84% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/ScreenshotHolder.kt rename to features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt index 9a7e64da59..c746a573d3 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/ScreenshotHolder.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.api.screenshot +package io.element.android.features.rageshake.impl.screenshot import android.graphics.Bitmap diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt index cae6f6e500..027e2fb38c 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt @@ -11,13 +11,13 @@ 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.rageshake.api.crash.CrashDataStore import io.element.android.features.rageshake.api.reporter.BugReporter -import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder -import io.element.android.features.rageshake.test.crash.A_CRASH_DATA -import io.element.android.features.rageshake.test.crash.FakeCrashDataStore -import io.element.android.features.rageshake.test.screenshot.A_SCREENSHOT_URI -import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder +import io.element.android.features.rageshake.impl.crash.A_CRASH_DATA +import io.element.android.features.rageshake.impl.crash.CrashDataStore +import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore +import io.element.android.features.rageshake.impl.screenshot.A_SCREENSHOT_URI +import io.element.android.features.rageshake.impl.screenshot.FakeScreenshotHolder +import io.element.android.features.rageshake.impl.screenshot.ScreenshotHolder import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.tests.testutils.WarmUpRule diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/crash/FakeCrashDataStore.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt similarity index 88% rename from features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/crash/FakeCrashDataStore.kt rename to features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt index 145e582dec..1a89a52bf7 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/crash/FakeCrashDataStore.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt @@ -5,9 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.test.crash +package io.element.android.features.rageshake.impl.crash -import io.element.android.features.rageshake.api.crash.CrashDataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt index a5fa6671d7..92dfbb02a2 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt @@ -12,9 +12,9 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.rageshake.api.crash.CrashDetectionEvents +import io.element.android.features.rageshake.impl.crash.A_CRASH_DATA import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter -import io.element.android.features.rageshake.test.crash.A_CRASH_DATA -import io.element.android.features.rageshake.test.crash.FakeCrashDataStore +import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule @@ -51,6 +51,20 @@ class CrashDetectionPresenterTest { } } + @Test + fun `present - initial state crash is ignored if the feature is not available`() = runTest { + val presenter = createPresenter( + FakeCrashDataStore(appHasCrashed = true), + isFeatureAvailable = false, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.crashDetected).isFalse() + } + } + @Test fun `present - reset app has crashed`() = runTest { val presenter = createPresenter( @@ -86,8 +100,10 @@ class CrashDetectionPresenterTest { private fun createPresenter( crashDataStore: FakeCrashDataStore = FakeCrashDataStore(), buildMeta: BuildMeta = aBuildMeta(), + isFeatureAvailable: Boolean = true, ) = DefaultCrashDetectionPresenter( buildMeta = buildMeta, crashDataStore = crashDataStore, + rageshakeFeatureAvailability = { isFeatureAvailable }, ) } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt index 5aa9679b8c..6f433a78d7 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt @@ -15,9 +15,9 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvents import io.element.android.features.rageshake.api.screenshot.ImageResult import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter -import io.element.android.features.rageshake.test.rageshake.FakeRageShake -import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore -import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder +import io.element.android.features.rageshake.impl.rageshake.FakeRageShake +import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataStore +import io.element.android.features.rageshake.impl.screenshot.FakeScreenshotHolder import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk @@ -52,6 +52,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, + rageshakeFeatureAvailability = { true }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -76,6 +77,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, + rageshakeFeatureAvailability = { true }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -101,6 +103,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, + rageshakeFeatureAvailability = { true }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -135,6 +138,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, + rageshakeFeatureAvailability = { true }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -169,6 +173,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, + rageshakeFeatureAvailability = { true }, ) ) moleculeFlow(RecompositionMode.Immediate) { diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt index 796545c859..ba68345440 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt @@ -12,9 +12,9 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents -import io.element.android.features.rageshake.test.rageshake.A_SENSITIVITY -import io.element.android.features.rageshake.test.rageshake.FakeRageShake -import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore +import io.element.android.features.rageshake.impl.rageshake.A_SENSITIVITY +import io.element.android.features.rageshake.impl.rageshake.FakeRageShake +import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataStore import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -28,7 +28,8 @@ class RageshakePreferencesPresenterTest { fun `present - initial state available`() = runTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = true), - FakeRageshakeDataStore(isEnabled = true) + FakeRageshakeDataStore(isEnabled = true), + rageshakeFeatureAvailability = { true }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -44,7 +45,8 @@ class RageshakePreferencesPresenterTest { fun `present - initial state not available`() = runTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = false), - FakeRageshakeDataStore(isEnabled = true) + FakeRageshakeDataStore(isEnabled = true), + rageshakeFeatureAvailability = { true }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -60,7 +62,8 @@ class RageshakePreferencesPresenterTest { fun `present - enable and disable`() = runTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = true), - FakeRageshakeDataStore(isEnabled = true) + FakeRageshakeDataStore(isEnabled = true), + rageshakeFeatureAvailability = { true }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -79,7 +82,8 @@ class RageshakePreferencesPresenterTest { fun `present - set sensitivity`() = runTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = true), - FakeRageshakeDataStore(isEnabled = true) + FakeRageshakeDataStore(isEnabled = true), + rageshakeFeatureAvailability = { true }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt similarity index 84% rename from features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt rename to features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt index 1e3ac96770..b35bde295a 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt @@ -5,9 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.test.rageshake - -import io.element.android.features.rageshake.api.rageshake.RageShake +package io.element.android.features.rageshake.impl.rageshake class FakeRageShake( private var isAvailableValue: Boolean = true diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt similarity index 87% rename from features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt rename to features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt index b8fcc22d23..ec67ed13c0 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt @@ -5,9 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.test.rageshake +package io.element.android.features.rageshake.impl.rageshake -import io.element.android.features.rageshake.api.rageshake.RageshakeDataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow 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 b38c52e6ef..e73c7863b4 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 @@ -8,9 +8,10 @@ package io.element.android.features.rageshake.impl.reporter import com.google.common.truth.Truth.assertThat +import io.element.android.appconfig.RageshakeConfig import io.element.android.features.rageshake.api.reporter.BugReporterListener -import io.element.android.features.rageshake.test.crash.FakeCrashDataStore -import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder +import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore +import io.element.android.features.rageshake.impl.screenshot.FakeScreenshotHolder import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.FakeSdkMetadata @@ -138,7 +139,7 @@ class DefaultBugReporterTest { val foundValues = collectValuesFromFormData(request) - assertThat(foundValues["app"]).isEqualTo("element-x-android") + assertThat(foundValues["app"]).isEqualTo(RageshakeConfig.BUG_REPORT_APP_NAME) assertThat(foundValues["can_contact"]).isEqualTo("true") assertThat(foundValues["device_id"]).isEqualTo("ABCDEFGH") assertThat(foundValues["sdk_sha"]).isEqualTo("123456789") diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt index 415440b164..71563892dc 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt @@ -16,7 +16,9 @@ class DefaultBugReporterUrlProviderTest { @Test fun `test DefaultBugReporterUrlProvider`() { val sut = DefaultBugReporterUrlProvider() - val result = sut.provide() - assertThat(result).isEqualTo(RageshakeConfig.BUG_REPORT_URL.toHttpUrl()) + if (RageshakeConfig.BUG_REPORT_URL.isNotEmpty()) { + val result = sut.provide() + assertThat(result).isEqualTo(RageshakeConfig.BUG_REPORT_URL.toHttpUrl()) + } } } diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/screenshot/FakeScreenshotHolder.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt similarity index 78% rename from features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/screenshot/FakeScreenshotHolder.kt rename to features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt index 3f7d83f1c2..8e37da1910 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/screenshot/FakeScreenshotHolder.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt @@ -5,10 +5,9 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.rageshake.test.screenshot +package io.element.android.features.rageshake.impl.screenshot import android.graphics.Bitmap -import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder const val A_SCREENSHOT_URI = "file://content/uri" diff --git a/features/roomaliasresolver/impl/src/main/res/values-nb/translations.xml b/features/roomaliasresolver/impl/src/main/res/values-nb/translations.xml new file mode 100644 index 0000000000..4fd8f68bdf --- /dev/null +++ b/features/roomaliasresolver/impl/src/main/res/values-nb/translations.xml @@ -0,0 +1,4 @@ + + + "Kunne ikke løse romalias." + diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index 57d1cc6df0..93c93c345a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -11,5 +11,6 @@ sealed interface RoomDetailsEvent { data object LeaveRoom : RoomDetailsEvent data object MuteNotification : RoomDetailsEvent data object UnmuteNotification : RoomDetailsEvent + data class CopyToClipboard(val text: String) : RoomDetailsEvent data class SetFavorite(val isFavorite: Boolean) : RoomDetailsEvent } 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 df1daf6386..5c6333a290 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 @@ -24,8 +24,12 @@ import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEn import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState +import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient @@ -45,6 +49,7 @@ import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange +import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList @@ -65,6 +70,7 @@ class RoomDetailsPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, private val analyticsService: AnalyticsService, private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled, + private val clipboardHelper: ClipboardHelper, ) : Presenter { @Composable override fun present(): RoomDetailsState { @@ -122,7 +128,9 @@ class RoomDetailsPresenter @Inject constructor( } val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) - val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) + val isKnockRequestsEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) + }.collectAsState(false) val knockRequestsCount by produceState(null) { room.knockRequestsFlow.collect { value = it.size } } @@ -132,6 +140,9 @@ class RoomDetailsPresenter @Inject constructor( val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState() + val snackbarDispatcher = LocalSnackbarDispatcher.current + val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() + fun handleEvents(event: RoomDetailsEvent) { when (event) { RoomDetailsEvent.LeaveRoom -> @@ -147,6 +158,10 @@ class RoomDetailsPresenter @Inject constructor( } } is RoomDetailsEvent.SetFavorite -> scope.setFavorite(event.isFavorite) + is RoomDetailsEvent.CopyToClipboard -> { + clipboardHelper.copyPlainText(event.text) + snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard)) + } } } @@ -188,6 +203,7 @@ class RoomDetailsPresenter @Inject constructor( canShowPinnedMessages = canShowPinnedMessages, canShowMediaGallery = canShowMediaGallery, pinnedMessagesCount = pinnedMessagesCount, + snackbarMessage = snackbarMessage, canShowKnockRequests = canShowKnockRequests, knockRequestsCount = knockRequestsCount, canShowSecurityAndPrivacy = canShowSecurityAndPrivacy, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 5502d4e29a..8a0439b15d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Immutable import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.userprofile.api.UserProfileState +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomMember @@ -42,6 +43,7 @@ data class RoomDetailsState( val canShowPinnedMessages: Boolean, val canShowMediaGallery: Boolean, val pinnedMessagesCount: Int?, + val snackbarMessage: SnackbarMessage?, val canShowKnockRequests: Boolean, val knockRequestsCount: Int?, val canShowSecurityAndPrivacy: Boolean, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index b2db46115c..4304f151a3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -17,6 +17,7 @@ import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.shared.aUserProfileState import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -111,6 +112,7 @@ fun aRoomDetailsState( canShowPinnedMessages: Boolean = true, canShowMediaGallery: Boolean = true, pinnedMessagesCount: Int? = null, + snackbarMessage: SnackbarMessage? = null, canShowKnockRequests: Boolean = false, knockRequestsCount: Int? = null, canShowSecurityAndPrivacy: Boolean = true, @@ -139,11 +141,12 @@ fun aRoomDetailsState( canShowPinnedMessages = canShowPinnedMessages, canShowMediaGallery = canShowMediaGallery, pinnedMessagesCount = pinnedMessagesCount, + snackbarMessage = snackbarMessage, canShowKnockRequests = canShowKnockRequests, knockRequestsCount = knockRequestsCount, canShowSecurityAndPrivacy = canShowSecurityAndPrivacy, hasMemberVerificationViolations = hasMemberVerificationViolations, - eventSink = eventSink + eventSink = eventSink, ) fun aRoomNotificationSettings( 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 33985c44ee..381f94ad26 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 @@ -55,6 +55,7 @@ import io.element.android.libraries.designsystem.components.button.MainActionBut import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.modifiers.niceClickable import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight @@ -69,6 +70,8 @@ import io.element.android.libraries.designsystem.theme.components.ListItemStyle 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.designsystem.utils.snackbar.SnackbarHost +import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -106,6 +109,7 @@ fun RoomDetailsView( onProfileClick: (UserId) -> Unit, modifier: Modifier = Modifier, ) { + val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) Scaffold( modifier = modifier, topBar = { @@ -115,6 +119,7 @@ fun RoomDetailsView( onActionClick = onActionClick ) }, + snackbarHost = { SnackbarHost(snackbarHostState) }, ) { padding -> Column( modifier = Modifier @@ -135,6 +140,9 @@ fun RoomDetailsView( openAvatarPreview = { avatarUrl -> openAvatarPreview(state.roomName, avatarUrl) }, + onSubtitleClick = { subtitle -> + state.eventSink(RoomDetailsEvent.CopyToClipboard(subtitle)) + } ) } is RoomDetailsType.Dm -> { @@ -145,6 +153,9 @@ fun RoomDetailsView( openAvatarPreview = { name, avatarUrl -> openAvatarPreview(name, avatarUrl) }, + onSubtitleClick = { subtitle -> + state.eventSink(RoomDetailsEvent.CopyToClipboard(subtitle)) + } ) } } @@ -368,6 +379,7 @@ private fun RoomHeaderSection( roomAlias: RoomAlias?, heroes: ImmutableList, openAvatarPreview: (url: String) -> Unit, + onSubtitleClick: (String) -> Unit, ) { Column( modifier = Modifier @@ -384,7 +396,11 @@ private fun RoomHeaderSection( .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .testTag(TestTags.roomDetailAvatar) ) - TitleAndSubtitle(title = roomName, subtitle = roomAlias?.value) + TitleAndSubtitle( + title = roomName, + subtitle = roomAlias?.value, + onSubtitleClick = onSubtitleClick, + ) } } @@ -394,6 +410,7 @@ private fun DmHeaderSection( otherMember: RoomMember, roomName: String, openAvatarPreview: (name: String, url: String) -> Unit, + onSubtitleClick: (String) -> Unit, modifier: Modifier = Modifier ) { Column( @@ -411,6 +428,7 @@ private fun DmHeaderSection( TitleAndSubtitle( title = roomName, subtitle = otherMember.userId.value, + onSubtitleClick = onSubtitleClick, ) } } @@ -419,6 +437,7 @@ private fun DmHeaderSection( private fun TitleAndSubtitle( title: String, subtitle: String?, + onSubtitleClick: (String) -> Unit, ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Spacer(modifier = Modifier.height(24.dp)) @@ -430,6 +449,7 @@ private fun TitleAndSubtitle( if (subtitle != null) { Spacer(modifier = Modifier.height(6.dp)) Text( + modifier = Modifier.niceClickable { onSubtitleClick(subtitle) }, text = subtitle, style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textSecondary, @@ -612,13 +632,13 @@ private fun PinnedMessagesItem( headlineContent = { Text(stringResource(R.string.screen_room_details_pinned_events_row_title)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Pin())), trailingContent = - if (pinnedMessagesCount == null) { - ListItemContent.Custom { - CircularProgressIndicator(strokeWidth = 2.dp, modifier = Modifier.size(24.dp)) - } - } else { - ListItemContent.Text(pinnedMessagesCount.toString()) - }, + if (pinnedMessagesCount == null) { + ListItemContent.Custom { + CircularProgressIndicator(strokeWidth = 2.dp, modifier = Modifier.size(24.dp)) + } + } else { + ListItemContent.Text(pinnedMessagesCount.toString()) + }, onClick = { analyticsService.captureInteraction(Interaction.Name.PinnedMessageRoomInfoButton) onPinnedMessagesClick() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt index 4001eb7edb..e5f63332aa 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt @@ -12,6 +12,7 @@ import dagger.Module import dagger.Provides import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.userprofile.api.UserProfilePresenterFactory +import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -25,6 +26,7 @@ object RoomMemberModule { room: MatrixRoom, userProfilePresenterFactory: UserProfilePresenterFactory, encryptionService: EncryptionService, + clipboardHelper: ClipboardHelper, ): RoomMemberDetailsPresenter.Factory { return object : RoomMemberDetailsPresenter.Factory { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { @@ -33,6 +35,7 @@ object RoomMemberModule { room = room, userProfilePresenterFactory = userProfilePresenterFactory, encryptionService = encryptionService, + clipboardHelper = clipboardHelper, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index 9a5d456cbe..c5de24c603 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -19,7 +19,11 @@ import io.element.android.features.userprofile.api.UserProfileEvents import io.element.android.features.userprofile.api.UserProfilePresenterFactory import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState +import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.identity.IdentityState @@ -27,6 +31,7 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange +import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -42,6 +47,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @Assisted private val roomMemberId: UserId, private val room: MatrixRoom, private val encryptionService: EncryptionService, + private val clipboardHelper: ClipboardHelper, userProfilePresenterFactory: UserProfilePresenterFactory, ) : Presenter { interface Factory { @@ -55,6 +61,8 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( override fun present(): UserProfileState { val coroutineScope = rememberCoroutineScope() + val snackbarDispatcher = LocalSnackbarDispatcher.current + val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val roomMember by room.getRoomMemberAsState(roomMemberId) LaunchedEffect(Unit) { // Update room member info when opening this screen @@ -111,7 +119,11 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( UserProfileEvents.WithdrawVerification -> coroutineScope.launch { encryptionService.withdrawVerification(roomMemberId) } - else -> Unit + is UserProfileEvents.CopyToClipboard -> { + clipboardHelper.copyPlainText(event.text) + snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard)) + } + else -> userProfileState.eventSink(event) } } @@ -119,13 +131,8 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( userName = roomUserName ?: userProfileState.userName, avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl, verificationState = verificationState, - eventSink = { event -> - if (event is UserProfileEvents.WithdrawVerification) { - eventSink(UserProfileEvents.WithdrawVerification) - } else { - userProfileState.eventSink(event) - } - } + snackbarMessage = snackbarMessage, + eventSink = ::eventSink ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt index 824c239a91..eaabc8e4c2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt @@ -13,7 +13,9 @@ import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RoomMembersModerationEvents { data class SelectRoomMember(val roomMember: RoomMember) : RoomMembersModerationEvents data object KickUser : RoomMembersModerationEvents + data class DoKickUser(val reason: String) : RoomMembersModerationEvents data object BanUser : RoomMembersModerationEvents + data class DoBanUser(val reason: String) : RoomMembersModerationEvents data class UnbanUser(val userId: UserId) : RoomMembersModerationEvents data object Reset : RoomMembersModerationEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt index e9d4a91514..1fea6a8146 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt @@ -96,20 +96,22 @@ class RoomMembersModerationPresenter @Inject constructor( } } is RoomMembersModerationEvents.KickUser -> { + kickUserAsyncAction.value = AsyncAction.ConfirmingNoParams + } + is RoomMembersModerationEvents.DoKickUser -> { selectedMember?.let { - coroutineScope.kickUser(it.userId, kickUserAsyncAction) + coroutineScope.kickUser(it.userId, event.reason, kickUserAsyncAction) } selectedMember = null } is RoomMembersModerationEvents.BanUser -> { - if (banUserAsyncAction.value.isConfirming()) { - selectedMember?.let { - coroutineScope.banUser(it.userId, banUserAsyncAction) - } - selectedMember = null - } else { - banUserAsyncAction.value = AsyncAction.ConfirmingNoParams + banUserAsyncAction.value = AsyncAction.ConfirmingNoParams + } + is RoomMembersModerationEvents.DoBanUser -> { + selectedMember?.let { + coroutineScope.banUser(it.userId, event.reason, banUserAsyncAction) } + selectedMember = null } is RoomMembersModerationEvents.UnbanUser -> { // We are already confirming when we are reaching this point @@ -138,18 +140,26 @@ class RoomMembersModerationPresenter @Inject constructor( private fun CoroutineScope.kickUser( userId: UserId, + reason: String, kickUserAction: MutableState>, ) = runActionAndWaitForMembershipChange(kickUserAction) { analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember)) - room.kickUser(userId) + room.kickUser( + userId = userId, + reason = reason.takeIf { it.isNotBlank() }, + ) } private fun CoroutineScope.banUser( userId: UserId, + reason: String, banUserAction: MutableState>, ) = runActionAndWaitForMembershipChange(banUserAction) { analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember)) - room.banUser(userId) + room.banUser( + userId = userId, + reason = reason.takeIf { it.isNotBlank() }, + ) } private fun CoroutineScope.unbanUser( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt index 41e61d8351..9139100980 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt @@ -37,10 +37,18 @@ class RoomMembersModerationStateProvider : PreviewParameterProvider { + TextFieldDialog( + title = stringResource(R.string.screen_room_member_list_kick_member_confirmation_title), + submitText = stringResource(R.string.screen_room_member_list_kick_member_confirmation_action), + onSubmit = { reason -> + state.eventSink(RoomMembersModerationEvents.DoKickUser(reason = reason)) + }, + onDismissRequest = { state.eventSink(RoomMembersModerationEvents.Reset) }, + placeholder = stringResource(id = CommonStrings.common_reason), + label = stringResource(id = CommonStrings.common_reason), + content = stringResource(R.string.screen_room_member_list_kick_member_confirmation_description), + value = "", + ) + } is AsyncAction.Loading -> { LaunchedEffect(action) { val userDisplayName = state.selectedRoomMember?.getBestName().orEmpty() @@ -113,12 +128,17 @@ fun RoomMembersModerationView( when (val action = state.banUserAsyncAction) { is AsyncAction.Confirming -> { - ConfirmationDialog( + TextFieldDialog( title = stringResource(R.string.screen_room_member_list_ban_member_confirmation_title), - content = stringResource(R.string.screen_room_member_list_ban_member_confirmation_description), submitText = stringResource(R.string.screen_room_member_list_ban_member_confirmation_action), - onSubmitClick = { state.eventSink(RoomMembersModerationEvents.BanUser) }, - onDismiss = { state.eventSink(RoomMembersModerationEvents.Reset) } + onSubmit = { reason -> + state.eventSink(RoomMembersModerationEvents.DoBanUser(reason = reason)) + }, + onDismissRequest = { state.eventSink(RoomMembersModerationEvents.Reset) }, + placeholder = stringResource(id = CommonStrings.common_reason), + label = stringResource(id = CommonStrings.common_reason), + content = stringResource(R.string.screen_room_member_list_ban_member_confirmation_description), + value = "", ) } is AsyncAction.Loading -> { diff --git a/features/roomdetails/impl/src/main/res/values-el/translations.xml b/features/roomdetails/impl/src/main/res/values-el/translations.xml index 3cd064109e..16d6fa18a4 100644 --- a/features/roomdetails/impl/src/main/res/values-el/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-el/translations.xml @@ -7,11 +7,11 @@ "Αποκλεισμός ατόμων" "Αφαίρεση μηνυμάτων" "Όλοι" - "Πρόσκληση ατόμων" + "Προσκάλεσε άτομα και αποδέξου αιτήματα συμμετοχής" "Συντονισμός μελών" "Μηνύματα και περιεχόμενο" "Διαχειριστές και συντονιστές" - "Αφαίρεση ατόμων" + "Αφαίρεση ατόμων και απόρριψη αιτημάτων συμμετοχής" "Αλλαγή avatar δωματίου" "Λεπτομέρειες δωματίου" "Αλλαγή ονόματος δωματίου" diff --git a/features/roomdetails/impl/src/main/res/values-et/translations.xml b/features/roomdetails/impl/src/main/res/values-et/translations.xml index 8cf2c4a6a3..16f3d852b1 100644 --- a/features/roomdetails/impl/src/main/res/values-et/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-et/translations.xml @@ -75,6 +75,9 @@ "%1$d osaleja" "%1$d osalejat" + "Eemalda" + "Uue kutse saamisel on tal võimalik selle jututoaga uuesti liituda." + "Kas sa oled kindel, et soovid selle osaleja eemaldada?" "Eemalda ja sea suhtluskeeld" "Eemalda kasutaja jututoast" "Eemalda ja sea suhtluskeeld" diff --git a/features/roomdetails/impl/src/main/res/values-eu/translations.xml b/features/roomdetails/impl/src/main/res/values-eu/translations.xml index 8968f36d80..ecbdc614d4 100644 --- a/features/roomdetails/impl/src/main/res/values-eu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-eu/translations.xml @@ -52,6 +52,7 @@ "Lehenetsia" "Jakinarazpenak" "Finkatutako mezuak" + "Profila" "Sartzeko eskaerak" "Rolak eta baimenak" "Gelaren izena" @@ -110,9 +111,12 @@ "Gelaren xehetasunak" "Rolak eta baimenak" "Gehitu gelaren helbidea" + "Bai, gaitu zifratzea" "Zifratzea" "Edonork aurkitu eta bat egin dezake" "Edonork" + "Gonbidatutako pertsonak bakarrik sartu ahal izango dira" + "Gonbidapen bidez" "Gelarako sarbidea" "Gaur-gaurkoz ez da guneekin bateragarria" "Guneko kideak" diff --git a/features/roomdetails/impl/src/main/res/values-hu/translations.xml b/features/roomdetails/impl/src/main/res/values-hu/translations.xml index 585f61ece5..e9cec9a02a 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -75,6 +75,9 @@ "%1$d személy" "%1$d személy" + "Eltávolítás" + "Ehhez a szobához is csatlakozhat, ha meghívják." + "Biztos, hogy eltávolítja ezt a tagot?" "Eltávolítás és a tag kitiltása" "Eltávolítás a szobából" "Eltávolítás és a tag kitiltása" diff --git a/features/roomdetails/impl/src/main/res/values-in/translations.xml b/features/roomdetails/impl/src/main/res/values-in/translations.xml index d09ccd3cff..99c763ca9f 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -7,11 +7,11 @@ "Cekal orang-orang" "Hapus pesan" "Semua orang" - "Undang orang-orang" + "Undang orang-orang dan terima permintaan untuk bergabung" "Moderasi anggota" "Pesan dan konten" "Admin dan moderator" - "Keluarkan orang-orang" + "Keluarkan orang-orang dan tolak permintaan untuk bergabung" "Ubah avatar ruangan" "Detail ruangan" "Ubah nama ruangan" @@ -57,6 +57,7 @@ "Permintaan untuk bergabung" "Peran dan perizinan" "Nama ruangan" + "Keamanan & privasi" "Keamanan" "Bagikan ruangan" "Info ruangan" @@ -118,6 +119,9 @@ "Minta untuk bergabung" "Enkripsi" "Siapa pun" + "Hanya undangan" + "Akses ruangan" "Siapa pun" "Keterlihatan ruangan" + "Keamanan & privasi" diff --git a/features/roomdetails/impl/src/main/res/values-nb/translations.xml b/features/roomdetails/impl/src/main/res/values-nb/translations.xml index 4dd6f3fbb1..5ed7599258 100644 --- a/features/roomdetails/impl/src/main/res/values-nb/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-nb/translations.xml @@ -10,6 +10,7 @@ "Fjern meldinger" "Alle" "Inviter folk og godta forespørsler om å bli med" + "Moderering av medlemmer" "Meldinger og innhold" "Administratorer og moderatorer" "Fjern folk og avslå forespørsler om å bli med" @@ -25,6 +26,7 @@ "Du vil ikke kunne angre denne endringen ettersom du degraderer deg selv, og hvis du er den siste privilegerte brukeren i rommet, vil det være umulig å få tilbake privilegiene." "Degradere deg selv?" "%1$s (Venter)" + "(Venter)" "Administratorer har automatisk moderatorrettigheter" "Rediger moderatorer" "Administratorer" @@ -108,6 +110,7 @@ "Endre rollen min" "Nedgradere til medlem" "Nedgradere til moderator" + "Moderering av medlemmer" "Meldinger og innhold" "Moderatorer" "Tillatelser" 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 4c470cd1a7..39e2fccaa9 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -76,6 +76,9 @@ "%1$d osoby" "%1$d osôb" + "Odstrániť" + "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." + "Ste si istý, že chcete odstrániť tohto člena?" "Odstrániť a zakázať člena" "Odstrániť z miestnosti" "Odstrániť a zakázať člena" diff --git a/features/roomdetails/impl/src/main/res/values-sv/translations.xml b/features/roomdetails/impl/src/main/res/values-sv/translations.xml index aace77772d..988c1dfdbc 100644 --- a/features/roomdetails/impl/src/main/res/values-sv/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sv/translations.xml @@ -75,6 +75,9 @@ "%1$d person" "%1$d personer" + "Ta bort" + "Denne kommer kunna gå med i rummet igen om denne bjuds in" + "Är du säker på att du vill ta bort den här medlemmen?" "Ta bort och banna medlem" "Ta bort från rummet" "Ta bort och banna medlem" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 62a7e48295..fb2cfc686e 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -75,6 +75,9 @@ "%1$d person" "%1$d people" + "Remove" + "They will be able to join this room again if invited." + "Are you sure you want to remove this member?" "Remove and ban member" "Remove from room" "Remove and ban member" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt index 07d10ede63..6c88ce8c2b 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt @@ -17,6 +17,8 @@ import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.userprofile.shared.aUserProfileState +import io.element.android.libraries.androidutils.clipboard.ClipboardHelper +import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -81,6 +83,7 @@ class RoomDetailsPresenterTest { ), isPinnedMessagesFeatureEnabled: Boolean = true, encryptionService: FakeEncryptionService = FakeEncryptionService(), + clipboardHelper: ClipboardHelper = FakeClipboardHelper(), ): RoomDetailsPresenter { val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { @@ -92,6 +95,7 @@ class RoomDetailsPresenterTest { Presenter { aUserProfileState() } }, encryptionService = encryptionService, + clipboardHelper = clipboardHelper, ) } } @@ -106,6 +110,7 @@ class RoomDetailsPresenterTest { dispatchers = dispatchers, isPinnedMessagesFeatureEnabled = { isPinnedMessagesFeatureEnabled }, analyticsService = analyticsService, + clipboardHelper = clipboardHelper, ) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt index 1506f50f42..41ff8706ae 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt @@ -17,6 +17,8 @@ import io.element.android.features.userprofile.api.UserProfileEvents import io.element.android.features.userprofile.api.UserProfilePresenterFactory import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.shared.aUserProfileState +import io.element.android.libraries.androidutils.clipboard.ClipboardHelper +import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState @@ -350,12 +352,14 @@ class RoomMemberDetailsPresenterTest { } }, encryptionService: FakeEncryptionService = FakeEncryptionService(getUserIdentityResult = { Result.success(null) }), + clipboardHelper: ClipboardHelper = FakeClipboardHelper(), ): RoomMemberDetailsPresenter { return RoomMemberDetailsPresenter( roomMemberId = UserId("@alice:server.org"), room = room, userProfilePresenterFactory = userProfilePresenterFactory, encryptionService = encryptionService, + clipboardHelper = clipboardHelper, ) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt index d86fc49925..66e7f6bd90 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt @@ -17,14 +17,18 @@ import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.aVictor import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers +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.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.test.A_REASON 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.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.persistentListOf @@ -153,13 +157,14 @@ class RoomMembersModerationPresenterTest { } @Test - fun `present - Kick removes the user`() = runTest { + fun `present - Kick requires confirmation and then kicks the user`() = runTest { val analyticsService = FakeAnalyticsService() + val kickUserResult = lambdaRecorder> { _, _ -> Result.success(Unit) } val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - kickUserResult = { _, _ -> Result.success(Unit) }, + kickUserResult = kickUserResult, ) val selectedMember = aVictor() val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService) @@ -169,6 +174,10 @@ class RoomMembersModerationPresenterTest { skipItems(1) awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) awaitItem().eventSink(RoomMembersModerationEvents.KickUser) + val confirmingState = awaitItem() + assertThat(confirmingState.kickUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) + // Confirm + confirmingState.eventSink(RoomMembersModerationEvents.DoKickUser(reason = A_REASON)) skipItems(1) val loadingState = awaitItem() assertThat(loadingState.actions).isEmpty() @@ -178,17 +187,22 @@ class RoomMembersModerationPresenterTest { assertThat(selectedRoomMember).isNull() } assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.KickMember)) + kickUserResult.assertions().isCalledOnce().with( + value(selectedMember.userId), + value(A_REASON), + ) } } @Test fun `present - BanUser requires confirmation and then bans the user`() = runTest { val analyticsService = FakeAnalyticsService() + val banUserResult = lambdaRecorder> { _, _ -> Result.success(Unit) } val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - banUserResult = { _, _ -> Result.success(Unit) }, + banUserResult = banUserResult, ) val selectedMember = aVictor() val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService) @@ -200,9 +214,8 @@ class RoomMembersModerationPresenterTest { awaitItem().eventSink(RoomMembersModerationEvents.BanUser) val confirmingState = awaitItem() assertThat(confirmingState.banUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) - // Confirm - confirmingState.eventSink(RoomMembersModerationEvents.BanUser) + confirmingState.eventSink(RoomMembersModerationEvents.DoBanUser(reason = A_REASON)) skipItems(1) val loadingItem = awaitItem() assertThat(loadingItem.actions).isEmpty() @@ -213,6 +226,10 @@ class RoomMembersModerationPresenterTest { assertThat(selectedRoomMember).isNull() } assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.BanMember)) + banUserResult.assertions().isCalledOnce().with( + value(selectedMember.userId), + value(A_REASON), + ) } } @@ -289,7 +306,7 @@ class RoomMembersModerationPresenterTest { val initialItem = awaitItem() // Kick user and fail awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor())) - awaitItem().eventSink(RoomMembersModerationEvents.KickUser) + awaitItem().eventSink(RoomMembersModerationEvents.DoKickUser(reason = "")) skipItems(1) assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -299,8 +316,7 @@ class RoomMembersModerationPresenterTest { // Ban user and fail initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor())) - awaitItem().eventSink(RoomMembersModerationEvents.BanUser) - awaitItem().eventSink(RoomMembersModerationEvents.BanUser) + awaitItem().eventSink(RoomMembersModerationEvents.DoBanUser(reason = "")) skipItems(1) assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt index 6fc9b8a20f..14c550d0c4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt @@ -10,11 +10,14 @@ package io.element.android.features.roomdetails.impl.members.moderation import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performTextInput import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.members.anAlice import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.test.A_REASON import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder @@ -95,6 +98,58 @@ class RoomMembersModerationViewTest { eventsRecorder.assertSingle(RoomMembersModerationEvents.KickUser) } + @Test + fun `cancelling 'Remove member' confirmation emits the expected event`() { + val eventsRecorder = EventsRecorder() + val roomMember = anAlice() + val state = aRoomMembersModerationState( + selectedRoomMember = roomMember, + kickUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ) + rule.setRoomMembersModerationView( + state = state, + ) + // Note: the string key semantics is not perfect here :/ + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset) + } + + @Test + fun `confirming 'Remove member' reason edition then validation emits the expected event`() { + val eventsRecorder = EventsRecorder() + val roomMember = anAlice() + val state = aRoomMembersModerationState( + selectedRoomMember = roomMember, + kickUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ) + rule.setRoomMembersModerationView( + state = state, + ) + val reason = rule.activity.getString(CommonStrings.common_reason) + rule.onNodeWithText(reason).performTextInput(A_REASON) + rule.clickOn(R.string.screen_room_member_list_kick_member_confirmation_action) + eventsRecorder.assertSingle(RoomMembersModerationEvents.DoKickUser(reason = A_REASON)) + } + + @Test + fun `confirming 'Remove member' confirmation emits the expected event`() { + val eventsRecorder = EventsRecorder() + val roomMember = anAlice() + val state = aRoomMembersModerationState( + selectedRoomMember = roomMember, + kickUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ) + rule.setRoomMembersModerationView( + state = state, + ) + // Note: the string key semantics is not perfect here :/ + rule.clickOn(R.string.screen_room_member_list_kick_member_confirmation_action) + eventsRecorder.assertSingle(RoomMembersModerationEvents.DoKickUser(reason = "")) + } + @Config(qualifiers = "h1024dp") @Test fun `clicking on 'Remove and ban member' emits the expected event`() { @@ -136,6 +191,24 @@ class RoomMembersModerationViewTest { eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset) } + @Test + fun `confirming 'Remove and ban member' reason edition emits the expected event`() { + val eventsRecorder = EventsRecorder() + val roomMember = anAlice() + val state = aRoomMembersModerationState( + selectedRoomMember = roomMember, + banUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ) + rule.setRoomMembersModerationView( + state = state, + ) + val reason = rule.activity.getString(CommonStrings.common_reason) + rule.onNodeWithText(reason).performTextInput(A_REASON) + rule.clickOn(R.string.screen_room_member_list_ban_member_confirmation_action) + eventsRecorder.assertSingle(RoomMembersModerationEvents.DoBanUser(reason = A_REASON)) + } + @Test fun `confirming 'Remove and ban member' confirmation emits the expected event`() { val eventsRecorder = EventsRecorder() @@ -150,7 +223,7 @@ class RoomMembersModerationViewTest { ) // Note: the string key semantics is not perfect here :/ rule.clickOn(R.string.screen_room_member_list_ban_member_confirmation_action) - eventsRecorder.assertSingle(RoomMembersModerationEvents.BanUser) + eventsRecorder.assertSingle(RoomMembersModerationEvents.DoBanUser(reason = "")) } @Test diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index a39971b415..a6f09ff4ed 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.features.networkmonitor.api) implementation(projects.features.logout.api) implementation(projects.features.leaveroom.api) + implementation(projects.features.rageshake.api) implementation(projects.services.analytics.api) implementation(libs.androidx.datastore.preferences) api(projects.features.roomlist.api) @@ -60,6 +61,9 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(libs.test.robolectric) + testImplementation(projects.features.invite.test) + testImplementation(projects.features.logout.test) + testImplementation(projects.features.networkmonitor.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.dateformatter.test) @@ -71,7 +75,5 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) - testImplementation(projects.features.networkmonitor.test) - testImplementation(projects.features.logout.test) testImplementation(projects.tests.testutils) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt index 64348f623c..7435424b15 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt @@ -11,8 +11,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentSet open class RoomListContentStateProvider : PreviewParameterProvider { override val values: Sequence @@ -29,10 +31,12 @@ internal fun aRoomsContentState( securityBannerState: SecurityBannerState = SecurityBannerState.None, summaries: ImmutableList = aRoomListRoomSummaryList(), fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(), + seenRoomInvites: Set = emptySet(), ) = RoomListContentState.Rooms( securityBannerState = securityBannerState, fullScreenIntentPermissionsState = fullScreenIntentPermissionsState, summaries = summaries, + seenRoomInvites = seenRoomInvites.toPersistentSet(), ) internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16) 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 f3e7daf4d7..461204b2a2 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 @@ -24,12 +24,14 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import im.vector.app.features.analytics.plan.Interaction +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.model.RoomListRoomSummary @@ -49,7 +51,6 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore @@ -57,6 +58,7 @@ import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -91,6 +93,8 @@ class RoomListPresenter @Inject constructor( private val notificationCleaner: NotificationCleaner, private val logoutPresenter: Presenter, private val appPreferencesStore: AppPreferencesStore, + private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, + private val seenInvitesStore: SeenInvitesStore, ) : Presenter { private val encryptionService: EncryptionService = client.encryptionService() @@ -99,10 +103,11 @@ class RoomListPresenter @Inject constructor( val coroutineScope = rememberCoroutineScope() val leaveRoomState = leaveRoomPresenter.present() val matrixUser = client.userProfile.collectAsState() - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() val filtersState = filtersPresenter.present() val searchState = searchPresenter.present() val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() + val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() } LaunchedEffect(Unit) { roomListDataSource.launchIn(this) @@ -163,6 +168,7 @@ class RoomListPresenter @Inject constructor( contextMenu = contextMenu.value, leaveRoomState = leaveRoomState, filtersState = filtersState, + canReportBug = canReportBug, searchState = searchState, contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, @@ -224,6 +230,7 @@ class RoomListPresenter @Inject constructor( loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading } } + val seenRoomInvites by remember { seenInvitesStore.seenRoomIds() }.collectAsState(emptySet()) val securityBannerState by rememberSecurityBannerState(securityBannerDismissed) return when { showEmpty -> RoomListContentState.Empty(securityBannerState = securityBannerState) @@ -232,7 +239,8 @@ class RoomListPresenter @Inject constructor( RoomListContentState.Rooms( securityBannerState = securityBannerState, fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(), - summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList() + summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList(), + seenRoomInvites = seenRoomInvites.toPersistentSet(), ) } } @@ -307,6 +315,8 @@ class RoomListPresenter @Inject constructor( private fun CoroutineScope.clearCacheOfRoom(roomId: RoomId) = launch { client.getRoom(roomId)?.use { room -> + // Ideally we wouldn't have a live timeline at this point, but right now we instantiate one when retrieving the room + room.liveTimeline.close() room.clearEventCacheStorage() } } 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 07994ef8cb..ae62b88deb 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 @@ -19,6 +19,7 @@ import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermiss import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableSet @Immutable data class RoomListState( @@ -29,6 +30,7 @@ data class RoomListState( val contextMenu: ContextMenu, val leaveRoomState: LeaveRoomState, val filtersState: RoomListFiltersState, + val canReportBug: Boolean, val searchState: RoomListSearchState, val contentState: RoomListContentState, val acceptDeclineInviteState: AcceptDeclineInviteState, @@ -64,9 +66,11 @@ sealed interface RoomListContentState { data class Empty( val securityBannerState: SecurityBannerState, ) : RoomListContentState + data class Rooms( val securityBannerState: SecurityBannerState, val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState, val summaries: ImmutableList, + val seenRoomInvites: ImmutableSet, ) : RoomListContentState } 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 4c21d797f1..6b075db9df 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 @@ -57,6 +57,7 @@ internal fun aRoomListState( leaveRoomState: LeaveRoomState = aLeaveRoomState(), searchState: RoomListSearchState = aRoomListSearchState(), filtersState: RoomListFiltersState = aRoomListFiltersState(), + canReportBug: Boolean = true, contentState: RoomListContentState = aRoomsContentState(), acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), directLogoutState: DirectLogoutState = aDirectLogoutState(), @@ -69,6 +70,7 @@ internal fun aRoomListState( contextMenu = contextMenu, leaveRoomState = leaveRoomState, filtersState = filtersState, + canReportBug = canReportBug, searchState = searchState, contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 8a5ade09d2..64ec1f8406 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -127,6 +127,7 @@ private fun RoomListScaffold( displayMenuItems = state.displayActions, displayFilters = state.displayFilters, filtersState = state.filtersState, + canReportBug = state.canReportBug, ) }, content = { padding -> diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index ebd618185c..acdb762fa7 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -46,6 +46,7 @@ import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState import io.element.android.features.roomlist.impl.model.RoomListRoomSummary +import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType 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 @@ -239,6 +240,8 @@ private fun RoomsViewList( ) { index, room -> RoomSummaryRow( room = room, + isInviteSeen = room.displayType == RoomSummaryDisplayType.INVITE && + state.seenRoomInvites.contains(room.roomId), onClick = onRoomClick, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt index a0e9078530..1f4c86f579 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt @@ -85,6 +85,7 @@ fun RoomListTopBar( displayMenuItems: Boolean, displayFilters: Boolean, filtersState: RoomListFiltersState, + canReportBug: Boolean, modifier: Modifier = Modifier, ) { DefaultRoomListTopBar( @@ -98,6 +99,7 @@ fun RoomListTopBar( displayMenuItems = displayMenuItems, displayFilters = displayFilters, filtersState = filtersState, + canReportBug = canReportBug, modifier = modifier, ) } @@ -115,6 +117,7 @@ private fun DefaultRoomListTopBar( displayMenuItems: Boolean, displayFilters: Boolean, filtersState: RoomListFiltersState, + canReportBug: Boolean, modifier: Modifier = Modifier, ) { // We need this to manually clip the top app bar in preview mode @@ -239,7 +242,7 @@ private fun DefaultRoomListTopBar( } ) } - if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM) { + if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM && canReportBug) { DropdownMenuItem( onClick = { showMenu = false @@ -319,6 +322,7 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview { displayMenuItems = true, displayFilters = true, filtersState = aRoomListFiltersState(), + canReportBug = true, onMenuActionClick = {}, ) } @@ -337,6 +341,7 @@ internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview { displayMenuItems = true, displayFilters = true, filtersState = aRoomListFiltersState(), + canReportBug = true, onMenuActionClick = {}, ) } 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 0fbd4d38fe..bfa255af13 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 @@ -57,7 +57,6 @@ import io.element.android.libraries.designsystem.theme.roomListRoomMessage import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate import io.element.android.libraries.designsystem.theme.roomListRoomName import io.element.android.libraries.designsystem.theme.unreadIndicator -import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.ui.components.InviteSenderView import io.element.android.libraries.matrix.ui.model.InviteSender @@ -69,6 +68,7 @@ internal val minHeight = 84.dp @Composable internal fun RoomSummaryRow( room: RoomListRoomSummary, + isInviteSeen: Boolean, onClick: (RoomListRoomSummary) -> Unit, eventSink: (RoomListEvents) -> Unit, modifier: Modifier = Modifier, @@ -86,8 +86,8 @@ internal fun RoomSummaryRow( Timber.d("Long click on invite room") }, ) { - InviteNameAndIndicatorRow(name = room.name) - InviteSubtitle(isDm = room.isDm, inviteSender = room.inviteSender, canonicalAlias = room.canonicalAlias) + InviteNameAndIndicatorRow(name = room.name, isInviteSeen = isInviteSeen) + InviteSubtitle(isDm = room.isDm, inviteSender = room.inviteSender) if (!room.isDm && room.inviteSender != null) { Spacer(modifier = Modifier.height(4.dp)) InviteSenderView( @@ -232,13 +232,12 @@ private fun NameAndTimestampRow( private fun InviteSubtitle( isDm: Boolean, inviteSender: InviteSender?, - canonicalAlias: RoomAlias?, modifier: Modifier = Modifier ) { val subtitle = if (isDm) { inviteSender?.userId?.value } else { - canonicalAlias?.value + null } if (subtitle != null) { Text( @@ -302,6 +301,7 @@ private fun LastMessageAndIndicatorRow( @Composable private fun InviteNameAndIndicatorRow( name: String?, + isInviteSeen: Boolean, modifier: Modifier = Modifier, ) { Row( @@ -318,9 +318,11 @@ private fun InviteNameAndIndicatorRow( maxLines = 1, overflow = TextOverflow.Ellipsis ) - UnreadIndicatorAtom( - color = ElementTheme.colors.unreadIndicator - ) + if (!isInviteSeen) { + UnreadIndicatorAtom( + color = ElementTheme.colors.unreadIndicator + ) + } } } @@ -386,6 +388,8 @@ private fun MentionIndicatorAtom() { internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryProvider::class) data: RoomListRoomSummary) = ElementPreview { RoomSummaryRow( room = data, + // Set isInviteSeen to true for the preview when the room has name "Bob" + isInviteSeen = data.name == "Bob", onClick = {}, eventSink = {}, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt index 81bae26b4a..214ac44cfd 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt @@ -10,7 +10,6 @@ package io.element.android.features.roomlist.impl.filters import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.animateScrollBy @@ -48,7 +47,6 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag -@OptIn(ExperimentalFoundationApi::class) @Composable fun RoomListFiltersView( state: RoomListFiltersState, 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 498ad762a3..cb4c48d3f7 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 @@ -39,12 +39,10 @@ data class RoomListRoomSummary( ) { val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE && (numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) || - isMarkedUnread || - displayType == RoomSummaryDisplayType.INVITE + isMarkedUnread val hasNewContent = numberOfUnreadMessages > 0 || numberOfUnreadMentions > 0 || numberOfUnreadNotifications > 0 || - isMarkedUnread || - displayType == RoomSummaryDisplayType.INVITE + isMarkedUnread } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index 90b4fba44c..4f6e783704 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -173,6 +173,8 @@ private fun RoomListSearchContent( ) { room -> RoomSummaryRow( room = room, + // TODO + isInviteSeen = false, onClick = ::onRoomClick, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/res/values-nb/translations.xml b/features/roomlist/impl/src/main/res/values-nb/translations.xml index a97826a0c3..15cfe25708 100644 --- a/features/roomlist/impl/src/main/res/values-nb/translations.xml +++ b/features/roomlist/impl/src/main/res/values-nb/translations.xml @@ -5,6 +5,7 @@ "Konfigurer gjenoppretting for å beskytte kontoen din" "Skriv inn gjenopprettingsnøkkelen din" "Har du glemt din gjenopprettingsnøkkel?" + "Nøkkellagringen din er ikke synkronisert" "For å sikre at du aldri går glipp av en viktig samtale, må du endre innstillingene dine for å tillate fullskjermvarsler når telefonen er låst." "Forbedre samtaleopplevelsen din" "Er du sikker på at du vil takke nei til invitasjonen til å bli med i %1$s?" diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt index a48d63cad8..50e7628d09 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt @@ -12,13 +12,16 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Interaction +import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.anAcceptDeclineInviteState +import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.aLeaveRoomState import io.element.android.features.logout.api.direct.aDirectLogoutState +import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory import io.element.android.features.roomlist.impl.filters.RoomListFiltersState @@ -105,12 +108,14 @@ class RoomListPresenterTest { matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.success(MatrixUser(matrixClient.sessionId, A_USER_NAME, AN_AVATAR_URL))) val presenter = createRoomListPresenter( client = matrixClient, + rageshakeFeatureAvailability = { false }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() assertThat(initialState.matrixUser).isEqualTo(MatrixUser(A_USER_ID)) + assertThat(initialState.canReportBug).isFalse() val withUserState = awaitItem() assertThat(withUserState.matrixUser.userId).isEqualTo(A_USER_ID) assertThat(withUserState.matrixUser.displayName).isEqualTo(A_USER_NAME) @@ -135,6 +140,7 @@ class RoomListPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.showAvatarIndicator).isTrue() + assertThat(initialState.canReportBug).isTrue() sessionVerificationService.emitNeedsSessionVerification(false) encryptionService.emitBackupState(BackupState.ENABLED) val finalState = awaitItem() @@ -165,10 +171,11 @@ class RoomListPresenterTest { val matrixClient = FakeMatrixClient( roomListService = roomListService ) - val presenter = createRoomListPresenter(client = matrixClient) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createRoomListPresenter( + client = matrixClient, + seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)), + ) + presenter.test { val initialState = consumeItemsUntilPredicate { state -> state.contentState is RoomListContentState.Skeleton }.last() assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Skeleton::class.java) roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) @@ -190,6 +197,7 @@ class RoomListPresenterTest { timestamp = "0 TimeOrDate true", ) ) + assertThat(withRoomsState.contentAsRooms().seenRoomInvites).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) cancelAndIgnoreRemainingEvents() } } @@ -675,6 +683,8 @@ class RoomListPresenterTest { acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(), + rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true }, + seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore() ) = RoomListPresenter( client = client, syncService = syncService, @@ -705,6 +715,8 @@ class RoomListPresenterTest { notificationCleaner = notificationCleaner, logoutPresenter = { aDirectLogoutState() }, appPreferencesStore = appPreferencesStore, + rageshakeFeatureAvailability = rageshakeFeatureAvailability, + seenInvitesStore = seenInvitesStore, ) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt index caa204328b..a7940a203b 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt @@ -65,12 +65,12 @@ class RoomListRoomSummaryTest { } @Test - fun `when display type is invite then isHighlighted and hasNewContent are true`() { + fun `when display type is invite then isHighlighted and hasNewContent are false`() { val sut = createRoomListRoomSummary( displayType = RoomSummaryDisplayType.INVITE, ) - assertThat(sut.isHighlighted).isTrue() - assertThat(sut.hasNewContent).isTrue() + assertThat(sut.isHighlighted).isFalse() + assertThat(sut.hasNewContent).isFalse() } } diff --git a/features/securebackup/impl/src/main/res/values-cs/translations.xml b/features/securebackup/impl/src/main/res/values-cs/translations.xml index 3ba3bd98ee..f03a7d68d6 100644 --- a/features/securebackup/impl/src/main/res/values-cs/translations.xml +++ b/features/securebackup/impl/src/main/res/values-cs/translations.xml @@ -62,9 +62,9 @@ "Nastavení obnovy" "Ano, resetovat nyní" "Tento proces je nevratný." - "Opravdu chcete obnovit šifrování?" + "Opravdu chcete obnovit svou identitu?" "Došlo k neznámé chybě. Zkontrolujte, zda je heslo k účtu správné a zkuste to znovu." "Zadejte…" - "Potvrďte, že chcete obnovit šifrování." + "Potvrďte, že chcete obnovit svou identitu." "Pro pokračování zadejte heslo k účtu" diff --git a/features/securebackup/impl/src/main/res/values-nb/translations.xml b/features/securebackup/impl/src/main/res/values-nb/translations.xml index 7d7401473e..1123e3ef2c 100644 --- a/features/securebackup/impl/src/main/res/values-nb/translations.xml +++ b/features/securebackup/impl/src/main/res/values-nb/translations.xml @@ -1,11 +1,14 @@ + "Slett nøkkellagring" "Slå på sikkerhetskopiering" "Lagre din kryptografiske identitet og meldingsnøkler sikkert på serveren. Dette gjør at du kan se meldingshistorikken din på alle nye enheter. %1$s." + "Nøkkellagring" "Last opp nøkler fra denne enheten" "Endre gjenopprettingsnøkkel" "Gjenopprett din kryptografiske identitet og meldingshistorikk med en gjenopprettingsnøkkel hvis du har mistet alle dine brukte enheter." "Skriv inn gjenopprettingsnøkkel" + "Nøkkellagringen din er for øyeblikket ikke synkronisert." "Konfigurer gjenoppretting" "Få tilgang til de krypterte meldingene dine hvis du mister alle enhetene dine eller blir logget ut av %1$s overalt." "Logg på kontoen din igjen" @@ -25,6 +28,7 @@ "Er du sikker på at du vil slå av sikkerhetskopiering?" "Du vil ikke ha kryptert meldingshistorikk på nye enheter" "Du mister tilgangen til de krypterte meldingene dine hvis du er logget ut av %1$s overalt" + "Er du sikker på at du vil slå av nøkkellagring og slette den?" "Få en ny gjenopprettingsnøkkel hvis du har mistet den eksisterende. Etter at du har endret gjenopprettingsnøkkelen, vil den gamle ikke lenger fungere." "Generer en ny gjenopprettingsnøkkel" "Ikke del dette med noen!" diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt index 866170daa3..66cb147158 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.media.FileInfo +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -116,7 +117,8 @@ class SharePresenterTest { @Test fun `present - send media ok`() = runTest { - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } val matrixRoom = FakeMatrixRoom( diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt index dd7c24ab35..773954c3e0 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt @@ -34,7 +34,9 @@ class SignedOutPresenter @AssistedInject constructor( @Composable override fun present(): SignedOutState { - val sessions by sessionStore.sessionsFlow().collectAsState(initial = emptyList()) + val sessions by remember { + sessionStore.sessionsFlow() + }.collectAsState(initial = emptyList()) val signedOutSession by remember { derivedStateOf { sessions.firstOrNull { it.userId == sessionId } } } diff --git a/features/userprofile/api/build.gradle.kts b/features/userprofile/api/build.gradle.kts index b2c1068556..8bdaa8d77e 100644 --- a/features/userprofile/api/build.gradle.kts +++ b/features/userprofile/api/build.gradle.kts @@ -16,5 +16,6 @@ android { dependencies { implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) implementation(projects.libraries.matrix.api) } diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt index b7b7ba2561..4a5f6bb415 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt @@ -15,4 +15,5 @@ sealed interface UserProfileEvents { data object ClearBlockUserError : UserProfileEvents data object ClearConfirmationDialog : UserProfileEvents data object WithdrawVerification : UserProfileEvents + data class CopyToClipboard(val text: String) : UserProfileEvents } diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt index f32033b0a7..b9f08a27f3 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt @@ -9,6 +9,7 @@ package io.element.android.features.userprofile.api import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -23,6 +24,7 @@ data class UserProfileState( val isCurrentUser: Boolean, val dmRoomId: RoomId?, val canCall: Boolean, + val snackbarMessage: SnackbarMessage?, val eventSink: (UserProfileEvents) -> Unit ) { enum class ConfirmationDialog { diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt index 4216287a70..c098177529 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt @@ -120,8 +120,9 @@ class UserProfilePresenter @AssistedInject constructor( UserProfileEvents.ClearStartDMState -> { startDmActionState.value = AsyncAction.Uninitialized } - // Do nothing for withdrawing verification as it's handled by the RoomMemberDetailsPresenter if needed - UserProfileEvents.WithdrawVerification -> Unit + // Do nothing for other event as they are handled by the RoomMemberDetailsPresenter if needed + UserProfileEvents.WithdrawVerification, + is UserProfileEvents.CopyToClipboard -> Unit } } @@ -136,6 +137,7 @@ class UserProfilePresenter @AssistedInject constructor( isCurrentUser = isCurrentUser, dmRoomId = dmRoomId, canCall = canCall, + snackbarMessage = null, eventSink = ::handleEvents ) } diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt index 3df57b9eb0..da88c0f508 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt @@ -13,9 +13,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -28,6 +30,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRow import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.modifiers.niceClickable import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.ButtonSize @@ -46,6 +49,7 @@ fun UserProfileHeaderSection( userName: String?, verificationState: UserProfileVerificationState, openAvatarPreview: (url: String) -> Unit, + onUserIdClick: () -> Unit, withdrawVerificationClick: () -> Unit, modifier: Modifier = Modifier ) { @@ -58,6 +62,7 @@ fun UserProfileHeaderSection( Avatar( avatarData = AvatarData(userId.value, userName, avatarUrl, AvatarSize.UserHeader), modifier = Modifier + .clip(CircleShape) .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .testTag(TestTags.memberDetailAvatar) ) @@ -72,6 +77,7 @@ fun UserProfileHeaderSection( Spacer(modifier = Modifier.height(6.dp)) } Text( + modifier = Modifier.niceClickable { onUserIdClick() }, text = userId.value, style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textSecondary, @@ -122,6 +128,7 @@ internal fun UserProfileHeaderSectionPreview() = ElementPreview { userName = "Alice", verificationState = UserProfileVerificationState.VERIFIED, openAvatarPreview = {}, + onUserIdClick = {}, withdrawVerificationClick = {}, ) } @@ -135,6 +142,7 @@ internal fun UserProfileHeaderSectionWithVerificationViolationPreview() = Elemen userName = "Alice", verificationState = UserProfileVerificationState.VERIFICATION_VIOLATION, openAvatarPreview = {}, + onUserIdClick = {}, withdrawVerificationClick = {}, ) } diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt index be9fad9c94..7a5cc53239 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt @@ -14,6 +14,7 @@ import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.components.aMatrixUser @@ -45,6 +46,7 @@ fun aUserProfileState( isCurrentUser: Boolean = false, dmRoomId: RoomId? = null, canCall: Boolean = false, + snackbarMessage: SnackbarMessage? = null, eventSink: (UserProfileEvents) -> Unit = {}, ) = UserProfileState( userId = userId, @@ -57,5 +59,6 @@ fun aUserProfileState( isCurrentUser = isCurrentUser, dmRoomId = dmRoomId, canCall = canCall, + snackbarMessage = snackbarMessage, eventSink = eventSink, ) diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt index 0c544cf659..a43478e466 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt @@ -38,6 +38,8 @@ import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost +import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.components.CreateDmConfirmationBottomSheet @@ -55,17 +57,19 @@ fun UserProfileView( onVerifyClick: (UserId) -> Unit, modifier: Modifier = Modifier, ) { + val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) Scaffold( modifier = modifier, topBar = { TopAppBar(title = { }, navigationIcon = { BackButton(onClick = goBack) }) }, + snackbarHost = { SnackbarHost(snackbarHostState) }, ) { padding -> Column( modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) - .verticalScroll(rememberScrollState()) + .padding(padding) + .consumeWindowInsets(padding) + .verticalScroll(rememberScrollState()) ) { UserProfileHeaderSection( avatarUrl = state.avatarUrl, @@ -75,6 +79,9 @@ fun UserProfileView( openAvatarPreview = { avatarUrl -> openAvatarPreview(state.userName ?: state.userId.value, avatarUrl) }, + onUserIdClick = { + state.eventSink(UserProfileEvents.CopyToClipboard(state.userId.value)) + }, withdrawVerificationClick = { state.eventSink(UserProfileEvents.WithdrawVerification) }, ) UserProfileMainActionsSection( diff --git a/features/verifysession/impl/src/main/res/values-cs/translations.xml b/features/verifysession/impl/src/main/res/values-cs/translations.xml index 9b59cb2041..bdf73c53f1 100644 --- a/features/verifysession/impl/src/main/res/values-cs/translations.xml +++ b/features/verifysession/impl/src/main/res/values-cs/translations.xml @@ -41,7 +41,7 @@ "Otevřete aplikaci na jiném ověřeném zařízení" "Pro větší bezpečnost ověřte tohoto uživatele porovnáním sady emotikonů na svých zařízeních. Proveďte to pomocí důvěryhodného způsobu komunikace." "Ověřte tohoto uživatele?" - "Pro větší bezpečnost chce jiný uživatel ověřit vaši totožnost. Zobrazí se vám sada emotikonů k porovnání." + "Pro větší bezpečnost chce jiný uživatel ověřit vaši identitu. Zobrazí se vám sada emotikonů k porovnání." "Na druhém zařízení byste měli vidět vyskakovací okno. Začněte s ověrením tam." "Spusťte ověření na druhém zařízení" "Čekání na druhé zařízení" diff --git a/features/verifysession/impl/src/main/res/values-eu/translations.xml b/features/verifysession/impl/src/main/res/values-eu/translations.xml index 999e7ca75d..2497e73069 100644 --- a/features/verifysession/impl/src/main/res/values-eu/translations.xml +++ b/features/verifysession/impl/src/main/res/values-eu/translations.xml @@ -21,11 +21,26 @@ "Saiatu berriro egiaztatzen" "Prest nago" "Bat etorriko zain…" + "Alderatu emojiak eta egiaztatu ordena berean ageri direla." "Saioa hasita" "Egiaztapenak huts egin du" + "Egiaztapen hau zeuk hasi baduzu bakarrik jarraitu." + "Egiaztatu beste gailua zure mezuen historia seguru mantentzeko." + "Orain mezuak modu seguruan irakurri edo bidal ditzakezu beste gailuan." "Gailua egiaztatu da" + "Egiaztapena eskatu da" "Ez datoz bat" "Bat datoz" + "Ziurtatu aplikazioa irekita duzula beste gailuan hemendik egiaztatzea hasi aurretik." + "Ireki aplikazioa egiaztatutako beste gailu batean" + "Segurtasun handiagorako, egiaztatu erabiltzailea emoji multzo bat alderatuz. Egin hau komunikatzeko modu fidagarri bat erabiliz." + "Erabiltzailea egiaztatu?" + "Segurtasun handiagorako, beste erabiltzaile batek zure identitatea egiaztatu nahi du. Emoji sorta bat erakutsiko zaizu konparatzeko." + "Beste gailuan laster-menu bat ikusi beharko zenuke. Hasi egiaztapena hortik orain." + "Hasi egiaztapena beste gailuan" + "Beste gailuaren zain" + "Beste erabiltzailearen zain" + "Onartutakoan egiaztapenarekin jarraitu ahal izango duzu." "Jarraitzeko, onartu zure beste saioan egiaztapen-prozesua hasteko eskaera." "Eskaera onartzeko zain" "Saioa amaitzen…" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a293b34b65..c5bde3385a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,9 +4,9 @@ [versions] # Project android_gradle_plugin = "8.9.1" -kotlin = "2.1.10" +kotlin = "2.1.20" kotlinpoet = "2.1.0" -ksp = "2.1.10-1.0.31" +ksp = "2.1.20-1.0.32" firebaseAppDistribution = "5.1.1" # AndroidX @@ -21,11 +21,11 @@ constraintlayout = "2.2.1" constraintlayout_compose = "1.1.1" lifecycle = "2.8.7" activity = "1.10.1" -media3 = "1.5.1" -camera = "1.4.1" +media3 = "1.6.0" +camera = "1.4.2" # Compose -compose_bom = "2025.03.00" +compose_bom = "2025.03.01" composecompiler = "1.5.15" # Coroutines @@ -39,18 +39,18 @@ test_core = "1.6.1" # Jetbrain datetime = "0.6.2" -serialization_json = "1.8.0" +serialization_json = "1.8.1" #other coil = "3.1.0" showkase = "1.0.3" -appyx = "1.6.0" +appyx = "1.7.0" sqldelight = "2.0.2" wysiwyg = "2.38.3" telephoto = "0.15.1" # Dependency analysis -dependencyAnalysis = "2.13.2" +dependencyAnalysis = "2.14.0" # DI dagger = "2.56.1" @@ -77,7 +77,7 @@ kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", ve ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } gms_google_services = "com.google.gms:google-services:4.4.2" # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:33.11.0" +google_firebase_bom = "com.google.firebase:firebase-bom:33.12.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } @@ -164,7 +164,7 @@ coil_network_okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version coil_compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil_gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } coil_test = { module = "io.coil-kt.coil3:coil-test", version.ref = "coil" } -compound = { module = "io.element.android:compound-android", version = "25.2.26" } +compound = { module = "io.element.android:compound-android", version = "25.4.4" } datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" } kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8" @@ -174,7 +174,7 @@ jsoup = "org.jsoup:jsoup:1.19.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.3.24" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.4.8" 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" } @@ -188,15 +188,15 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.2" -maplibre = "org.maplibre.gl:android-sdk:11.8.4" +maplibre = "org.maplibre.gl:android-sdk:11.8.5" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" opusencoder = "io.element.android:opusencoder:1.1.0" zxing_cpp = "io.github.zxing-cpp:android:2.3.0" # Analytics -posthog = "com.posthog:posthog-android:3.12.0" -sentry = "io.sentry:sentry-android:8.5.0" +posthog = "com.posthog:posthog-android:3.13.1" +sentry = "io.sentry:sentry-android:8.6.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" @@ -212,7 +212,7 @@ anvil_compiler_api = { module = "dev.zacsweers.anvil:compiler-api", version.ref anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" } # Element Call -element_call_embedded = "io.element.android:element-call-embedded:0.9.0-rc.4" +element_call_embedded = "io.element.android:element-call-embedded:0.9.0" # Auto services google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } @@ -239,7 +239,7 @@ anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" } detekt = "io.gitlab.arturbosch.detekt:1.23.8" ktlint = "org.jlleitschuh.gradle.ktlint:12.2.0" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:12.1.0" +dependencycheck = "org.owasp.dependencycheck:12.1.1" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:1.3.5" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index 48dbcade47..abd6e10d1f 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -98,3 +98,22 @@ fun String.ensureEndsLeftToRight() = if (containsRtLOverride()) "$this$LTR_OVERR fun String.containsRtLOverride() = contains(RTL_OVERRIDE_CHAR) fun String.filterDirectionOverrides() = filterNot { it == RTL_OVERRIDE_CHAR || it == LTR_OVERRIDE_CHAR } + +/** + * This works around https://github.com/element-hq/element-x-android/issues/2105. + * @param maxLength Max characters to retrieve. Defaults to `500`. + * @param ellipsize Whether to add an ellipsis (`…`) char at the end or not. Defaults to `false`. + * @return The string truncated to [maxLength] characters, with an optional ellipsis if larger. + */ +fun String.toSafeLength( + maxLength: Int = 500, + ellipsize: Boolean = false, +): String { + return if (ellipsize) { + ellipsize(maxLength) + } else if (length > maxLength) { + take(maxLength) + } else { + this + } +} diff --git a/libraries/dateformatter/impl/src/main/res/values-el/translations.xml b/libraries/dateformatter/impl/src/main/res/values-el/translations.xml index 3b337d1e29..63df4a0070 100644 --- a/libraries/dateformatter/impl/src/main/res/values-el/translations.xml +++ b/libraries/dateformatter/impl/src/main/res/values-el/translations.xml @@ -1,4 +1,5 @@ + "%1$s στις %2$s" "Αυτό το μήνα" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt index 4da9f58f69..51507064eb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt @@ -14,14 +14,10 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.Badge import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.badgeInfoBackgroundColor -import io.element.android.libraries.designsystem.theme.badgeInfoContentColor import io.element.android.libraries.designsystem.theme.badgeNegativeBackgroundColor import io.element.android.libraries.designsystem.theme.badgeNegativeContentColor import io.element.android.libraries.designsystem.theme.badgeNeutralBackgroundColor import io.element.android.libraries.designsystem.theme.badgeNeutralContentColor -import io.element.android.libraries.designsystem.theme.badgePositiveBackgroundColor -import io.element.android.libraries.designsystem.theme.badgePositiveContentColor object MatrixBadgeAtom { data class MatrixBadgeData( @@ -42,22 +38,22 @@ object MatrixBadgeAtom { data: MatrixBadgeData, ) { val backgroundColor = when (data.type) { - Type.Positive -> ElementTheme.colors.badgePositiveBackgroundColor + Type.Positive -> ElementTheme.colors.bgBadgeAccent Type.Neutral -> ElementTheme.colors.badgeNeutralBackgroundColor Type.Negative -> ElementTheme.colors.badgeNegativeBackgroundColor - Type.Info -> ElementTheme.colors.badgeInfoBackgroundColor + Type.Info -> ElementTheme.colors.bgBadgeInfo } val textColor = when (data.type) { - Type.Positive -> ElementTheme.colors.badgePositiveContentColor + Type.Positive -> ElementTheme.colors.textBadgeAccent Type.Neutral -> ElementTheme.colors.badgeNeutralContentColor Type.Negative -> ElementTheme.colors.badgeNegativeContentColor - Type.Info -> ElementTheme.colors.badgeInfoContentColor + Type.Info -> ElementTheme.colors.textBadgeInfo } val iconColor = when (data.type) { - Type.Positive -> ElementTheme.colors.iconSuccessPrimary + Type.Positive -> ElementTheme.colors.textBadgeAccent Type.Neutral -> ElementTheme.colors.iconSecondary Type.Negative -> ElementTheme.colors.iconCriticalPrimary - Type.Info -> ElementTheme.colors.iconInfoPrimary + Type.Info -> ElementTheme.colors.textBadgeInfo } Badge( text = data.text, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt index d49e0a41e0..5e2160a01f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt @@ -25,8 +25,6 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.badgePositiveBackgroundColor -import io.element.android.libraries.designsystem.theme.badgePositiveContentColor 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 @@ -76,9 +74,9 @@ internal fun BadgePreview() { Badge( text = "Trusted", icon = CompoundIcons.Verified(), - backgroundColor = ElementTheme.colors.badgePositiveBackgroundColor, - textColor = ElementTheme.colors.badgePositiveContentColor, - iconColor = ElementTheme.colors.iconSuccessPrimary, + backgroundColor = ElementTheme.colors.bgBadgeAccent, + textColor = ElementTheme.colors.textBadgeAccent, + iconColor = ElementTheme.colors.textBadgeAccent, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index 169403104a..3638556da3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -135,7 +135,7 @@ fun ClickableLinkText( fun AnnotatedString.linkify(linkStyle: SpanStyle): AnnotatedString { val original = this - val spannable = SpannableString(this.text) + val spannable = SpannableString.valueOf(this.text) LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES) val spans = spannable.getSpans(0, spannable.length, URLSpan::class.java) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt index 0f8f1e43d8..944fca1cf4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.designsystem.components.dialogs +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope @@ -38,6 +39,7 @@ fun ListDialog( cancelText: String = stringResource(CommonStrings.action_cancel), submitText: String = stringResource(CommonStrings.action_ok), enabled: Boolean = true, + applyPaddingToContents: Boolean = true, listItems: LazyListScope.() -> Unit, ) { val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let { @@ -61,6 +63,7 @@ fun ListDialog( onSubmitClick = onSubmit, enabled = enabled, listItems = listItems, + applyPaddingToContents = applyPaddingToContents, ) } } @@ -72,8 +75,9 @@ private fun ListDialogContent( onSubmitClick: () -> Unit, cancelText: String, submitText: String, - title: String? = null, - enabled: Boolean = true, + title: String?, + enabled: Boolean, + applyPaddingToContents: Boolean, subtitle: @Composable (() -> Unit)? = null, ) { SimpleAlertDialogContent( @@ -84,10 +88,13 @@ private fun ListDialogContent( onCancelClick = onDismissRequest, onSubmitClick = onSubmitClick, enabled = enabled, - applyPaddingToContents = false, + applyPaddingToContents = applyPaddingToContents, ) { + // No start padding if padding is already applied to the content + val horizontalPadding = if (applyPaddingToContents) 0.dp else 8.dp LazyColumn( - modifier = Modifier.padding(start = 8.dp) + modifier = Modifier.padding(horizontal = horizontalPadding), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { listItems() } } } @@ -111,6 +118,8 @@ internal fun ListDialogContentPreview() { onSubmitClick = {}, cancelText = "Cancel", submitText = "Save", + enabled = true, + applyPaddingToContents = true, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt new file mode 100644 index 0000000000..bb31edd313 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.components.list.TextFieldListItem +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun TextFieldDialog( + title: String, + onSubmit: (String) -> Unit, + onDismissRequest: () -> Unit, + value: String?, + placeholder: String?, + modifier: Modifier = Modifier, + validation: (String?) -> Boolean = { true }, + onValidationErrorMessage: String? = null, + autoSelectOnDisplay: Boolean = true, + maxLines: Int = 1, + content: String? = null, + label: String? = null, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + submitText: String = stringResource(CommonStrings.action_ok), +) { + val focusRequester = remember { FocusRequester() } + var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf( + TextFieldValue( + value.orEmpty(), + selection = TextRange(value.orEmpty().length) + ) + ) + } + var error by rememberSaveable { mutableStateOf(if (!validation(value.orEmpty())) onValidationErrorMessage else null) } + var canRequestFocus by rememberSaveable { mutableStateOf(false) } + val canSubmit by remember { derivedStateOf { validation(textFieldContents.text) } } + ListDialog( + title = title, + onSubmit = { onSubmit(textFieldContents.text) }, + onDismissRequest = onDismissRequest, + enabled = canSubmit, + submitText = submitText, + modifier = modifier, + ) { + if (content != null) { + item { + Text( + text = content, + style = ElementTheme.materialTypography.bodyMedium, + ) + } + } + item { + TextFieldListItem( + placeholder = placeholder.orEmpty(), + label = label, + text = textFieldContents, + onTextChange = { + error = if (!validation(it.text)) onValidationErrorMessage else null + textFieldContents = it + }, + error = error, + keyboardOptions = keyboardOptions, + keyboardActions = KeyboardActions(onAny = { + if (validation(textFieldContents.text)) { + onSubmit(textFieldContents.text) + } + }), + maxLines = maxLines, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + ) + canRequestFocus = true + } + } + + if (autoSelectOnDisplay && canRequestFocus) { + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + } +} + +@PreviewsDayNight +@Composable +internal fun TextFieldDialogPreview() = ElementPreview { + TextFieldDialog( + title = "Title", + value = "", + placeholder = "Placeholder", + onSubmit = {}, + onDismissRequest = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun TextFieldDialogWithErrorPreview() = ElementPreview { + TextFieldDialog( + title = "Title", + content = "Some content", + onSubmit = {}, + validation = { false }, + onDismissRequest = {}, + value = "Value", + placeholder = "Placeholder", + label = "Label", + onValidationErrorMessage = "Error message", + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt index 299b41ad94..f9d61b6593 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt @@ -9,17 +9,14 @@ package io.element.android.libraries.designsystem.components.list import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.designsystem.theme.components.TextFieldValidity @Composable fun TextFieldListItem( @@ -29,26 +26,19 @@ fun TextFieldListItem( modifier: Modifier = Modifier, error: String? = null, maxLines: Int = 1, + label: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, ) { - val textFieldStyle = ElementTheme.materialTypography.bodyLarge - - OutlinedTextField( + TextField( value = text, onValueChange = { onTextChange(it) }, - placeholder = placeholder?.let { @Composable { Text(it) } }, - colors = OutlinedTextFieldDefaults.colors( - disabledBorderColor = Color.Transparent, - errorBorderColor = Color.Transparent, - focusedBorderColor = Color.Transparent, - unfocusedBorderColor = Color.Transparent, - ), - isError = error != null, - supportingText = error?.let { @Composable { Text(it) } }, + placeholder = placeholder, + label = label, + validity = if (error != null) TextFieldValidity.Invalid else TextFieldValidity.None, + supportingText = error, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, - textStyle = textFieldStyle, maxLines = maxLines, singleLine = maxLines == 1, modifier = modifier, @@ -63,26 +53,19 @@ fun TextFieldListItem( modifier: Modifier = Modifier, error: String? = null, maxLines: Int = 1, + label: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, ) { - val textFieldStyle = ElementTheme.materialTypography.bodyLarge - - OutlinedTextField( + TextField( value = text, onValueChange = { onTextChange(it) }, - placeholder = placeholder?.let { @Composable { Text(it) } }, - colors = OutlinedTextFieldDefaults.colors( - disabledBorderColor = Color.Transparent, - errorBorderColor = Color.Transparent, - focusedBorderColor = Color.Transparent, - unfocusedBorderColor = Color.Transparent, - ), - isError = error != null, - supportingText = error?.let { @Composable { Text(it) } }, + placeholder = placeholder, + label = label, + validity = if (error != null) TextFieldValidity.Invalid else TextFieldValidity.None, + supportingText = error, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, - textStyle = textFieldStyle, maxLines = maxLines, singleLine = maxLines == 1, modifier = modifier, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt index 8810c83f54..98f324f88a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt @@ -7,24 +7,15 @@ package io.element.android.libraries.designsystem.components.preferences -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.TextFieldValue -import io.element.android.libraries.designsystem.components.dialogs.ListDialog +import io.element.android.libraries.designsystem.components.dialogs.TextFieldDialog import io.element.android.libraries.designsystem.components.list.ListItemContent -import io.element.android.libraries.designsystem.components.list.TextFieldListItem import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.Text @@ -74,58 +65,3 @@ fun PreferenceTextField( ) } } - -@Composable -private fun TextFieldDialog( - title: String, - onSubmit: (String) -> Unit, - onDismissRequest: () -> Unit, - value: String?, - placeholder: String?, - validation: (String?) -> Boolean = { true }, - onValidationErrorMessage: String? = null, - autoSelectOnDisplay: Boolean = true, - maxLines: Int = 1, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, -) { - val focusRequester = remember { FocusRequester() } - var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(value.orEmpty(), selection = TextRange(value.orEmpty().length))) - } - var error by rememberSaveable { mutableStateOf(null) } - var canRequestFocus by rememberSaveable { mutableStateOf(false) } - val canSubmit by remember { derivedStateOf { validation(textFieldContents.text) } } - ListDialog( - title = title, - onSubmit = { onSubmit(textFieldContents.text) }, - onDismissRequest = onDismissRequest, - enabled = canSubmit, - ) { - item { - TextFieldListItem( - placeholder = placeholder.orEmpty(), - text = textFieldContents, - onTextChange = { - error = if (!validation(it.text)) onValidationErrorMessage else null - textFieldContents = it - }, - error = error, - keyboardOptions = keyboardOptions, - keyboardActions = KeyboardActions(onAny = { - if (validation(textFieldContents.text)) { - onSubmit(textFieldContents.text) - } - }), - maxLines = maxLines, - modifier = Modifier.focusRequester(focusRequester), - ) - canRequestFocus = true - } - } - - if (autoSelectOnDisplay && canRequestFocus) { - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - } -} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt index c8f71f16cc..3931d8d8d0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt @@ -8,7 +8,11 @@ package io.element.android.libraries.designsystem.modifiers import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp fun Modifier.clickableIfNotNull(onClick: (() -> Unit)? = null): Modifier = then( if (onClick != null) { @@ -17,3 +21,11 @@ fun Modifier.clickableIfNotNull(onClick: (() -> Unit)? = null): Modifier = then( Modifier } ) + +fun Modifier.niceClickable( + onClick: () -> Unit, +): Modifier { + return clip(RoundedCornerShape(4.dp)) + .clickable { onClick() } + .padding(horizontal = 4.dp) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt index 36ac7d71bd..79f70cc2f5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt @@ -8,7 +8,7 @@ package io.element.android.libraries.designsystem.text import android.graphics.Typeface -import android.text.SpannableString +import android.text.SpannedString import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan import android.text.style.UnderlineSpan @@ -26,7 +26,7 @@ import io.element.android.compound.theme.LinkColor fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString { append(this@toAnnotatedString) - val spannable = SpannableString(this@toAnnotatedString) + val spannable = SpannedString.valueOf(this@toAnnotatedString) spannable.getSpans(0, spannable.length, Any::class.java).forEach { span -> val start = spannable.getSpanStart(span) val end = spannable.getSpanEnd(span) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt index 503acdb6a4..5576a56519 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt @@ -38,77 +38,42 @@ val SemanticColors.placeholderBackground get() = bgSubtleSecondary // This color is not present in Semantic color, so put hard-coded value for now +@OptIn(CoreColorToken::class) val SemanticColors.messageFromMeBackground - get() = if (isLight) { - // We want LightDesignTokens.colorGray400 - Color(0xFFE1E6EC) - } else { - // We want DarkDesignTokens.colorGray500 - Color(0xFF323539) - } + get() = if (isLight) LightColorTokens.colorGray400 else DarkColorTokens.colorGray500 // This color is not present in Semantic color, so put hard-coded value for now +@OptIn(CoreColorToken::class) val SemanticColors.messageFromOtherBackground - get() = if (isLight) { - // We want LightDesignTokens.colorGray300 - Color(0xFFF0F2F5) - } else { - // We want DarkDesignTokens.colorGray400 - Color(0xFF26282D) - } + get() = if (isLight) LightColorTokens.colorGray300 else DarkColorTokens.colorGray400 // This color is not present in Semantic color, so put hard-coded value for now +@OptIn(CoreColorToken::class) val SemanticColors.progressIndicatorTrackColor - get() = if (isLight) { - // We want LightDesignTokens.colorAlphaGray500 - Color(0x33052448) - } else { - // We want DarkDesignTokens.colorAlphaGray500 - Color(0x25F4F7FA) - } + get() = if (isLight) LightColorTokens.colorAlphaGray500 else DarkColorTokens.colorAlphaGray500 // This color is not present in Semantic color, so put hard-coded value for now +@OptIn(CoreColorToken::class) val SemanticColors.iconSuccessPrimaryBackground - get() = if (isLight) { - // We want LightDesignTokens.colorGreen300 - Color(0xffe3f7ed) - } else { - // We want DarkDesignTokens.colorGreen300 - Color(0xff002513) - } + get() = if (isLight) LightColorTokens.colorGreen300 else DarkColorTokens.colorGreen300 // This color is not present in Semantic color, so put hard-coded value for now +@OptIn(CoreColorToken::class) val SemanticColors.bgSubtleTertiary - get() = if (isLight) { - // We want LightDesignTokens.colorGray100 - Color(0xfffbfcfd) - } else { - // We want DarkDesignTokens.colorGray100 - Color(0xff14171b) - } + get() = if (isLight) LightColorTokens.colorGray100 else DarkColorTokens.colorGray100 // Temporary color, which is not in the token right now val SemanticColors.temporaryColorBgSpecial get() = if (isLight) Color(0xFFE4E8F0) else Color(0xFF3A4048) // This color is not present in Semantic color, so put hard-coded value for now +@OptIn(CoreColorToken::class) val SemanticColors.pinDigitBg - get() = if (isLight) { - // We want LightDesignTokens.colorGray300 - Color(0xFFF0F2F5) - } else { - // We want DarkDesignTokens.colorGray400 - Color(0xFF26282D) - } + get() = if (isLight) LightColorTokens.colorGray300 else DarkColorTokens.colorGray400 +@OptIn(CoreColorToken::class) val SemanticColors.currentUserMentionPillText - get() = if (isLight) { - // We want LightDesignTokens.colorGreen1100 - Color(0xff005c45) - } else { - // We want DarkDesignTokens.colorGreen1100 - Color(0xff1fc090) - } + get() = if (isLight) LightColorTokens.colorGreen1100 else DarkColorTokens.colorGreen1100 val SemanticColors.currentUserMentionPillBackground get() = if (isLight) { @@ -141,14 +106,6 @@ val SemanticColors.highlightedMessageBackgroundColor // Badge colors -@OptIn(CoreColorToken::class) -val SemanticColors.badgePositiveBackgroundColor - get() = if (isLight) LightColorTokens.colorAlphaGreen300 else DarkColorTokens.colorAlphaGreen300 - -@OptIn(CoreColorToken::class) -val SemanticColors.badgePositiveContentColor - get() = if (isLight) LightColorTokens.colorGreen1100 else DarkColorTokens.colorGreen1100 - @OptIn(CoreColorToken::class) val SemanticColors.badgeNeutralBackgroundColor get() = if (isLight) LightColorTokens.colorAlphaGray300 else DarkColorTokens.colorAlphaGray300 @@ -165,14 +122,6 @@ val SemanticColors.badgeNegativeBackgroundColor val SemanticColors.badgeNegativeContentColor get() = if (isLight) LightColorTokens.colorRed1100 else DarkColorTokens.colorRed1100 -@OptIn(CoreColorToken::class) -val SemanticColors.badgeInfoBackgroundColor - get() = if (isLight) LightColorTokens.colorAlphaBlue300 else DarkColorTokens.colorAlphaBlue300 - -@OptIn(CoreColorToken::class) -val SemanticColors.badgeInfoContentColor - get() = if (isLight) LightColorTokens.colorBlue1100 else DarkColorTokens.colorBlue1100 - @OptIn(CoreColorToken::class) val SemanticColors.pinnedMessageBannerIndicator get() = if (isLight) LightColorTokens.colorAlphaGray600 else DarkColorTokens.colorAlphaGray600 diff --git a/libraries/eventformatter/impl/src/main/res/values-eu/translations.xml b/libraries/eventformatter/impl/src/main/res/values-eu/translations.xml index ecd1489c3d..20c4c8e160 100644 --- a/libraries/eventformatter/impl/src/main/res/values-eu/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-eu/translations.xml @@ -49,6 +49,8 @@ "Finkatutako mezuak aldatu dituzu" "%1$s(e)k mezu bat finkatu du" "Mezu bat finkatu duzu" + "%1$s(e)k mezu bat finkatzeari utzi dio" + "Mezu bat finkatzeari utzi diozu" "%1$s(e)k gonbidapena baztertu du" "Gonbidapena baztertu duzu" "%1$s(e)k %2$s kendu du" 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 c19793fb8d..6a85cf45f5 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 @@ -42,10 +42,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import java.io.Closeable import java.util.Optional -interface MatrixClient : Closeable { +interface MatrixClient { val sessionId: SessionId val deviceId: DeviceId val userProfile: StateFlow 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 915e1584a9..2c77f70d0a 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 @@ -9,12 +9,14 @@ package io.element.android.libraries.matrix.api.notification import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.timeline.item.event.MessageType data class NotificationData( val eventId: EventId, + val threadId: ThreadId?, val roomId: RoomId, // mxc url val senderAvatarUrl: String?, 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 ec94ec0b92..a2a737a019 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 @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility @@ -138,7 +139,8 @@ interface MatrixRoom : Closeable { imageInfo: ImageInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result suspend fun sendVideo( @@ -147,7 +149,8 @@ interface MatrixRoom : Closeable { videoInfo: VideoInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result suspend fun sendAudio( @@ -156,6 +159,7 @@ interface MatrixRoom : Closeable { caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result suspend fun sendFile( @@ -164,8 +168,36 @@ interface MatrixRoom : Closeable { caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result + suspend fun sendVoiceMessage( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ): Result + + /** + * Share a location message in the room. + * + * @param body A human readable textual representation of the location. + * @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`. + * Respectively: latitude, longitude, and (optional) uncertainty. + * @param description Optional description of the location to display to the user. + * @param zoomLevel Optional zoom level to display the map at. + * @param assetType Optional type of the location asset. + * Set to SENDER if sharing own location. Set to PIN if sharing any location. + */ + suspend fun sendLocation( + body: String, + geoUri: String, + description: String? = null, + zoomLevel: Int? = null, + assetType: AssetType? = null, + ): Result + suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result suspend fun forwardEvent(eventId: EventId, roomIds: List): Result @@ -235,25 +267,6 @@ interface MatrixRoom : Closeable { */ suspend fun clearEventCacheStorage(): Result - /** - * Share a location message in the room. - * - * @param body A human readable textual representation of the location. - * @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`. - * Respectively: latitude, longitude, and (optional) uncertainty. - * @param description Optional description of the location to display to the user. - * @param zoomLevel Optional zoom level to display the map at. - * @param assetType Optional type of the location asset. - * Set to SENDER if sharing own location. Set to PIN if sharing any location. - */ - suspend fun sendLocation( - body: String, - geoUri: String, - description: String? = null, - zoomLevel: Int? = null, - assetType: AssetType? = null, - ): Result - /** * Create a poll in the room. * @@ -302,13 +315,6 @@ interface MatrixRoom : Closeable { */ suspend fun endPoll(pollStartId: EventId, text: String): Result - suspend fun sendVoiceMessage( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback? - ): Result - /** * Send a typing notification. * @param isTyping True if the user is typing, false otherwise. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/message/ReplyParameters.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/message/ReplyParameters.kt new file mode 100644 index 0000000000..6157989a43 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/message/ReplyParameters.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.message + +import io.element.android.libraries.matrix.api.core.EventId + +data class ReplyParameters( + val inReplyToEventId: EventId, + val enforceThreadReply: Boolean, + val replyWithinThread: Boolean, +) + +fun replyInThread(eventId: EventId, explicitReply: Boolean = false) = ReplyParameters( + inReplyToEventId = eventId, + enforceThreadReply = true, + replyWithinThread = explicitReply, +) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt index 21554dbbd6..13cb54b500 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -7,7 +7,6 @@ package io.element.android.libraries.matrix.api.sync -import io.element.android.libraries.core.coroutine.mapState import kotlinx.coroutines.flow.StateFlow interface SyncService { @@ -25,6 +24,6 @@ interface SyncService { * Flow of [SyncState]. Will be updated as soon as the current [SyncState] changes. */ val syncState: StateFlow -} -fun SyncService.isOnline(): StateFlow = syncState.mapState { it != SyncState.Offline } + val isOnline: StateFlow +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index c74f7b2c00..a940a0981f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId @@ -49,7 +50,10 @@ interface Timeline : AutoCloseable { val membershipChangeEventReceived: Flow suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result suspend fun paginate(direction: PaginationDirection): Result - fun paginationStatus(direction: PaginationDirection): StateFlow + + val backwardPaginationStatus: StateFlow + val forwardPaginationStatus: StateFlow + val timelineItems: Flow> suspend fun sendMessage( @@ -72,7 +76,7 @@ interface Timeline : AutoCloseable { ): Result suspend fun replyMessage( - eventId: EventId, + replyParameters: ReplyParameters, body: String, htmlBody: String?, intentionalMentions: List, @@ -85,7 +89,8 @@ interface Timeline : AutoCloseable { imageInfo: ImageInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result suspend fun sendVideo( @@ -94,18 +99,18 @@ interface Timeline : AutoCloseable { videoInfo: VideoInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result - suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result - suspend fun sendAudio( file: File, audioInfo: AudioInfo, caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, - ): Result + replyParameters: ReplyParameters?, + ): Result suspend fun sendFile( file: File, @@ -113,15 +118,9 @@ interface Timeline : AutoCloseable { caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result - suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result - - suspend fun forwardEvent(eventId: EventId, roomIds: List): Result - - suspend fun cancelSend(transactionId: TransactionId): Result = - redactEvent(transactionId.toEventOrTransactionId(), reason = null) - /** * Share a location message in the room. * @@ -141,6 +140,23 @@ interface Timeline : AutoCloseable { assetType: AssetType? = null, ): Result + suspend fun sendVoiceMessage( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ): Result + + suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result + + suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result + + suspend fun forwardEvent(eventId: EventId, roomIds: List): Result + + suspend fun cancelSend(transactionId: TransactionId): Result = + redactEvent(transactionId.toEventOrTransactionId(), reason = null) + /** * Create a poll in the room. * @@ -189,13 +205,6 @@ interface Timeline : AutoCloseable { */ suspend fun endPoll(pollStartId: EventId, text: String): Result - suspend fun sendVoiceMessage( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback? - ): Result - suspend fun loadReplyDetails(eventId: EventId): InReplyTo /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt new file mode 100644 index 0000000000..b0a67a9e92 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.tracing + +enum class TraceLogPack(val key: String) { + EVENT_CACHE("event_cache") { + override val title: String = "Event Cache" + }, + SEND_QUEUE("send_queue") { + override val title: String = "Send Queue" + }, + TIMELINE("timeline") { + override val title: String = "Timeline" + }; + + abstract val title: String +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt index db2c946bf5..f559176fa6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.api.tracing data class TracingConfiguration( val logLevel: LogLevel, val extraTargets: List, + val traceLogPacks: Set, val writesToLogcat: Boolean, val writesToFilesConfiguration: WriteToFilesConfiguration, ) 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 6a69511fd3..d3a004115f 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 @@ -476,16 +476,13 @@ class RustMatrixClient( override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService - override fun close() { + internal suspend fun destroy() { innerNotificationClient.close() - appCoroutineScope.launch { - roomFactory.destroy() - rustSyncService.destroy() - notificationSettingsService.destroy() - // This is sync, but it can destroy the `Client` instance and block stopping the sync service - notificationProcessSetup.destroy() - } + roomFactory.destroy() + rustSyncService.destroy() + notificationSettingsService.destroy() + notificationProcessSetup.destroy() sessionCoroutineScope.cancel() clientDelegateTaskHandle?.cancelAndDestroy() @@ -504,7 +501,7 @@ class RustMatrixClient( override suspend fun clearCache() { innerClient.clearCaches() - close() + destroy() } override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean) { @@ -527,7 +524,7 @@ class RustMatrixClient( } } } - close() + destroy() deleteSessionDirectory() if (userInitiated) { @@ -577,7 +574,7 @@ class RustMatrixClient( throw it } } - close() + destroy() deleteSessionDirectory() sessionStore.removeSession(sessionId.value) }.onFailure { 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 deca69e1e9..b302784528 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 @@ -105,7 +105,7 @@ class RustMatrixClientFactory @Inject constructor( cachePath = sessionPaths.cacheDirectory.absolutePath, ) .setSessionDelegate(sessionDelegate) - .passphrase(passphrase) + .sessionPassphrase(passphrase) .userAgent(userAgentProvider.provide()) .addRootCertificates(userCertificatesProvider.provides()) .autoEnableBackups(true) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt index b253985942..ec220112de 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt @@ -9,12 +9,9 @@ package io.element.android.libraries.matrix.impl.auth import io.element.android.libraries.matrix.api.auth.OidcConfig import org.matrix.rustcomponents.sdk.OidcConfiguration -import java.io.File import javax.inject.Inject -class OidcConfigurationProvider @Inject constructor( - private val baseDirectory: File, -) { +class OidcConfigurationProvider @Inject constructor() { fun get(): OidcConfiguration = OidcConfiguration( clientName = "Element", redirectUri = OidcConfig.REDIRECT_URI, @@ -29,6 +26,5 @@ class OidcConfigurationProvider @Inject constructor( staticRegistrations = mapOf( "https://id.thirdroom.io/realms/thirdroom" to "elementx", ), - dynamicRegistrationsFile = File(baseDirectory, "oidc/registrations.json").absolutePath, ) } 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 2549fc622a..33af371569 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 @@ -324,7 +324,7 @@ class RustMatrixAuthenticationService @Inject constructor( passphrase = pendingPassphrase, slidingSyncType = ClientBuilderSlidingSync.Discovered, ) - .passphrase(passphrase) + .sessionPassphrase(passphrase) .buildWithQrCode(qrCodeData, oidcConfiguration, progressListener) } 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 8b21648071..7fdb4a9fdc 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 @@ -36,6 +36,8 @@ class NotificationMapper( ) NotificationData( eventId = eventId, + // FIXME once the `NotificationItem` in the SDK returns the thread id + threadId = null, roomId = roomId, senderAvatarUrl = item.senderInfo.avatarUrl, senderDisplayName = item.senderInfo.displayName, 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 5b742e83f8..7ed2ad5eb6 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 @@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.roomNotificationSettings @@ -497,8 +498,17 @@ class RustMatrixRoom( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { - return liveTimeline.sendImage(file, thumbnailFile, imageInfo, caption, formattedCaption, progressCallback) + return liveTimeline.sendImage( + file = file, + thumbnailFile = thumbnailFile, + imageInfo = imageInfo, + caption = caption, + formattedCaption = formattedCaption, + progressCallback = progressCallback, + replyParameters = replyParameters + ) } override suspend fun sendVideo( @@ -508,8 +518,17 @@ class RustMatrixRoom( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { - return liveTimeline.sendVideo(file, thumbnailFile, videoInfo, caption, formattedCaption, progressCallback) + return liveTimeline.sendVideo( + file = file, + thumbnailFile = thumbnailFile, + videoInfo = videoInfo, + caption = caption, + formattedCaption = formattedCaption, + progressCallback = progressCallback, + replyParameters = replyParameters + ) } override suspend fun sendAudio( @@ -518,6 +537,7 @@ class RustMatrixRoom( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { return liveTimeline.sendAudio( file = file, @@ -525,6 +545,7 @@ class RustMatrixRoom( caption = caption, formattedCaption = formattedCaption, progressCallback = progressCallback, + replyParameters = replyParameters, ) } @@ -534,16 +555,44 @@ class RustMatrixRoom( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { return liveTimeline.sendFile( - file, - fileInfo, - caption, - formattedCaption, - progressCallback, + file = file, + fileInfo = fileInfo, + caption = caption, + formattedCaption = formattedCaption, + progressCallback = progressCallback, + replyParameters = replyParameters, ) } + override suspend fun sendVoiceMessage( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ): Result { + return liveTimeline.sendVoiceMessage( + file = file, + audioInfo = audioInfo, + waveform = waveform, + progressCallback = progressCallback, + replyParameters = replyParameters, + ) + } + + override suspend fun sendLocation( + body: String, + geoUri: String, + description: String?, + zoomLevel: Int?, + assetType: AssetType?, + ): Result { + return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType) + } + override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result { return liveTimeline.toggleReaction(emoji, eventOrTransactionId) } @@ -631,16 +680,6 @@ class RustMatrixRoom( } } - override suspend fun sendLocation( - body: String, - geoUri: String, - description: String?, - zoomLevel: Int?, - assetType: AssetType?, - ): Result { - return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType) - } - override suspend fun createPoll( question: String, answers: List, @@ -674,15 +713,6 @@ class RustMatrixRoom( return liveTimeline.endPoll(pollStartId, text) } - override suspend fun sendVoiceMessage( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback?, - ): Result { - return liveTimeline.sendVoiceMessage(file, audioInfo, waveform, progressCallback) - } - override suspend fun typingNotice(isTyping: Boolean) = withContext(roomDispatcher) { runCatching { innerRoom.typingNotice(isTyping) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index 2bcd15c142..a65112fca9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -81,6 +81,10 @@ class RustRoomFactory( withContext(NonCancellable + dispatcher) { mutex.withLock { Timber.d("Destroying room factory") + cache.snapshot().values.forEach { (listItem, innerRoom) -> + innerRoom.destroy() + listItem.destroy() + } cache.evictAll() isDestroyed = true } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/ReplyParameters.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/ReplyParameters.kt new file mode 100644 index 0000000000..415d493e7d --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/ReplyParameters.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.room.message + +import io.element.android.libraries.matrix.api.room.message.ReplyParameters + +fun ReplyParameters.map() = org.matrix.rustcomponents.sdk.ReplyParameters( + eventId = inReplyToEventId.value, + enforceThread = enforceThreadReply, + replyWithinThread = replyWithinThread, +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 3318e1384f..1d8eb7c0ea 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.impl.sync +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import kotlinx.coroutines.CoroutineDispatcher @@ -73,4 +74,6 @@ class RustSyncService( } .distinctUntilChanged() .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) + + override val isOnline: StateFlow = syncState.mapState { it != SyncState.Offline } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 46c2bfebd4..f13b5465fb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters 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.Timeline @@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.impl.media.toMSC3246range import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.location.toInner +import io.element.android.libraries.matrix.impl.room.message.map 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 @@ -54,7 +56,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -127,11 +128,11 @@ class RustTimeline( private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode) private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode) - private val backPaginationStatus = MutableStateFlow( + override val backwardPaginationStatus = MutableStateFlow( Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PINNED_EVENTS) ) - private val forwardPaginationStatus = MutableStateFlow( + override val forwardPaginationStatus = MutableStateFlow( Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode == Timeline.Mode.FOCUSED_ON_EVENT) ) @@ -167,7 +168,7 @@ class RustTimeline( private fun updatePaginationStatus(direction: Timeline.PaginationDirection, update: (Timeline.PaginationStatus) -> Timeline.PaginationStatus) { when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus.getAndUpdate(update) + Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus.getAndUpdate(update) Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.getAndUpdate(update) } } @@ -185,7 +186,7 @@ class RustTimeline( } }.onFailure { error -> if (error is TimelineException.CannotPaginate) { - Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backPaginationStatus.value}") + Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backwardPaginationStatus.value}") } else { updatePaginationStatus(direction) { it.copy(isPaginating = false) } Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}") @@ -199,21 +200,14 @@ class RustTimeline( private fun canPaginate(direction: Timeline.PaginationDirection): Boolean { if (!isTimelineInitialized.value) return false return when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus.value.canPaginate + Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus.value.canPaginate Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.value.canPaginate } } - override fun paginationStatus(direction: Timeline.PaginationDirection): StateFlow { - return when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus - Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus - } - } - override val timelineItems: Flow> = combine( _timelineItems, - backPaginationStatus, + backwardPaginationStatus, forwardPaginationStatus, matrixRoom.roomInfoFlow.map { it.creator to it.isDm }.distinctUntilChanged(), isTimelineInitialized, @@ -336,7 +330,7 @@ class RustTimeline( } override suspend fun replyMessage( - eventId: EventId, + replyParameters: ReplyParameters, body: String, htmlBody: String?, intentionalMentions: List, @@ -344,7 +338,10 @@ class RustTimeline( ): Result = withContext(dispatcher) { runCatching { val msg = MessageEventContent.from(body, htmlBody, intentionalMentions) - inner.sendReply(msg, eventId.value) + inner.sendReply( + msg = msg, + replyParams = replyParameters.map(), + ) } } @@ -355,6 +352,7 @@ class RustTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) return sendAttachment(listOfNotNull(file, thumbnailFile)) { @@ -367,6 +365,7 @@ class RustTimeline( }, useSendQueue = useSendQueue, mentions = null, + replyParams = replyParameters?.map(), ), thumbnailPath = thumbnailFile?.path, imageInfo = imageInfo.map(), @@ -382,6 +381,7 @@ class RustTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) return sendAttachment(listOfNotNull(file, thumbnailFile)) { @@ -394,6 +394,7 @@ class RustTimeline( }, useSendQueue = useSendQueue, mentions = null, + replyParams = replyParameters?.map(), ), thumbnailPath = thumbnailFile?.path, videoInfo = videoInfo.map(), @@ -408,6 +409,7 @@ class RustTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) return sendAttachment(listOf(file)) { @@ -420,6 +422,7 @@ class RustTimeline( }, useSendQueue = useSendQueue, mentions = null, + replyParams = replyParameters?.map(), ), audioInfo = audioInfo.map(), progressWatcher = progressCallback?.toProgressWatcher() @@ -433,6 +436,7 @@ class RustTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) return sendAttachment(listOf(file)) { @@ -445,6 +449,7 @@ class RustTimeline( }, useSendQueue = useSendQueue, mentions = null, + replyParams = replyParameters?.map(), ), fileInfo = fileInfo.map(), progressWatcher = progressCallback?.toProgressWatcher(), @@ -487,6 +492,32 @@ class RustTimeline( } } + override suspend fun sendVoiceMessage( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ): Result { + val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) + return sendAttachment(listOf(file)) { + inner.sendVoiceMessage( + params = UploadParameters( + filename = file.path, + // Maybe allow a caption in the future? + caption = null, + formattedCaption = null, + useSendQueue = useSendQueue, + mentions = null, + replyParams = replyParameters?.map(), + ), + audioInfo = audioInfo.map(), + waveform = waveform.toMSC3246range(), + progressWatcher = progressCallback?.toProgressWatcher(), + ) + } + } + override suspend fun createPoll( question: String, answers: List, @@ -550,30 +581,6 @@ class RustTimeline( } } - override suspend fun sendVoiceMessage( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback?, - ): Result { - val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue) - return sendAttachment(listOf(file)) { - inner.sendVoiceMessage( - params = UploadParameters( - filename = file.path, - // Maybe allow a caption in the future? - caption = null, - formattedCaption = null, - useSendQueue = useSendQueue, - mentions = null, - ), - audioInfo = audioInfo.map(), - waveform = waveform.toMSC3246range(), - progressWatcher = progressCallback?.toProgressWatcher(), - ) - } - } - 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/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 19e4c36c6f..4d1de2cbfa 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershi 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.UtdCause import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.poll.map @@ -110,7 +109,6 @@ class TimelineEventContentMapper( } is TimelineItemContent.CallInvite -> LegacyCallInviteContent is TimelineItemContent.CallNotify -> CallNotifyContent - else -> UnknownContent } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt index 8df231bbab..d60d88f168 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt @@ -15,6 +15,7 @@ class VirtualTimelineItemMapper { return when (virtualTimelineItem) { is RustVirtualTimelineItem.DateDivider -> VirtualTimelineItem.DayDivider(virtualTimelineItem.ts.toLong()) RustVirtualTimelineItem.ReadMarker -> VirtualTimelineItem.ReadMarker + RustVirtualTimelineItem.TimelineStart -> VirtualTimelineItem.RoomBeginning } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt index cfc03e50b9..4dded4519d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt @@ -7,8 +7,6 @@ package io.element.android.libraries.matrix.impl.timeline.postprocessor -import androidx.annotation.VisibleForTesting -import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.Timeline @@ -32,55 +30,59 @@ class RoomBeginningPostProcessor(private val mode: Timeline.Mode) { return when { items.isEmpty() -> items mode == Timeline.Mode.PINNED_EVENTS -> items - isDm -> processForDM(items, roomCreator) + isDm -> processForDM(items, roomCreator, hasMoreToLoadBackwards) hasMoreToLoadBackwards -> items else -> processForRoom(items) } } private fun processForRoom(items: List): List { - val roomBeginningItem = createRoomBeginningItem() - return listOf(roomBeginningItem) + items + // No changes needed, timeline start item is already added by the SDK + return items } - private fun processForDM(items: List, roomCreator: UserId?): List { + private fun processForDM(items: List, roomCreator: UserId?, hasMoreToLoadBackwards: Boolean): List { + val roomBeginningItemIndex = if (!hasMoreToLoadBackwards) { + items.indexOfFirst { it is MatrixTimelineItem.Virtual && it.virtual is VirtualTimelineItem.RoomBeginning }.takeIf { it >= 0 } + } else { + null + } + // Find room creation event. // This is usually the first MatrixTimelineItem.Event (so index 1, index 0 is a date) val roomCreationEventIndex = items.indexOfFirst { val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent stateEventContent?.content is OtherState.RoomCreate - } + }.takeIf { it >= 0 } // If the parameter roomCreator is null, the creator is the sender of the RoomCreate Event. - val roomCreatorUserId = roomCreator ?: (items.getOrNull(roomCreationEventIndex) as? MatrixTimelineItem.Event)?.event?.sender + val roomCreatorUserId = roomCreator ?: roomCreationEventIndex?.let { + (items.getOrNull(it) as? MatrixTimelineItem.Event)?.event?.sender + } // Find self-join event for the room creator. // This is usually the second MatrixTimelineItem.Event (so index 2) 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 + }.takeIf { it >= 0 } + } - if (roomCreationEventIndex == -1 && selfUserJoinedEventIndex == -1) { + val indicesToRemove = listOfNotNull( + roomBeginningItemIndex, + roomCreationEventIndex, + selfUserJoinedEventIndex, + ) + if (indicesToRemove.isEmpty()) { + // Nothing to do, return the list as is return items } + // 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) + indicesToRemove.sortedDescending().forEach { index -> + newItems.removeAt(index) } return newItems } - - @VisibleForTesting - fun createRoomBeginningItem(): MatrixTimelineItem.Virtual { - return MatrixTimelineItem.Virtual( - uniqueId = UniqueId("RoomBeginning"), - virtual = VirtualTimelineItem.RoomBeginning - ) - } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt index 60ee29dc57..07ee1dc1b9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt @@ -51,7 +51,6 @@ fun TracingConfiguration.map(): org.matrix.rustcomponents.sdk.TracingConfigurati writeToStdoutOrSystem = writesToLogcat, logLevel = logLevel.toRustLogLevel(), extraTargets = extraTargets, - // WARNING: this should be used only to debug issues, changes to this value should *never* be published - traceLogPacks = emptyList(), + traceLogPacks = traceLogPacks.map(), writeToFiles = writesToFilesConfiguration.toTracingFileConfiguration(), ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt new file mode 100644 index 0000000000..11187a90a3 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.tracing + +import io.element.android.libraries.matrix.api.tracing.TraceLogPack +import org.matrix.rustcomponents.sdk.TraceLogPacks as RustTraceLogPack + +fun TraceLogPack.map(): RustTraceLogPack = when (this) { + TraceLogPack.SEND_QUEUE -> RustTraceLogPack.SEND_QUEUE + TraceLogPack.EVENT_CACHE -> RustTraceLogPack.EVENT_CACHE + TraceLogPack.TIMELINE -> RustTraceLogPack.TIMELINE +} + +fun Collection.map(): List { + return map { it.map() } +} 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 b761dd7e3b..7f179ad2f7 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 @@ -29,7 +29,7 @@ class DefaultCallWidgetSettingsProvider @Inject constructor( private val analyticsService: AnalyticsService, ) : CallWidgetSettingsProvider { override suspend fun provide(baseUrl: String, widgetId: String, encrypted: Boolean): MatrixWidgetSettings { - val isAnalyticsEnabled = analyticsService.getUserConsent().first() + val isAnalyticsEnabled = analyticsService.userConsentFlow.first() val options = VirtualElementCallWidgetOptions( elementCallUrl = baseUrl, widgetId = widgetId, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt index 887155e5d7..c051aebbac 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt @@ -31,7 +31,7 @@ class RustMatrixClientFactoryTest { val sut = createRustMatrixClientFactory() val result = sut.create(aSessionData()) assertThat(result.sessionId).isEqualTo(SessionId("@alice:server.org")) - result.close() + result.destroy() } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt index 33f943cf53..f9a1afb656 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt @@ -28,9 +28,10 @@ import java.io.File class RustMatrixClientTest { @Test fun `ensure that sessionId and deviceId can be retrieved from the client`() = runTest { - createRustMatrixClient().use { sut -> - assertThat(sut.sessionId).isEqualTo(A_USER_ID) - assertThat(sut.deviceId).isEqualTo(A_DEVICE_ID) + createRustMatrixClient().run { + assertThat(sessionId).isEqualTo(A_USER_ID) + assertThat(deviceId).isEqualTo(A_DEVICE_ID) + destroy() } } @@ -38,16 +39,16 @@ class RustMatrixClientTest { fun `clear cache invokes the method clearCaches from the client and close it`() = runTest { val clearCachesResult = lambdaRecorder { } val closeResult = lambdaRecorder { } - createRustMatrixClient( + val client = createRustMatrixClient( client = FakeRustClient( clearCachesResult = clearCachesResult, closeResult = closeResult, ) - ).use { sut -> - sut.clearCache() - clearCachesResult.assertions().isCalledOnce() - closeResult.assertions().isCalledOnce() - } + ) + client.clearCache() + clearCachesResult.assertions().isCalledOnce() + closeResult.assertions().isCalledOnce() + client.destroy() } private fun TestScope.createRustMatrixClient( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt index 0164cfeb20..01321fcdf6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt @@ -10,12 +10,11 @@ package io.element.android.libraries.matrix.impl.auth import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.auth.OidcConfig import org.junit.Test -import java.io.File class OidcConfigurationProviderTest { @Test fun get() { - val result = OidcConfigurationProvider(File("/base")).get() + val result = OidcConfigurationProvider().get() assertThat(result.redirectUri).isEqualTo(OidcConfig.REDIRECT_URI) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt index 61df8e6455..e2a9d883ee 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt @@ -48,7 +48,7 @@ class RustMatrixAuthenticationServiceTest { sessionStore = sessionStore, rustMatrixClientFactory = rustMatrixClientFactory, passphraseGenerator = FakePassphraseGenerator(), - oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory), + oidcConfigurationProvider = OidcConfigurationProvider(), ) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt index 21c62cf3eb..8852734a71 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClientBuilder.kt @@ -30,7 +30,7 @@ class FakeRustClientBuilder : ClientBuilder(NoPointer) { override fun roomDecryptionTrustRequirement(trustRequirement: TrustRequirement) = this override fun disableSslVerification() = this override fun homeserverUrl(url: String) = this - override fun passphrase(passphrase: String?) = this + override fun sessionPassphrase(passphrase: String?) = this override fun proxy(url: String) = this override fun requestConfig(config: RequestConfig) = this override fun roomKeyRecipientStrategy(strategy: CollectStrategy) = this diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt index 00e93e0e66..34cb1ac9e1 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt @@ -19,6 +19,10 @@ 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 +internal val timelineStartEvent = MatrixTimelineItem.Virtual( + uniqueId = UniqueId("timeline_start"), + virtual = VirtualTimelineItem.RoomBeginning, +) internal val roomCreateEvent = MatrixTimelineItem.Event( uniqueId = UniqueId("m.room.create"), event = anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate)) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt index 5d72a68a68..bb1e4581a1 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt @@ -50,8 +50,9 @@ class RoomBeginningPostProcessorTest { } @Test - fun `processor removes room creation event and self-join event from DM timeline`() { + fun `processor removes timeline start, room creation event and self-join event from DM timeline`() { val timelineItems = listOf( + timelineStartEvent, roomCreateEvent, roomCreatorJoinEvent, ) @@ -98,43 +99,6 @@ class RoomBeginningPostProcessorTest { assertThat(processedItems).isEqualTo(expected) } - @Test - fun `processor will add beginning of room item if it's not a DM`() { - val timelineItems = listOf( - roomCreateEvent, - roomCreatorJoinEvent, - ) - val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) - val processedItems = processor.process(timelineItems, isDm = false, roomCreator = A_USER_ID, hasMoreToLoadBackwards = false) - assertThat(processedItems).isEqualTo( - listOf(processor.createRoomBeginningItem()) + timelineItems - ) - } - - @Test - fun `processor will not add beginning of room item if it's not a DM but the room has more to load`() { - val timelineItems = listOf( - roomCreateEvent, - roomCreatorJoinEvent, - ) - val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) - val processedItems = processor.process(timelineItems, isDm = false, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true) - assertThat(processedItems).isEqualTo(timelineItems) - } - - @Test - fun `processor will add beginning of room item if it's not a DM, when the parameter roomCreator is null`() { - val timelineItems = listOf( - roomCreateEvent, - roomCreatorJoinEvent, - ) - val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) - val processedItems = processor.process(timelineItems, isDm = false, roomCreator = null, hasMoreToLoadBackwards = false) - assertThat(processedItems).isEqualTo( - listOf(processor.createRoomBeginningItem()) + timelineItems - ) - } - @Test fun `processor removes items event it's not at the start of the timeline`() { val timelineItems = listOf( 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 8a77a60975..446a039bd9 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 @@ -181,8 +181,6 @@ class FakeMatrixClient( deactivateAccountResult(password, eraseData) } - override fun close() = Unit - override suspend fun getUserProfile(): Result = simulateLongTask { val result = getProfileResults[sessionId]?.getOrNull() ?: MatrixUser(sessionId, userDisplayName, userAvatarUrl) _userProfile.tryEmit(result) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 509fac833c..7f9e218697 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -63,6 +63,7 @@ const val A_MESSAGE = "Hello world!" const val A_REPLY = "OK, I'll be there!" const val ANOTHER_MESSAGE = "Hello universe!" const val A_CAPTION = "A media caption" +const val A_REASON = "A reason" const val A_REDACTION_REASON = "A redaction reason" diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt index f99ff0e355..85473d9367 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.test.notification +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -19,6 +20,7 @@ fun aNotificationData( content: NotificationContent = NotificationContent.MessageLike.RoomEncrypted, isDirect: Boolean = false, hasMention: Boolean = false, + threadId: ThreadId? = null, timestamp: Long = A_TIMESTAMP, senderDisplayName: String? = A_USER_NAME_2, senderIsNameAmbiguous: Boolean = false, @@ -26,6 +28,7 @@ fun aNotificationData( ): NotificationData { return NotificationData( eventId = AN_EVENT_ID, + threadId = threadId, roomId = A_ROOM_ID, senderAvatarUrl = null, senderDisplayName = senderDisplayName, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt index 487b898db3..c00ab8f113 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt @@ -12,13 +12,13 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.tests.testutils.lambda.lambdaError class FakePermalinkParser( - private var result: () -> PermalinkData = { lambdaError() } + private var result: (String) -> PermalinkData = { lambdaError() } ) : PermalinkParser { fun givenResult(result: PermalinkData) { this.result = { result } } override fun parse(uriString: String): PermalinkData { - return result() + return result(uriString) } } 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 7d9bbd1bfc..b7d226d499 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 @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility @@ -87,16 +88,16 @@ class FakeMatrixRoom( private val canRedactOtherResult: (UserId) -> Result = { lambdaError() }, private val canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, private val canUserSendMessageResult: (UserId, MessageEventType) -> Result = { _, _ -> lambdaError() }, - private val sendImageResult: (File, File?, ImageInfo, String?, String?, ProgressCallback?) -> Result = + private val sendImageResult: (File, File?, ImageInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result = + { _, _, _, _, _, _, _ -> lambdaError() }, + private val sendVideoResult: (File, File?, VideoInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result = + { _, _, _, _, _, _, _ -> lambdaError() }, + private val sendFileResult: (File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result = { _, _, _, _, _, _ -> lambdaError() }, - private val sendVideoResult: (File, File?, VideoInfo, String?, String?, ProgressCallback?) -> Result = + private val sendAudioResult: (File, AudioInfo, String?, String?, ProgressCallback?, ReplyParameters?) -> Result = { _, _, _, _, _, _ -> lambdaError() }, - private val sendFileResult: (File, FileInfo, String?, String?, ProgressCallback?) -> Result = + private val sendVoiceMessageResult: (File, AudioInfo, List, ProgressCallback?, ReplyParameters?) -> Result = { _, _, _, _, _ -> lambdaError() }, - private val sendAudioResult: (File, AudioInfo, String?, String?, ProgressCallback?) -> Result = - { _, _, _, _, _ -> lambdaError() }, - private val sendVoiceMessageResult: (File, AudioInfo, List, ProgressCallback?) -> Result = - { _, _, _, _ -> lambdaError() }, private val setNameResult: (String) -> Result = { lambdaError() }, private val setTopicResult: (String) -> Result = { lambdaError() }, private val updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() }, @@ -332,7 +333,8 @@ class FakeMatrixRoom( imageInfo: ImageInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = simulateLongTask { simulateSendMediaProgress(progressCallback) sendImageResult( @@ -342,6 +344,7 @@ class FakeMatrixRoom( caption, formattedCaption, progressCallback, + replyParameters, ) } @@ -351,7 +354,8 @@ class FakeMatrixRoom( videoInfo: VideoInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = simulateLongTask { simulateSendMediaProgress(progressCallback) sendVideoResult( @@ -361,6 +365,7 @@ class FakeMatrixRoom( caption, formattedCaption, progressCallback, + replyParameters, ) } @@ -369,7 +374,8 @@ class FakeMatrixRoom( audioInfo: AudioInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = simulateLongTask { simulateSendMediaProgress(progressCallback) sendAudioResult( @@ -378,6 +384,7 @@ class FakeMatrixRoom( caption, formattedCaption, progressCallback, + replyParameters, ) } @@ -386,7 +393,8 @@ class FakeMatrixRoom( fileInfo: FileInfo, caption: String?, formattedCaption: String?, - progressCallback: ProgressCallback? + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = simulateLongTask { simulateSendMediaProgress(progressCallback) sendFileResult( @@ -395,6 +403,40 @@ class FakeMatrixRoom( caption, formattedCaption, progressCallback, + replyParameters, + ) + } + + override suspend fun sendVoiceMessage( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ): Result = simulateLongTask { + simulateSendMediaProgress(progressCallback) + sendVoiceMessageResult( + file, + audioInfo, + waveform, + progressCallback, + replyParameters, + ) + } + + override suspend fun sendLocation( + body: String, + geoUri: String, + description: String?, + zoomLevel: Int?, + assetType: AssetType?, + ): Result = simulateLongTask { + return sendLocationResult( + body, + geoUri, + description, + zoomLevel, + assetType, ) } @@ -464,22 +506,6 @@ class FakeMatrixRoom( return Result.success(Unit) } - override suspend fun sendLocation( - body: String, - geoUri: String, - description: String?, - zoomLevel: Int?, - assetType: AssetType?, - ): Result = simulateLongTask { - return sendLocationResult( - body, - geoUri, - description, - zoomLevel, - assetType, - ) - } - override suspend fun createPoll( question: String, answers: List, @@ -524,21 +550,6 @@ class FakeMatrixRoom( return endPollResult(pollStartId, text) } - override suspend fun sendVoiceMessage( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback? - ): Result = simulateLongTask { - simulateSendMediaProgress(progressCallback) - sendVoiceMessageResult( - file, - audioInfo, - waveform, - progressCallback, - ) - } - override suspend fun typingNotice(isTyping: Boolean): Result { return typingNoticeResult(isTyping) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index 9019eea646..5469e5b4aa 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.test.sync +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import kotlinx.coroutines.flow.MutableStateFlow @@ -29,6 +30,8 @@ class FakeSyncService( override val syncState: StateFlow = syncStateFlow + override val isOnline: StateFlow = syncState.mapState { it != SyncState.Offline } + suspend fun emitSyncState(syncState: SyncState) { syncStateFlow.emit(syncState) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt index 1c335057b5..859f81b5c3 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.message.ReplyParameters 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.Timeline @@ -28,19 +29,18 @@ import io.element.android.tests.testutils.lambda.lambdaError import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import java.io.File class FakeTimeline( private val name: String = "FakeTimeline", override val timelineItems: Flow> = MutableStateFlow(emptyList()), - private val backwardPaginationStatus: MutableStateFlow = MutableStateFlow( + override val backwardPaginationStatus: MutableStateFlow = MutableStateFlow( Timeline.PaginationStatus( isPaginating = false, hasMoreToLoad = true ) ), - private val forwardPaginationStatus: MutableStateFlow = MutableStateFlow( + override val forwardPaginationStatus: MutableStateFlow = MutableStateFlow( Timeline.PaginationStatus( isPaginating = false, hasMoreToLoad = false @@ -111,7 +111,7 @@ class FakeTimeline( ) var replyMessageLambda: ( - eventId: EventId, + replyParameters: ReplyParameters, body: String, htmlBody: String?, intentionalMentions: List, @@ -121,13 +121,13 @@ class FakeTimeline( } override suspend fun replyMessage( - eventId: EventId, + replyParameters: ReplyParameters, body: String, htmlBody: String?, intentionalMentions: List, fromNotification: Boolean, ): Result = replyMessageLambda( - eventId, + replyParameters, body, htmlBody, intentionalMentions, @@ -141,7 +141,8 @@ class FakeTimeline( body: String?, formattedBody: String?, progressCallback: ProgressCallback?, - ) -> Result = { _, _, _, _, _, _ -> + replyParameters: ReplyParameters?, + ) -> Result = { _, _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -152,13 +153,15 @@ class FakeTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = sendImageLambda( file, thumbnailFile, imageInfo, caption, formattedCaption, - progressCallback + progressCallback, + replyParameters, ) var sendVideoLambda: ( @@ -168,7 +171,8 @@ class FakeTimeline( body: String?, formattedBody: String?, progressCallback: ProgressCallback?, - ) -> Result = { _, _, _, _, _, _ -> + replyParameters: ReplyParameters?, + ) -> Result = { _, _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -179,13 +183,15 @@ class FakeTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = sendVideoLambda( file, thumbnailFile, videoInfo, caption, formattedCaption, - progressCallback + progressCallback, + replyParameters, ) var sendAudioLambda: ( @@ -194,7 +200,8 @@ class FakeTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, - ) -> Result = { _, _, _, _, _ -> + replyParameters: ReplyParameters?, + ) -> Result = { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -204,12 +211,14 @@ class FakeTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = sendAudioLambda( file, audioInfo, caption, formattedCaption, - progressCallback + progressCallback, + replyParameters, ) var sendFileLambda: ( @@ -218,7 +227,8 @@ class FakeTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, - ) -> Result = { _, _, _, _, _ -> + replyParameters: ReplyParameters?, + ) -> Result = { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -228,22 +238,39 @@ class FakeTimeline( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result = sendFileLambda( file, fileInfo, caption, formattedCaption, - progressCallback + progressCallback, + replyParameters, ) - var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result = { _, _ -> Result.success(Unit) } - override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result = toggleReactionLambda( - emoji, - eventOrTransactionId - ) + var sendVoiceMessageLambda: ( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ) -> Result = { _, _, _, _, _ -> + Result.success(FakeMediaUploadHandler()) + } - var forwardEventLambda: (eventId: EventId, roomIds: List) -> Result = { _, _ -> Result.success(Unit) } - override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = forwardEventLambda(eventId, roomIds) + override suspend fun sendVoiceMessage( + file: File, + audioInfo: AudioInfo, + waveform: List, + progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, + ): Result = sendVoiceMessageLambda( + file, + audioInfo, + waveform, + progressCallback, + replyParameters, + ) var sendLocationLambda: ( body: String, @@ -269,6 +296,17 @@ class FakeTimeline( assetType ) + var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result = { _, _ -> Result.success(Unit) } + + override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result = toggleReactionLambda( + emoji, + eventOrTransactionId + ) + + var forwardEventLambda: (eventId: EventId, roomIds: List) -> Result = { _, _ -> Result.success(Unit) } + + override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = forwardEventLambda(eventId, roomIds) + var createPollLambda: ( question: String, answers: List, @@ -338,27 +376,6 @@ class FakeTimeline( text: String, ): Result = endPollLambda(pollStartId, text) - var sendVoiceMessageLambda: ( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback?, - ) -> Result = { _, _, _, _ -> - Result.success(FakeMediaUploadHandler()) - } - - override suspend fun sendVoiceMessage( - file: File, - audioInfo: AudioInfo, - waveform: List, - progressCallback: ProgressCallback?, - ): Result = sendVoiceMessageLambda( - file, - audioInfo, - waveform, - progressCallback - ) - var sendReadReceiptLambda: ( eventId: EventId, receiptType: ReceiptType, @@ -377,13 +394,6 @@ class FakeTimeline( override suspend fun paginate(direction: Timeline.PaginationDirection): Result = paginateLambda(direction) - override fun paginationStatus(direction: Timeline.PaginationDirection): StateFlow { - return when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus - Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus - } - } - var loadReplyDetailsLambda: (eventId: EventId) -> InReplyTo = { InReplyTo.NotLoaded(it) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt index 008438305d..452f6ed5a2 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt @@ -34,8 +34,8 @@ fun InviteSenderView( modifier = modifier, ) { Box(modifier = Modifier.padding(vertical = 2.dp)) { - Avatar(avatarData = inviteSender.avatarData) - } + Avatar(avatarData = inviteSender.avatarData) + } Text( text = inviteSender.annotatedString(), style = ElementTheme.typography.fontBodyMdRegular, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt index c71e5679cb..0c02a24a0e 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt @@ -7,32 +7,26 @@ package io.element.android.libraries.matrix.ui.messages -import androidx.compose.runtime.staticCompositionLocalOf import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.runningFold import javax.inject.Inject @SingleIn(RoomScope::class) class RoomMemberProfilesCache @Inject constructor() { private val cache = MutableStateFlow(mapOf()) + val updateFlow = cache.drop(1).runningFold(0) { acc, _ -> acc + 1 } - private val _lastCacheUpdate = MutableStateFlow(0L) - val lastCacheUpdate: StateFlow = _lastCacheUpdate - - fun replace(items: List) { + suspend fun replace(items: List) = coroutineScope { cache.value = items.associateBy { it.userId } - _lastCacheUpdate.tryEmit(_lastCacheUpdate.value + 1) } fun getDisplayName(userId: UserId): String? { return cache.value[userId]?.disambiguatedDisplayName } } - -val LocalRoomMemberProfilesCache = staticCompositionLocalOf { - RoomMemberProfilesCache() -} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt new file mode 100644 index 0000000000..598b7c28da --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.messages + +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.runningFold +import javax.inject.Inject + +@SingleIn(RoomScope::class) +class RoomNamesCache @Inject constructor() { + private val cache = MutableStateFlow(mapOf()) + val updateFlow = cache.drop(1).runningFold(0) { acc, _ -> acc + 1 } + + suspend fun replace(items: List) = coroutineScope { + val roomNamesByRoomIdOrAlias = LinkedHashMap(items.size * 2) + items + .forEach { summary -> + roomNamesByRoomIdOrAlias[summary.info.id.toRoomIdOrAlias()] = summary.info.name + val canonicalAlias = summary.info.canonicalAlias + if (canonicalAlias != null) { + roomNamesByRoomIdOrAlias[canonicalAlias.toRoomIdOrAlias()] = summary.info.name + } + } + cache.value = roomNamesByRoomIdOrAlias + } + + fun getDisplayName(roomIdOrAlias: RoomIdOrAlias): String? { + return cache.value[roomIdOrAlias] + } +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt index 3fb4ddd74a..d3135d9fd8 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.core.extensions.toSafeLength import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview @@ -152,8 +153,10 @@ private fun ReplyToContentText(metadata: InReplyToMetadata?) { val text = when (metadata) { InReplyToMetadata.Redacted -> stringResource(id = CommonStrings.common_message_removed) InReplyToMetadata.UnableToDecrypt -> stringResource(id = CommonStrings.common_waiting_for_decryption_key) - is InReplyToMetadata.Text -> metadata.text - is InReplyToMetadata.Thumbnail -> metadata.text + // Add a limit to the text length to avoid a crash in Compose + is InReplyToMetadata.Text -> metadata.text.toSafeLength() + // Add a limit to the text length to avoid a crash in Compose + is InReplyToMetadata.Thumbnail -> metadata.text.toSafeLength() null -> "" } val iconResourceId = when (metadata) { diff --git a/libraries/matrixui/src/main/res/values-eu/translations.xml b/libraries/matrixui/src/main/res/values-eu/translations.xml index 15f4977395..3bea664f97 100644 --- a/libraries/matrixui/src/main/res/values-eu/translations.xml +++ b/libraries/matrixui/src/main/res/values-eu/translations.xml @@ -1,4 +1,6 @@ + "Bidali gonbidapena" + "Gonbidapena bidali?" "%1$s(e)k (%2$s) gonbidatu zaitu" diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index 584539eacc..b68f077df9 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -12,6 +12,7 @@ import io.element.android.libraries.core.extensions.flatMapCatching import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job @@ -46,12 +47,14 @@ class MediaSender @Inject constructor( caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, + replyParameters: ReplyParameters?, ): Result { return room.sendMedia( uploadInfo = mediaUploadInfo, progressCallback = progressCallback, caption = caption, - formattedCaption = formattedCaption + formattedCaption = formattedCaption, + replyParameters = replyParameters, ) .handleSendResult() } @@ -61,7 +64,8 @@ class MediaSender @Inject constructor( mimeType: String, caption: String? = null, formattedCaption: String? = null, - progressCallback: ProgressCallback? = null + progressCallback: ProgressCallback? = null, + replyParameters: ReplyParameters? = null, ): Result { val compressIfPossible = sessionPreferencesStore.doesCompressMedia().first() return preProcessor @@ -76,7 +80,8 @@ class MediaSender @Inject constructor( uploadInfo = info, progressCallback = progressCallback, caption = caption, - formattedCaption = formattedCaption + formattedCaption = formattedCaption, + replyParameters = replyParameters, ) } .handleSendResult() @@ -86,7 +91,8 @@ class MediaSender @Inject constructor( uri: Uri, mimeType: String, waveForm: List, - progressCallback: ProgressCallback? = null + progressCallback: ProgressCallback? = null, + replyParameters: ReplyParameters? = null, ): Result { return preProcessor .process( @@ -106,7 +112,8 @@ class MediaSender @Inject constructor( uploadInfo = newInfo, progressCallback = progressCallback, caption = null, - formattedCaption = null + formattedCaption = null, + replyParameters = replyParameters, ) } .handleSendResult() @@ -128,6 +135,7 @@ class MediaSender @Inject constructor( progressCallback: ProgressCallback?, caption: String?, formattedCaption: String?, + replyParameters: ReplyParameters?, ): Result { val handler = when (uploadInfo) { is MediaUploadInfo.Image -> { @@ -137,7 +145,8 @@ class MediaSender @Inject constructor( imageInfo = uploadInfo.imageInfo, caption = caption, formattedCaption = formattedCaption, - progressCallback = progressCallback + progressCallback = progressCallback, + replyParameters = replyParameters, ) } is MediaUploadInfo.Video -> { @@ -147,7 +156,8 @@ class MediaSender @Inject constructor( videoInfo = uploadInfo.videoInfo, caption = caption, formattedCaption = formattedCaption, - progressCallback = progressCallback + progressCallback = progressCallback, + replyParameters = replyParameters, ) } is MediaUploadInfo.Audio -> { @@ -156,7 +166,8 @@ class MediaSender @Inject constructor( audioInfo = uploadInfo.audioInfo, caption = caption, formattedCaption = formattedCaption, - progressCallback = progressCallback + progressCallback = progressCallback, + replyParameters = replyParameters, ) } is MediaUploadInfo.VoiceMessage -> { @@ -164,7 +175,8 @@ class MediaSender @Inject constructor( file = uploadInfo.file, audioInfo = uploadInfo.audioInfo, waveform = uploadInfo.waveform, - progressCallback = progressCallback + progressCallback = progressCallback, + replyParameters = replyParameters, ) } is MediaUploadInfo.AnyFile -> { @@ -173,7 +185,8 @@ class MediaSender @Inject constructor( fileInfo = uploadInfo.fileInfo, caption = caption, formattedCaption = formattedCaption, - progressCallback = progressCallback + progressCallback = progressCallback, + replyParameters = replyParameters, ) } } diff --git a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt index 48640da8ea..bd5795af51 100644 --- a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt +++ b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.message.ReplyParameters import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor @@ -46,7 +47,7 @@ class MediaSenderTest { @Test fun `given an attachment when sending it the MatrixRoom will call sendMedia`() = runTest { val sendImageResult = - lambdaRecorder> { _, _, _, _, _, _ -> + lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( @@ -74,8 +75,8 @@ class MediaSenderTest { @Test fun `given a failure in the media upload when sending the whole process fails`() = runTest { val sendImageResult = - lambdaRecorder> { _, _, _, _, _, _ -> - Result.failure(Exception()) + lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? -> + Result.failure(Exception()) } val room = FakeMatrixRoom( sendImageResult = sendImageResult @@ -91,7 +92,8 @@ class MediaSenderTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `given a cancellation in the media upload when sending the job is cancelled`() = runTest(StandardTestDispatcher()) { - val sendFileResult = lambdaRecorder> { _, _, _, _, _ -> + val sendFileResult = + lambdaRecorder> { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt index 4ad36d8827..1dc81a11e3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn @@ -85,11 +86,13 @@ class TimelineMediaGalleryDataSource @Inject constructor( } }.flatMapLatest { timelineMediaItemsFactory.timelineItems - }.map { timelineItems -> - mediaItemsPostProcessor.process(mediaItems = timelineItems) - }.map { - mediaTimeline.orCache(it) - }.onEach { groupedMediaItems -> + } + .distinctUntilChanged() + .map { timelineItems -> + val groupedItems = mediaItemsPostProcessor.process(mediaItems = timelineItems) + mediaTimeline.orCache(groupedItems) + } + .onEach { groupedMediaItems -> groupedMediaItemsFlow.emit(AsyncData.Success(groupedMediaItems)) } .onCompletion { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt index 8750647d97..56af3ca2c8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.mediaviewer.impl.local +import android.Manifest import android.app.Activity import android.content.ContentResolver import android.content.ContentValues @@ -26,6 +27,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.core.content.FileProvider +import androidx.core.content.PermissionChecker import androidx.core.net.toFile import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent @@ -119,7 +121,14 @@ class AndroidLocalMediaActions @Inject constructor( when (localMedia.info.mimeType) { MimeTypes.Apk -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (activityContext?.packageManager?.canRequestPackageInstalls() == false) { + if (PermissionChecker.checkPermission( + context, + Manifest.permission.REQUEST_INSTALL_PACKAGES, + -1, + -1, + context.packageName + ) == PermissionChecker.PERMISSION_GRANTED && + activityContext?.packageManager?.canRequestPackageInstalls() == false) { pendingMedia = localMedia activityContext?.startInstallFromSourceIntent(apkInstallLauncher!!).let { } } else { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 10ac3a7c2a..bec02c7fd8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -31,6 +31,7 @@ fun LocalMediaView( textFileViewer: TextFileViewer, modifier: Modifier = Modifier, isDisplayed: Boolean = true, + isUserSelected: Boolean = false, localMediaViewState: LocalMediaViewState = rememberLocalMediaViewState(), mediaInfo: MediaInfo? = localMedia?.info, ) { @@ -47,6 +48,7 @@ fun LocalMediaView( localMediaViewState = localMediaViewState, bottomPaddingInPixels = bottomPaddingInPixels, localMedia = localMedia, + autoplay = isUserSelected, modifier = modifier, ) mimeType == MimeTypes.PlainText -> TextFileView( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 684b96cb9f..028a25999f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -111,6 +111,7 @@ private fun ExoPlayerMediaAudioView( MediaPlayerControllerState( isVisible = true, isPlaying = false, + isReady = false, progressInMillis = 0, durationInMillis = 0, canMute = false, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt index b89f6a324f..a098a53174 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt @@ -187,10 +187,6 @@ class ExoPlayerForPreview( override fun setDeviceMuted(muted: Boolean) {} override fun setDeviceMuted(muted: Boolean, flags: Int) {} override fun setAudioAttributes(audioAttributes: AudioAttributes, handleAudioFocus: Boolean) {} - override fun getAudioComponent(): ExoPlayer.AudioComponent? = throw NotImplementedError() - override fun getVideoComponent(): ExoPlayer.VideoComponent? = throw NotImplementedError() - override fun getTextComponent(): ExoPlayer.TextComponent? = throw NotImplementedError() - override fun getDeviceComponent(): ExoPlayer.DeviceComponent? = throw NotImplementedError() override fun addAudioOffloadListener(listener: ExoPlayer.AudioOffloadListener) {} override fun removeAudioOffloadListener(listener: ExoPlayer.AudioOffloadListener) {} override fun getAnalyticsCollector(): AnalyticsCollector = throw NotImplementedError() diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt index 349044439e..6160b6758c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt @@ -12,6 +12,7 @@ import androidx.annotation.FloatRange data class MediaPlayerControllerState( val isVisible: Boolean, val isPlaying: Boolean, + val isReady: Boolean, val progressInMillis: Long, val durationInMillis: Long, val canMute: Boolean, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt index 88e4c2f7c7..2ce66a79e3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt @@ -27,6 +27,7 @@ open class MediaPlayerControllerStateProvider : PreviewParameterProvider> { + internal fun dataFlow(): Flow> { return galleryDataSource.groupedMediaItemsFlow() .map { groupedItems -> when (groupedItems) { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index f897984578..858e3e7916 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -148,6 +148,7 @@ class MediaViewerPresenter @AssistedInject constructor( } return MediaViewerState( + initiallySelectedEventId = inputs.eventId, listData = data.value, currentIndex = currentIndex.intValue, snackbarMessage = snackbarMessage, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt index 32c22b0470..5a0c4c2307 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta import kotlinx.collections.immutable.ImmutableList data class MediaViewerState( + val initiallySelectedEventId: EventId?, val listData: ImmutableList, val currentIndex: Int, val snackbarMessage: SnackbarMessage?, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 6686cd9fce..324b093325 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaviewer.api.MediaInfo @@ -202,6 +203,7 @@ fun aMediaViewerState( mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden, eventSink: (MediaViewerEvents) -> Unit = {}, ) = MediaViewerState( + initiallySelectedEventId = EventId("\$a:b"), listData = listData.toPersistentList(), currentIndex = currentIndex, snackbarMessage = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 501c434674..a55846106b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -142,8 +142,13 @@ fun MediaViewerView( Box( modifier = Modifier.fillMaxSize() ) { + val isDisplayed = remember(pagerState.settledPage) { + // This 'item provider' lambda will be called when the data source changes with an outdated `settlePage` value + // So we need to update this value only when the `settledPage` value changes. It seems like a bug that needs to be fixed in Compose. + page == pagerState.settledPage + } MediaViewerPage( - isDisplayed = page == pagerState.settledPage, + isDisplayed = isDisplayed, showOverlay = showOverlay, bottomPaddingInPixels = bottomPaddingInPixels, data = dataForPage, @@ -157,7 +162,8 @@ fun MediaViewerView( }, onShowOverlayChange = { showOverlay = it - } + }, + isUserSelected = (state.listData[page] as? MediaViewerPageData.MediaViewerData)?.eventId == state.initiallySelectedEventId, ) // Bottom bar AnimatedVisibility(visible = showOverlay, enter = fadeIn(), exit = fadeOut()) { @@ -273,6 +279,7 @@ private fun MediaViewerPage( bottomPaddingInPixels: Int, data: MediaViewerPageData.MediaViewerData, textFileViewer: TextFileViewer, + isUserSelected: Boolean, onDismiss: () -> Unit, onRetry: () -> Unit, onDismissError: () -> Unit, @@ -328,6 +335,7 @@ private fun MediaViewerPage( currentOnShowOverlayChange(!currentShowOverlay) } }, + isUserSelected = isUserSelected, ) ThumbnailView( mediaInfo = data.mediaInfo, diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt index 3e5e3b7407..e9072e3666 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -56,13 +57,13 @@ class DefaultPermissionsPresenter @AssistedInject constructor( // To reset the store: ResetStore() - val isAlreadyDenied: Boolean by permissionsStore - .isPermissionDenied(permission) - .collectAsState(initial = false) + val isAlreadyDenied: Boolean by remember { + permissionsStore.isPermissionDenied(permission) + }.collectAsState(initial = false) - val isAlreadyAsked: Boolean by permissionsStore - .isPermissionAsked(permission) - .collectAsState(initial = false) + val isAlreadyAsked: Boolean by remember { + permissionsStore.isPermissionAsked(permission) + }.collectAsState(initial = false) var permissionState: PermissionState? = null diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt index 537c8d6040..c072229626 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.preferences.api.store import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import kotlinx.coroutines.flow.Flow interface AppPreferencesStore { @@ -26,5 +27,8 @@ interface AppPreferencesStore { suspend fun setTracingLogLevel(logLevel: LogLevel) fun getTracingLogLevelFlow(): Flow + suspend fun setTracingLogPacks(targets: Set) + fun getTracingLogPacksFlow(): Flow> + suspend fun reset() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt index 06f4b05912..a05e9c48da 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -32,6 +33,7 @@ private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseU private val themeKey = stringPreferencesKey("theme") private val hideImagesAndVideosKey = booleanPreferencesKey("hideImagesAndVideos") private val logLevelKey = stringPreferencesKey("logLevel") +private val traceLogPacksKey = stringPreferencesKey("traceLogPacks") @ContributesBinding(AppScope::class) class DefaultAppPreferencesStore @Inject constructor( @@ -105,6 +107,23 @@ class DefaultAppPreferencesStore @Inject constructor( } } + override suspend fun setTracingLogPacks(targets: Set) { + val value = targets.joinToString(",") { it.key } + store.edit { prefs -> + prefs[traceLogPacksKey] = value + } + } + + override fun getTracingLogPacksFlow(): Flow> { + return store.data.map { prefs -> + prefs[traceLogPacksKey] + ?.split(",") + ?.mapNotNull { value -> TraceLogPack.entries.find { it.key == value } } + ?.toSet() + ?: emptySet() + } + } + override suspend fun reset() { store.edit { it.clear() } } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt index 1a332b42aa..ab3913cd08 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.preferences.test import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -17,15 +18,15 @@ class InMemoryAppPreferencesStore( hideImagesAndVideos: Boolean = false, customElementCallBaseUrl: String? = null, theme: String? = null, - simplifiedSlidingSyncEnabled: Boolean = false, logLevel: LogLevel = LogLevel.INFO, + traceLockPacks: Set = emptySet(), ) : AppPreferencesStore { private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) private val hideImagesAndVideos = MutableStateFlow(hideImagesAndVideos) private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl) private val theme = MutableStateFlow(theme) - private val simplifiedSlidingSyncEnabled = MutableStateFlow(simplifiedSlidingSyncEnabled) private val logLevel = MutableStateFlow(logLevel) + private val tracingLogPacks = MutableStateFlow(traceLockPacks) override suspend fun setDeveloperModeEnabled(enabled: Boolean) { isDeveloperModeEnabled.value = enabled @@ -67,6 +68,14 @@ class InMemoryAppPreferencesStore( return logLevel } + override suspend fun setTracingLogPacks(targets: Set) { + tracingLogPacks.value = targets + } + + override fun getTracingLogPacksFlow(): Flow> { + return tracingLogPacks + } + override suspend fun reset() { // No op } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index c8e8ba4431..b84a13b4f2 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -102,6 +102,7 @@ class DefaultNotifiableEventResolver @Inject constructor( senderId = content.senderId, roomId = roomId, eventId = eventId, + threadId = threadId, noisy = isNoisy, timestamp = this.timestamp, senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index aea52c8b78..90ed3a3d05 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -14,9 +14,9 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.message.replyInThread import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.push.api.notifications.NotificationCleaner @@ -54,7 +54,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( Timber.tag(loggerTag.value).d("onReceive: ${intent.action} ${intent.data} for: ${roomId?.value}/${eventId?.value}") when (intent.action) { actionIds.smartReply -> if (roomId != null) { - handleSmartReply(sessionId, roomId, threadId, intent) + handleSmartReply(sessionId, roomId, eventId, threadId, intent) } actionIds.dismissRoom -> if (roomId != null) { notificationCleaner.clearMessagesForRoom(sessionId, roomId) @@ -106,6 +106,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( private fun handleSmartReply( sessionId: SessionId, roomId: RoomId, + replyToEventId: EventId?, threadId: ThreadId?, intent: Intent, ) = appCoroutineScope.launch { @@ -120,6 +121,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( sendMatrixEvent( sessionId = sessionId, roomId = roomId, + replyToEventId = replyToEventId, threadId = threadId, room = room, message = message, @@ -131,6 +133,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( sessionId: SessionId, roomId: RoomId, threadId: ThreadId?, + replyToEventId: EventId?, room: MatrixRoom, message: String, ) { @@ -158,13 +161,13 @@ class NotificationBroadcastReceiverHandler @Inject constructor( ) onNotifiableEventReceived.onNotifiableEventReceived(notifiableMessageEvent) - if (threadId != null) { + if (threadId != null && replyToEventId != null) { room.liveTimeline.replyMessage( - eventId = threadId.asEventId(), body = message, htmlBody = null, intentionalMentions = emptyList(), fromNotification = true, + replyParameters = replyInThread(replyToEventId), ) } else { room.liveTimeline.sendMessage( diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index f8cf2836b8..d462ebb5f6 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -197,7 +197,8 @@ class DefaultNotificationCreator @Inject constructor( addAction(markAsReadActionFactory.create(roomInfo)) // Quick reply if (!roomInfo.hasSmartReplyError) { - addAction(quickReplyActionFactory.create(roomInfo, threadId)) + val latestEventId = events.lastOrNull()?.eventId + addAction(quickReplyActionFactory.create(roomInfo, latestEventId, threadId)) } if (openIntent != null) { setContentIntent(openIntent) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt index f8efa9b0e7..6a590aeda8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt @@ -16,6 +16,7 @@ import androidx.core.app.RemoteInput import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId @@ -33,11 +34,11 @@ class QuickReplyActionFactory @Inject constructor( private val stringProvider: StringProvider, private val clock: SystemClock, ) { - fun create(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): NotificationCompat.Action? { + fun create(roomInfo: RoomEventGroupInfo, eventId: EventId?, threadId: ThreadId?): NotificationCompat.Action? { if (!NotificationConfig.SHOW_QUICK_REPLY_ACTION) return null val sessionId = roomInfo.sessionId val roomId = roomInfo.roomId - val replyPendingIntent = buildQuickReplyIntent(sessionId, roomId, threadId) + val replyPendingIntent = buildQuickReplyIntent(sessionId, roomId, eventId, threadId) val remoteInput = RemoteInput.Builder(NotificationBroadcastReceiver.KEY_TEXT_REPLY) .setLabel(stringProvider.getString(R.string.notification_room_action_quick_reply)) .build() @@ -63,6 +64,7 @@ class QuickReplyActionFactory @Inject constructor( private fun buildQuickReplyIntent( sessionId: SessionId, roomId: RoomId, + eventId: EventId?, threadId: ThreadId?, ): PendingIntent { val intent = Intent(context, NotificationBroadcastReceiver::class.java) @@ -70,9 +72,8 @@ class QuickReplyActionFactory @Inject constructor( intent.data = createIgnoredUri("quickReply/$sessionId/$roomId" + threadId?.let { "/$it" }.orEmpty()) intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId.value) intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId.value) - threadId?.let { - intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, it.value) - } + eventId?.let { intent.putExtra(NotificationBroadcastReceiver.KEY_EVENT_ID, it.value) } + threadId?.let { intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, it.value) } return PendingIntent.getBroadcast( context, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index 66742652bf..dd2214650e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -118,7 +118,7 @@ class DefaultPushHandler @Inject constructor( } } - private fun handleRingingCallEvent(notifiableEvent: NotifiableRingingCallEvent) { + private suspend fun handleRingingCallEvent(notifiableEvent: NotifiableRingingCallEvent) { Timber.i("## handleInternal() : Incoming call.") elementCallEntryPoint.handleIncomingCall( callType = CallType.RoomCall(notifiableEvent.sessionId, notifiableEvent.roomId), diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt index d75c4f4d59..5a30db6f59 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt @@ -38,10 +38,8 @@ class PushProvidersTest @Inject constructor( val result = sortedPushProvider.isNotEmpty() if (result) { delegate.updateState( - description = stringProvider.getQuantityString( - resId = R.plurals.troubleshoot_notifications_test_detect_push_provider_success, - quantity = sortedPushProvider.size, - sortedPushProvider.size, + description = stringProvider.getString( + resId = R.string.troubleshoot_notifications_test_detect_push_provider_success_2, sortedPushProvider.joinToString { it.name } ), status = NotificationTroubleshootTestState.Status.Success diff --git a/libraries/push/impl/src/main/res/values-el/translations.xml b/libraries/push/impl/src/main/res/values-el/translations.xml index ad88f0f3b3..c45f487d25 100644 --- a/libraries/push/impl/src/main/res/values-el/translations.xml +++ b/libraries/push/impl/src/main/res/values-el/translations.xml @@ -14,7 +14,7 @@ "%d ειδοποιήσεις" "Γνωστοποίηση" - "Εισερχόμενη κλήση" + "📹 Εισερχόμενη κλήση" "** Αποτυχία αποστολής - παρακαλώ άνοιξε το δωμάτιο" "Συμμετοχή" "Απόρριψη" diff --git a/libraries/push/impl/src/main/res/values-in/translations.xml b/libraries/push/impl/src/main/res/values-in/translations.xml index 4c12681fbe..204b719abf 100644 --- a/libraries/push/impl/src/main/res/values-in/translations.xml +++ b/libraries/push/impl/src/main/res/values-in/translations.xml @@ -12,7 +12,7 @@ "%d pemberitahuan" "Notifikasi" - "Panggilan masuk" + "📹 Panggilan masuk" "** Gagal mengirim — silakan buka ruangan" "Gabung" "Tolak" diff --git a/libraries/push/impl/src/main/res/values-nb/translations.xml b/libraries/push/impl/src/main/res/values-nb/translations.xml index 57b2aa3fe9..4187b4c2f8 100644 --- a/libraries/push/impl/src/main/res/values-nb/translations.xml +++ b/libraries/push/impl/src/main/res/values-nb/translations.xml @@ -57,6 +57,7 @@ "Gjeldende push-leverandør: %1$s." "Nåværende push-leverandør" "Ingen push-leverandører funnet." + "Oppdag push-leverandører" "Kontroller at programmet kan vise varsler." "Det er ikke klikket på varselet." "Kan ikke vise varselet." diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index 5c6c852f14..9860c8bef7 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -58,13 +58,14 @@ "No push providers selected." "Current push provider: %1$s." "Current push provider" - "Ensure that the application has at least one push provider." - "No push providers found." + "Ensure that the application supports at least one push provider." + "No push provider support found." "Found %1$d push provider: %2$s" "Found %1$d push providers: %2$s" - "Detect push providers" + "The application was built with support for: %1$s" + "Push provider support" "Check that the application can display notification." "The notification has not been clicked." "Cannot display the notification." diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt index 0ce6038d79..9405e19464 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt @@ -14,8 +14,9 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.room.IntentionalMention +import io.element.android.libraries.matrix.api.room.message.ReplyParameters +import io.element.android.libraries.matrix.api.room.message.replyInThread import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -330,7 +331,8 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test send reply`() = runTest { val sendMessage = lambdaRecorder, Result> { _, _, _ -> Result.success(Unit) } - val replyMessage = lambdaRecorder, Boolean, Result> { _, _, _, _, _ -> Result.success(Unit) } + val replyMessage = + lambdaRecorder, Boolean, Result> { _, _, _, _, _ -> Result.success(Unit) } val liveTimeline = FakeTimeline().apply { sendMessageLambda = sendMessage replyMessageLambda = replyMessage @@ -396,7 +398,8 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test send reply to thread`() = runTest { val sendMessage = lambdaRecorder, Result> { _, _, _ -> Result.success(Unit) } - val replyMessage = lambdaRecorder, Boolean, Result> { _, _, _, _, _ -> Result.success(Unit) } + val replyMessage = + lambdaRecorder, Boolean, Result> { _, _, _, _, _ -> Result.success(Unit) } val liveTimeline = FakeTimeline().apply { sendMessageLambda = sendMessage replyMessageLambda = replyMessage @@ -423,6 +426,7 @@ class NotificationBroadcastReceiverHandlerTest { createIntent( action = actionIds.smartReply, roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, threadId = A_THREAD_ID, ), ) @@ -433,7 +437,13 @@ class NotificationBroadcastReceiverHandlerTest { .isCalledOnce() replyMessage.assertions() .isCalledOnce() - .with(value(A_THREAD_ID.asEventId()), value(A_MESSAGE), value(null), value(emptyList()), value(true)) + .with( + value(replyInThread(eventId = AN_EVENT_ID, explicitReply = false)), + value(A_MESSAGE), + value(null), + value(emptyList()), + value(true) + ) } private fun createIntent( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt index cc4df51b55..8947b646a6 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt @@ -53,7 +53,6 @@ class PushProvidersTestTest { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) - assertThat(lastItem.description).contains("2") assertThat(lastItem.description).contains("foo") assertThat(lastItem.description).contains("bar") } diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index 8ee03dd5ad..6363c6781b 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -105,7 +105,8 @@ object TestTags { /** * Timeline item. */ - val timelineItemSenderInfo = TestTag("timeline_item-sender_info") + val timelineItemSenderAvatar = TestTag("timeline_item-sender_avatar") + val timelineItemSenderName = TestTag("timeline_item-sender_name") /** * Search field. 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 1e74160e81..b2adb257d1 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 @@ -106,6 +106,7 @@ fun TextComposer( onReceiveSuggestion: (Suggestion?) -> Unit, onSelectRichContent: ((Uri) -> Unit)?, resolveMentionDisplay: (text: String, url: String) -> TextDisplay, + resolveAtRoomMentionDisplay: () -> TextDisplay, modifier: Modifier = Modifier, showTextFormatting: Boolean = false, subcomposing: Boolean = false, @@ -176,7 +177,7 @@ fun TextComposer( composerMode = composerMode, onResetComposerMode = onResetComposerMode, resolveMentionDisplay = resolveMentionDisplay, - resolveRoomMentionDisplay = { resolveMentionDisplay("@room", "#") }, + resolveRoomMentionDisplay = resolveAtRoomMentionDisplay, onError = onError, onTyping = onTyping, onSelectRichContent = onSelectRichContent, @@ -930,6 +931,7 @@ private fun ATextComposer( onTyping = {}, onReceiveSuggestion = {}, resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, + resolveAtRoomMentionDisplay = { TextDisplay.Plain }, onSelectRichContent = null, ) } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt index ee0ec181ca..81b2aab287 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt @@ -30,9 +30,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle -import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater import io.element.android.libraries.textcomposer.mentions.MentionSpan -import io.element.android.libraries.textcomposer.mentions.updateMentionStyles import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.SuggestionType @@ -75,7 +74,7 @@ fun MarkdownTextInput( } } - val mentionSpanTheme = LocalMentionSpanTheme.current + val mentionSpanUpdater = LocalMentionSpanUpdater.current AndroidView( modifier = Modifier @@ -124,10 +123,9 @@ fun MarkdownTextInput( }, update = { editText -> editText.applyStyleInCompose(richTextEditorStyle) - + val text = state.text.value() + mentionSpanUpdater.updateMentionSpans(text) if (state.text.needsDisplaying()) { - val text = state.text.value() - mentionSpanTheme.updateMentionStyles(text) editText.updateEditableText(text) if (canUpdateState) { state.text.update(editText.editableText, false) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt index 6d861894a6..bc1de30826 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt @@ -16,11 +16,11 @@ import io.element.android.libraries.core.extensions.orEmpty @Stable class StableCharSequence(initialText: CharSequence = "") { - private var value by mutableStateOf(SpannableString(initialText)) + private var value by mutableStateOf(SpannableString.valueOf(initialText)) private var needsDisplaying by mutableStateOf(false) fun update(newText: CharSequence?, needsDisplaying: Boolean) { - value = SpannableString(newText.orEmpty()) + value = SpannableString.valueOf(newText.orEmpty()) this.needsDisplaying = needsDisplaying } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt index 2f55b96c9f..76587d6e12 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt @@ -11,119 +11,153 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import android.graphics.Typeface +import android.text.TextPaint +import android.text.TextUtils import android.text.style.ReplacementSpan import androidx.core.text.getSpans -import io.element.android.libraries.core.extensions.orEmpty +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.wysiwyg.view.spans.CustomMentionSpan -import kotlin.math.min import kotlin.math.roundToInt +/** + * A span that represents a mention (user, room, etc.) in text. + * @param type The type of mention this span represents. + */ class MentionSpan( - text: String, - val rawValue: String, - val type: Type, + val type: MentionType, ) : ReplacementSpan() { - companion object { - private const val MAX_LENGTH = 20 - } + private val backgroundPaint = Paint() + private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) - var backgroundColor: Int = 0 - var textColor: Int = 0 - var startPadding: Int = 0 - var endPadding: Int = 0 - var typeface: Typeface = Typeface.DEFAULT + private var backgroundColor: Int = 0 + private var textColor: Int = 0 + private var startPadding: Int = 0 + private var endPadding: Int = 0 + private var typeface: Typeface = Typeface.DEFAULT - private var textWidth = 0 - private val backgroundPaint = Paint().apply { - isAntiAlias = true - color = backgroundColor - } + private var measuredTextWidth = 0 - var text: String = text - set(value) { - field = value - mentionText = getActualText(text) + // The formatted display text, will be set by the formatter + var displayText: CharSequence = "" + private set + + /** + * Updates the visual properties of this span. + */ + fun updateTheme(mentionSpanTheme: MentionSpanTheme) { + val isCurrentUser = when (type) { + is MentionType.User -> type.userId == mentionSpanTheme.currentUserId + else -> false } - private var mentionText: CharSequence = getActualText(text) - - fun update(mentionSpanTheme: MentionSpanTheme) { - val isCurrentUser = rawValue == mentionSpanTheme.currentUserId?.value backgroundColor = when (type) { - Type.USER -> if (isCurrentUser) mentionSpanTheme.currentUserBackgroundColor else mentionSpanTheme.otherBackgroundColor - Type.ROOM -> mentionSpanTheme.otherBackgroundColor - Type.EVERYONE -> mentionSpanTheme.currentUserBackgroundColor + is MentionType.User -> if (isCurrentUser) mentionSpanTheme.currentUserBackgroundColor else mentionSpanTheme.otherBackgroundColor + is MentionType.Everyone -> mentionSpanTheme.currentUserBackgroundColor + is MentionType.Room -> mentionSpanTheme.otherBackgroundColor + is MentionType.Message -> mentionSpanTheme.otherBackgroundColor } + textColor = when (type) { - Type.USER -> if (isCurrentUser) mentionSpanTheme.currentUserTextColor else mentionSpanTheme.otherTextColor - Type.ROOM -> mentionSpanTheme.otherTextColor - Type.EVERYONE -> mentionSpanTheme.currentUserTextColor + is MentionType.User -> if (isCurrentUser) mentionSpanTheme.currentUserTextColor else mentionSpanTheme.otherTextColor + is MentionType.Everyone -> mentionSpanTheme.currentUserTextColor + is MentionType.Room -> mentionSpanTheme.otherTextColor + is MentionType.Message -> mentionSpanTheme.otherTextColor } - backgroundPaint.color = backgroundColor + val (startPaddingPx, endPaddingPx) = mentionSpanTheme.paddingValuesPx.value startPadding = startPaddingPx endPadding = endPaddingPx typeface = mentionSpanTheme.typeface.value } - override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { - paint.typeface = typeface - textWidth = paint.measureText(mentionText, 0, mentionText.length).roundToInt() - return textWidth + startPadding + endPadding + /** + * Updates the display text using a formatter. + */ + fun updateDisplayText(formatter: MentionSpanFormatter) { + displayText = formatter.formatDisplayText(type) } - override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { + override fun getSize( + paint: Paint, + text: CharSequence?, + start: Int, + end: Int, + fm: Paint.FontMetricsInt? + ): Int { + textPaint.set(paint) + textPaint.typeface = typeface + // Measure the full text width without truncation + measuredTextWidth = textPaint.measureText(displayText, 0, displayText.length).roundToInt() + return measuredTextWidth + startPadding + endPadding + } + + override fun draw( + canvas: Canvas, + text: CharSequence?, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint + ) { // Extra vertical space to add below the baseline (y). This helps us center the span vertically val extraVerticalSpace = y + paint.ascent() + paint.descent() - top - val rect = RectF(x, top.toFloat(), x + textWidth + startPadding + endPadding, y.toFloat() + extraVerticalSpace) + val availableWidth = (canvas.width - x).coerceAtLeast(0f) + val measuredWidth = measuredTextWidth + startPadding + endPadding + val pillWidth = minOf(availableWidth, measuredWidth.toFloat()) + + backgroundPaint.color = backgroundColor + val rect = RectF(x, top.toFloat(), x + pillWidth, y.toFloat() + extraVerticalSpace) val radius = rect.height() / 2 canvas.drawRoundRect(rect, radius, radius, backgroundPaint) - paint.color = textColor - paint.typeface = typeface - canvas.drawText(mentionText, 0, mentionText.length, x + startPadding, y.toFloat(), paint) - } - private fun getActualText(text: String): CharSequence { - return buildString { - val mentionText = text.orEmpty() - when (type) { - Type.USER -> { - if (text.firstOrNull() != '@') { - append("@") - } - } - Type.ROOM -> { - if (text.firstOrNull() != '#') { - append("#") - } - } - Type.EVERYONE -> Unit - } - append(mentionText.substring(0, min(mentionText.length, MAX_LENGTH))) - if (mentionText.length > MAX_LENGTH) { - append("…") - } + textPaint.set(paint) + textPaint.color = textColor + textPaint.typeface = typeface + + val availableWidthForText = availableWidth - startPadding - endPadding + val textToDraw = if (measuredTextWidth > availableWidthForText) { + TextUtils.ellipsize( + displayText, + textPaint, + availableWidthForText, + TextUtils.TruncateAt.END + ) + } else { + displayText } - } - - enum class Type { - USER, - ROOM, - EVERYONE, + canvas.drawText(textToDraw, 0, textToDraw.length, x + startPadding, y.toFloat(), textPaint) } } -fun CharSequence.getMentionSpans(): List { +/** + * Sealed interface representing different types of mentions. + */ +sealed interface MentionType { + data class User(val userId: UserId) : MentionType + data class Room(val roomIdOrAlias: RoomIdOrAlias) : MentionType + data class Message(val roomIdOrAlias: RoomIdOrAlias, val eventId: EventId) : MentionType + data object Everyone : MentionType +} + +/** + * Extension function to get all MentionSpans from a CharSequence. + */ +fun CharSequence.getMentionSpans(start: Int = 0, end: Int = length): List { return if (this is android.text.Spanned) { - val customMentionSpans = getSpans() - if (customMentionSpans.isNotEmpty()) { - // If we have custom mention spans created by the RTE, we need to extract the provided spans and filter them - customMentionSpans.map { it.providedSpan }.filterIsInstance() - } else { - // Otherwise try to get the spans directly - getSpans().toList() - } + // If we have custom mention spans created by the RTE, we need to extract the provided spans and filter them + val customMentionSpans = getSpans(start, end) + .map { it.providedSpan } + .filterIsInstance() + // Collect all direct mention spans + val directMentionSpans = getSpans(start, end) + // Return the union of both + customMentionSpans + directMentionSpans } else { emptyList() } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt new file mode 100644 index 0000000000..f4b5f96bf4 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.textcomposer.mentions + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.matrix.ui.messages.RoomNamesCache +import javax.inject.Inject + +private const val EVERYONE_DISPLAY_TEXT = "@room" +private const val BUBBLE_ICON = "\uD83D\uDCAC" // 💬 + +interface MentionSpanFormatter { + fun formatDisplayText(mentionType: MentionType): CharSequence +} + +/** + * Formatter for MentionSpan display text. + * This class is responsible for formatting the display text of a MentionSpan + * based on its MentionType and context. + */ +@ContributesBinding(RoomScope::class) +class DefaultMentionSpanFormatter @Inject constructor( + private val roomMemberProfilesCache: RoomMemberProfilesCache, + private val roomNamesCache: RoomNamesCache, +) : MentionSpanFormatter { + /** + * Format the display text for a mention span. + * + * @param mentionType The type of mention + * @return The formatted display text + */ + override fun formatDisplayText(mentionType: MentionType): CharSequence { + return when (mentionType) { + is MentionType.User -> formatUserMention(mentionType.userId) + is MentionType.Room -> formatRoomMention(mentionType.roomIdOrAlias) + is MentionType.Message -> formatMessageMention(mentionType.roomIdOrAlias) + is MentionType.Everyone -> EVERYONE_DISPLAY_TEXT + } + } + + private fun formatUserMention(userId: UserId): String { + // Try to get the display name from cache, fallback to userId + val displayName = roomMemberProfilesCache.getDisplayName(userId) + return if (displayName != null) { + "@$displayName" + } else { + userId.value + } + } + + private fun formatRoomMention(roomIdOrAlias: RoomIdOrAlias): String { + val displayName = roomNamesCache.getDisplayName(roomIdOrAlias) + return if (displayName != null) { + "#$displayName" + } else { + roomIdOrAlias.identifier + } + } + + private fun formatMessageMention( + roomIdOrAlias: RoomIdOrAlias, + ): String { + val roomMention = formatRoomMention(roomIdOrAlias) + return "$BUBBLE_ICON > $roomMention" + } +} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt index 63197ffb9c..a233897254 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt @@ -7,46 +7,118 @@ package io.element.android.libraries.textcomposer.mentions -import androidx.compose.runtime.Stable +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import javax.inject.Inject -@Stable +private const val EVERYONE_MENTION_TEXT = "@room" + +/** + * Provider for [MentionSpan]s. + */ open class MentionSpanProvider @Inject constructor( private val permalinkParser: PermalinkParser, + private val mentionSpanFormatter: MentionSpanFormatter, + private val mentionSpanTheme: MentionSpanTheme, ) { - fun getMentionSpanFor(text: String, url: String): MentionSpan { + /** + * Creates a mention span from a text and URL. + * + * @param text The text associated with the mention + * @param url The URL associated with the mention + * @return A mention span if the URL can be parsed as a permalink, null otherwise + */ + fun getMentionSpanFor(text: String, url: String): MentionSpan? { val permalinkData = permalinkParser.parse(url) - return when { - permalinkData is PermalinkData.UserLink -> { - MentionSpan( - text = text, - rawValue = permalinkData.userId.toString(), - type = MentionSpan.Type.USER, - ) + return getMentionSpanFor(text, permalinkData) + } + + /** + * Creates a mention span from a text and permalink data. + * + * @param text The text associated with the mention + * @param permalinkData The permalink data associated with the mention + * @return A mention span based on the permalink data, null if the permalink data is not supported + */ + private fun getMentionSpanFor(text: String, permalinkData: PermalinkData): MentionSpan? { + return when (permalinkData) { + is PermalinkData.UserLink -> { + createUserMentionSpan(permalinkData.userId) } - text == "@room" && permalinkData is PermalinkData.FallbackLink -> { - MentionSpan( - text = text, - rawValue = "@room", - type = MentionSpan.Type.EVERYONE, - ) + is PermalinkData.RoomLink -> { + val eventId = permalinkData.eventId + if (eventId != null) { + createMessageMentionSpan(permalinkData.roomIdOrAlias, eventId) + } else { + createRoomMentionSpan(permalinkData.roomIdOrAlias) + } } - permalinkData is PermalinkData.RoomLink -> { - MentionSpan( - text = text, - rawValue = permalinkData.roomIdOrAlias.identifier, - type = MentionSpan.Type.ROOM, - ) - } - else -> { - MentionSpan( - text = text, - rawValue = text, - type = MentionSpan.Type.ROOM, - ) + is PermalinkData.FallbackLink -> { + if (text == EVERYONE_MENTION_TEXT) { + createEveryoneMentionSpan() + } else { + null + } } + else -> null + } + } + + /** + * Create a mention span for a user mention. + * + * @param userId The user ID + * @return A mention span for the user + */ + fun createUserMentionSpan(userId: UserId): MentionSpan { + return MentionSpan(type = MentionType.User(userId = userId)).apply { + updateDisplayText(mentionSpanFormatter) + updateTheme(mentionSpanTheme) + } + } + + /** + * Create a mention span for a room mention. + * + * @param roomIdOrAlias The room ID or alias + * @return A mention span for the room + */ + fun createRoomMentionSpan(roomIdOrAlias: RoomIdOrAlias): MentionSpan { + return MentionSpan(MentionType.Room(roomIdOrAlias)).apply { + updateDisplayText(mentionSpanFormatter) + updateTheme(mentionSpanTheme) + } + } + + /** + * Create a mention span for a message mention. + * + * @param roomIdOrAlias The room ID or alias where the message is located + * @param eventId The event ID of the message + * @return A mention span for the message + */ + fun createMessageMentionSpan( + roomIdOrAlias: RoomIdOrAlias, + eventId: EventId, + ): MentionSpan { + return MentionSpan(type = MentionType.Message(roomIdOrAlias, eventId)).apply { + updateTheme(mentionSpanTheme) + updateDisplayText(mentionSpanFormatter) + } + } + + /** + * Create a mention span for @room (everyone). + * + * @return A mention span for @room + */ + fun createEveryoneMentionSpan(): MentionSpan { + return MentionSpan(type = MentionType.Everyone).apply { + updateTheme(mentionSpanTheme) + updateDisplayText(mentionSpanFormatter) } } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt index b81fb45b32..edd5cfe9dd 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt @@ -15,11 +15,9 @@ import android.view.ViewGroup import android.widget.TextView import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection @@ -34,6 +32,9 @@ import io.element.android.libraries.designsystem.theme.currentUserMentionPillBac import io.element.android.libraries.designsystem.theme.currentUserMentionPillText import io.element.android.libraries.designsystem.theme.mentionPillBackground import io.element.android.libraries.designsystem.theme.mentionPillText +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias @@ -45,13 +46,14 @@ import javax.inject.Inject /** * Theme used for mention spans. * To make this work, you need to: - * 1. Provide [LocalMentionSpanTheme] in a composable that wraps the ones where you want to use mentions. - * 2. Call [MentionSpanTheme.updateStyles] with the current [UserId] so the colors and sizes are computed. - * 3. Use either [MentionSpanTheme.updateMentionStyles] or [MentionSpan.update] to update the styles of the mention spans. + * 1. Call [MentionSpanTheme.updateStyles] so the colors and sizes are computed. + * 2. Use either [MentionSpanTheme.updateMentionStyles] or [MentionSpan.updateTheme] to update the styles of the mention spans. */ @Stable -class MentionSpanTheme @Inject constructor() { - internal var currentUserId: UserId? = null +@SingleIn(SessionScope::class) +class MentionSpanTheme(val currentUserId: UserId) { + @Inject constructor(matrixClient: MatrixClient) : this(matrixClient.sessionId) + internal var currentUserTextColor: Int = 0 internal var currentUserBackgroundColor: Int = Color.WHITE internal var otherTextColor: Int = 0 @@ -66,8 +68,7 @@ class MentionSpanTheme @Inject constructor() { */ @Suppress("ComposableNaming") @Composable - fun updateStyles(currentUserId: UserId) { - this.currentUserId = currentUserId + fun updateStyles() { currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb() currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb() otherTextColor = ElementTheme.colors.mentionPillText.toArgb() @@ -93,24 +94,28 @@ fun MentionSpanTheme.updateMentionStyles(charSequence: CharSequence) { val spanned = charSequence as? Spanned ?: return val mentionSpans = spanned.getMentionSpans() for (span in mentionSpans) { - span.update(this) + span.updateTheme(this) } } -/** - * Composition local containing the current [MentionSpanTheme]. - */ -val LocalMentionSpanTheme = staticCompositionLocalOf { - MentionSpanTheme() -} - - @PreviewsDayNight - @Composable - internal fun MentionSpanThemePreview() { +@PreviewsDayNight +@Composable +internal fun MentionSpanThemePreview() { ElementPreview { - val mentionSpanTheme = remember { MentionSpanTheme() } + val mentionSpanTheme = remember { MentionSpanTheme(UserId("@me:matrix.org")) } val provider = remember { MentionSpanProvider( + mentionSpanTheme = mentionSpanTheme, + mentionSpanFormatter = object : MentionSpanFormatter { + override fun formatDisplayText(mentionType: MentionType): CharSequence { + return when (mentionType) { + is MentionType.User -> mentionType.userId.value + is MentionType.Room -> mentionType.roomIdOrAlias.identifier + is MentionType.Message -> "\uD83D\uDCAC️ > ${mentionType.roomIdOrAlias.identifier}" + is MentionType.Everyone -> "@room" + } + } + }, permalinkParser = object : PermalinkParser { override fun parse(uriString: String): PermalinkData { return when (uriString) { @@ -133,36 +138,31 @@ val LocalMentionSpanTheme = staticCompositionLocalOf { fun mentionSpanMe() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@me:matrix.org") fun mentionSpanOther() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@other:matrix.org") fun mentionSpanRoom() = provider.getMentionSpanFor("room:matrix.org", "https://matrix.to/#/#room:matrix.org") - fun mentionSpanEveryone() = provider.getMentionSpanFor("@room", "@room") - mentionSpanTheme.updateStyles(currentUserId = UserId("@me:matrix.org")) + fun mentionSpanEveryone() = provider.createEveryoneMentionSpan() + mentionSpanTheme.updateStyles() - CompositionLocalProvider( - LocalMentionSpanTheme provides mentionSpanTheme - ) { - AndroidView(factory = { context -> - TextView(context).apply { - includeFontPadding = false - layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - text = buildSpannedString { - append("This is a ") - append("@mention", mentionSpanMe(), 0) - append(" to the current user and this is a ") - append("@mention", mentionSpanOther(), 0) - append(" to other user. This is for everyone in the ") - append("@room", mentionSpanEveryone(), 0) - append(". This one is for a link to another room: ") - append("#room:matrix.org", mentionSpanRoom(), 0) - append("\n\n") - append("This ") - append("mention", mentionSpanMe(), 0) - append(" didn't have an '@' and it was automatically added, same as this ") - append("room:matrix.org", mentionSpanRoom(), 0) - append(" one, which had no leading '#'.") - } - mentionSpanTheme.updateMentionStyles(text) - setTextColor(textColor) + AndroidView(factory = { context -> + TextView(context).apply { + includeFontPadding = false + layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + text = buildSpannedString { + append("This is a ") + append("@mention", mentionSpanMe(), 0) + append(" to the current user and this is a ") + append("@mention", mentionSpanOther(), 0) + append(" to other user. This is for everyone in the ") + append("@room", mentionSpanEveryone(), 0) + append(". This one is for a link to another room: ") + append("#room:matrix.org", mentionSpanRoom(), 0) + append("\n\n") + append("This ") + append("mention", mentionSpanMe(), 0) + append(" didn't have an '@' and it was automatically added, same as this ") + append("room:matrix.org", mentionSpanRoom(), 0) + append(" one, which had no leading '#'.") } - }) - } + setTextColor(textColor) + } + }) } - } +} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt new file mode 100644 index 0000000000..94d74a72e3 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.textcomposer.mentions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.matrix.ui.messages.RoomNamesCache +import javax.inject.Inject + +interface MentionSpanUpdater { + fun updateMentionSpans(text: CharSequence): CharSequence + + @Composable + fun rememberMentionSpans(text: CharSequence): CharSequence +} + +@ContributesBinding(RoomScope::class) +class DefaultMentionSpanUpdater @Inject constructor( + private val formatter: MentionSpanFormatter, + private val theme: MentionSpanTheme, + private val roomMemberProfilesCache: RoomMemberProfilesCache, + private val roomNamesCache: RoomNamesCache, +) : MentionSpanUpdater { + @Composable + override fun rememberMentionSpans(text: CharSequence): CharSequence { + val isLightTheme = ElementTheme.isLightTheme + val roomInfoCacheUpdate by roomNamesCache.updateFlow.collectAsState(0) + val roomMemberProfilesCacheUpdate by roomMemberProfilesCache.updateFlow.collectAsState(0) + return remember(text, roomInfoCacheUpdate, roomMemberProfilesCacheUpdate, isLightTheme) { + updateMentionSpans(text) + text + } + } + + override fun updateMentionSpans(text: CharSequence): CharSequence { + for (mentionSpan in text.getMentionSpans()) { + mentionSpan.updateTheme(theme) + mentionSpan.updateDisplayText(formatter) + } + return text + } +} + +private object NoOpMentionSpanUpdater : MentionSpanUpdater { + override fun updateMentionSpans(text: CharSequence): CharSequence { + return text + } + + @Composable + override fun rememberMentionSpans(text: CharSequence): CharSequence { + return text + } +} + +val LocalMentionSpanUpdater = staticCompositionLocalOf { NoOpMentionSpanUpdater } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt index 543fe093d4..14f56f3bbb 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.textcomposer.model import android.os.Parcelable import android.text.Spannable -import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.Spanned import androidx.compose.runtime.Composable @@ -21,14 +20,13 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.SaverScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.core.text.getSpans -import io.element.android.libraries.matrix.api.core.RoomAlias -import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.textcomposer.components.markdown.StableCharSequence -import io.element.android.libraries.textcomposer.mentions.MentionSpan import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionType import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.mentions.getMentionSpans import kotlinx.parcelize.Parcelize @@ -48,39 +46,33 @@ class MarkdownTextEditorState( fun insertSuggestion( resolvedSuggestion: ResolvedSuggestion, mentionSpanProvider: MentionSpanProvider, - permalinkBuilder: PermalinkBuilder, ) { val suggestion = currentSuggestion ?: return when (resolvedSuggestion) { is ResolvedSuggestion.AtRoom -> { val currentText = SpannableStringBuilder(text.value()) - val replaceText = "@room" - val roomPill = mentionSpanProvider.getMentionSpanFor(replaceText, "") + val mentionSpan = mentionSpanProvider.createEveryoneMentionSpan() currentText.replace(suggestion.start, suggestion.end, "@ ") val end = suggestion.start + 1 - currentText.setSpan(roomPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) text.update(currentText, true) selection = IntRange(end + 1, end + 1) } is ResolvedSuggestion.Member -> { val currentText = SpannableStringBuilder(text.value()) - val text = resolvedSuggestion.roomMember.displayName?.prependIndent("@") ?: resolvedSuggestion.roomMember.userId.value - val link = permalinkBuilder.permalinkForUser(resolvedSuggestion.roomMember.userId).getOrNull() ?: return - val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link) + val mentionSpan = mentionSpanProvider.createUserMentionSpan(resolvedSuggestion.roomMember.userId) currentText.replace(suggestion.start, suggestion.end, "@ ") val end = suggestion.start + 1 - currentText.setSpan(mentionPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) this.text.update(currentText, true) this.selection = IntRange(end + 1, end + 1) } is ResolvedSuggestion.Alias -> { val currentText = SpannableStringBuilder(text.value()) - val text = resolvedSuggestion.roomAlias.value - val link = permalinkBuilder.permalinkForRoomAlias(resolvedSuggestion.roomAlias).getOrNull() ?: return - val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link) + val mentionSpan = mentionSpanProvider.createRoomMentionSpan(resolvedSuggestion.roomAlias.toRoomIdOrAlias()) currentText.replace(suggestion.start, suggestion.end, "# ") val end = suggestion.start + 1 - currentText.setSpan(mentionPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) this.text.update(currentText, true) this.selection = IntRange(end + 1, end + 1) } @@ -98,19 +90,23 @@ class MarkdownTextEditorState( val start = charSequence.getSpanStart(mention) val end = charSequence.getSpanEnd(mention) when (mention.type) { - MentionSpan.Type.USER -> { - permalinkBuilder.permalinkForUser(UserId(mention.rawValue)).getOrNull()?.let { link -> - replace(start, end, "[${mention.rawValue}]($link)") + is MentionType.User -> { + permalinkBuilder.permalinkForUser(mention.type.userId).getOrNull()?.let { link -> + replace(start, end, "[${mention.type.userId}]($link)") } } - MentionSpan.Type.EVERYONE -> { + is MentionType.Everyone -> { replace(start, end, "@room") } - MentionSpan.Type.ROOM -> { - permalinkBuilder.permalinkForRoomAlias(RoomAlias(mention.rawValue)).getOrNull()?.let { link -> - replace(start, end, "[${mention.text}]($link)") + is MentionType.Room -> { + val roomIdOrAlias = mention.type.roomIdOrAlias + if (roomIdOrAlias is RoomIdOrAlias.Alias) { + permalinkBuilder.permalinkForRoomAlias(roomIdOrAlias.roomAlias).getOrNull()?.let { link -> + replace(start, end, "[${roomIdOrAlias.roomAlias}]($link)") + } } } + else -> Unit } } } @@ -121,13 +117,12 @@ class MarkdownTextEditorState( } fun getMentions(): List { - val text = SpannableString(text.value()) - val mentionSpans = text.getSpans(0, text.length) + val mentionSpans = text.value().getMentionSpans() return mentionSpans.mapNotNull { mentionSpan -> when (mentionSpan.type) { - MentionSpan.Type.USER -> IntentionalMention.User(UserId(mentionSpan.rawValue)) - MentionSpan.Type.EVERYONE -> IntentionalMention.Room - MentionSpan.Type.ROOM -> null + is MentionType.User -> IntentionalMention.User(mentionSpan.type.userId) + is MentionType.Everyone -> IntentionalMention.Room + else -> 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 44eaee1fd3..ca7018aaa7 100644 --- a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml @@ -7,6 +7,7 @@ "Aggiungi una didascalia" "Messaggio cifrato…" "Messaggio…" + "Messaggio non cifrato…" "Crea un collegamento" "Modifica collegamento" "Applica il formato grassetto" diff --git a/libraries/textcomposer/impl/src/main/res/values-pl/translations.xml b/libraries/textcomposer/impl/src/main/res/values-pl/translations.xml index e47afe0fe1..121bc2f4d8 100644 --- a/libraries/textcomposer/impl/src/main/res/values-pl/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-pl/translations.xml @@ -5,7 +5,9 @@ "Zamknij opcje formatowania" "Przełącz blok kodu" "Dodaj opis" + "Wiadomość szyfrowana…" "Wiadomość…" + "Niezaszyfrowana wiadomość…" "Utwórz link" "Edytuj link" "Zastosuj pogrubienie" diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt index 9fdc4e2246..1176cdadaa 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt @@ -16,14 +16,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.test.A_SESSION_ID -import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput +import io.element.android.libraries.textcomposer.impl.mentions.aMentionSpanProvider import io.element.android.libraries.textcomposer.mentions.MentionSpan -import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState import io.element.android.libraries.textcomposer.model.Suggestion @@ -146,7 +145,6 @@ class MarkdownTextInputTest { @Test fun `inserting a mention replaces the existing text with a span`() = runTest { val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(A_SESSION_ID) }) - val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$A_SESSION_ID") }) val state = aMarkdownTextEditorState(initialText = "@", initialFocus = true) state.currentSuggestion = Suggestion(0, 1, SuggestionType.Mention, "") rule.setMarkdownTextInput(state = state) @@ -155,8 +153,7 @@ class MarkdownTextInputTest { editor = it.findEditor() state.insertSuggestion( ResolvedSuggestion.Member(roomMember = aRoomMember()), - MentionSpanProvider(permalinkParser = permalinkParser), - permalinkBuilder, + aMentionSpanProvider(permalinkParser), ) } rule.awaitIdle() diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt index 50ac074078..de4b9bc64a 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt @@ -14,8 +14,7 @@ import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser -import io.element.android.libraries.textcomposer.mentions.MentionSpan -import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionType import io.element.android.tests.testutils.WarmUpRule import org.junit.Rule import org.junit.Test @@ -28,22 +27,22 @@ class IntentionalMentionSpanProviderTest { val warmUpRule = WarmUpRule() private val permalinkParser = FakePermalinkParser() - private val mentionSpanProvider = MentionSpanProvider( - permalinkParser = permalinkParser, - ) + private val mentionSpanProvider = aMentionSpanProvider(permalinkParser) @Test fun `getting mention span for a user returns a MentionSpan of type USER`() { permalinkParser.givenResult(PermalinkData.UserLink(A_USER_ID)) val mentionSpan = mentionSpanProvider.getMentionSpanFor("@me:matrix.org", "https://matrix.to/#/${A_USER_ID.value}") - assertThat(mentionSpan.type).isEqualTo(MentionSpan.Type.USER) + assertThat(mentionSpan?.type).isInstanceOf(MentionType.User::class.java) + val userType = mentionSpan?.type as MentionType.User + assertThat(userType.userId).isEqualTo(A_USER_ID) } @Test fun `getting mention span for everyone in the room returns a MentionSpan of type EVERYONE`() { permalinkParser.givenResult(PermalinkData.FallbackLink(uri = Uri.EMPTY)) val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#") - assertThat(mentionSpan.type).isEqualTo(MentionSpan.Type.EVERYONE) + assertThat(mentionSpan?.type).isEqualTo(MentionType.Everyone) } @Test @@ -54,6 +53,8 @@ class IntentionalMentionSpanProviderTest { ) ) val mentionSpan = mentionSpanProvider.getMentionSpanFor("#room:matrix.org", "https://matrix.to/#/#room:matrix.org") - assertThat(mentionSpan.type).isEqualTo(MentionSpan.Type.ROOM) + assertThat(mentionSpan?.type).isInstanceOf(MentionType.Room::class.java) + val roomType = mentionSpan?.type as MentionType.Room + assertThat(roomType.roomIdOrAlias).isEqualTo(RoomAlias("#room:matrix.org").toRoomIdOrAlias()) } } diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt new file mode 100644 index 0000000000..2d0156a3df --- /dev/null +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.textcomposer.impl.mentions + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_ROOM_ALIAS +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.room.aRoomMember +import io.element.android.libraries.matrix.test.room.aRoomSummary +import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache +import io.element.android.libraries.matrix.ui.messages.RoomNamesCache +import io.element.android.libraries.textcomposer.mentions.DefaultMentionSpanFormatter +import io.element.android.libraries.textcomposer.mentions.MentionType +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MentionSpanFormatterTest { + private val roomMemberProfilesCache = RoomMemberProfilesCache() + private val roomNamesCache = RoomNamesCache() + private val formatter = DefaultMentionSpanFormatter( + roomMemberProfilesCache = roomMemberProfilesCache, + roomNamesCache = roomNamesCache, + ) + + @Test + fun `formatDisplayText - formats user mention with empty cache`() = runTest { + val userId = A_USER_ID + val mentionType = MentionType.User(userId) + val result = formatter.formatDisplayText(mentionType) + assertThat(result.toString()).isEqualTo(userId.value) + } + + @Test + fun `formatDisplayText - formats user mention with filled cache`() = runTest { + val userId = A_USER_ID + val roomMember = aRoomMember(userId, displayName = "alice") + roomMemberProfilesCache.replace(listOf(roomMember)) + val mentionType = MentionType.User(userId) + val result = formatter.formatDisplayText(mentionType) + assertThat(result.toString()).isEqualTo("@alice") + } + + @Test + fun `formatDisplayText - formats room mention with empty cache`() = runTest { + val roomAlias = A_ROOM_ALIAS + val mentionType = MentionType.Room(roomAlias.toRoomIdOrAlias()) + + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo(roomAlias.value) + } + + @Test + fun `formatDisplayText - formats room mention with filled cache`() = runTest { + val roomAlias = A_ROOM_ALIAS + val roomSummary = aRoomSummary( + canonicalAlias = roomAlias, + name = "my room" + ) + roomNamesCache.replace(listOf(roomSummary)) + val mentionType = MentionType.Room(roomAlias.toRoomIdOrAlias()) + + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo("#my room") + } + + @Test + fun `formatDisplayText - formats room mention with room id and empty cache`() = runTest { + val roomId = A_ROOM_ID + val mentionType = MentionType.Room(roomId.toRoomIdOrAlias()) + + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo(roomId.value) + } + + @Test + fun `formatDisplayText - formats room mention with room id and filled cache`() = runTest { + val roomId = A_ROOM_ID + val roomSummary = aRoomSummary( + roomId = roomId, + name = "my room" + ) + roomNamesCache.replace(listOf(roomSummary)) + + val mentionType = MentionType.Room(roomId.toRoomIdOrAlias()) + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo("#my room") + } + + @Test + fun `formatDisplayText - formats message mention with empty cache`() = runTest { + val roomId = A_ROOM_ID + val mentionType = MentionType.Message(roomId.toRoomIdOrAlias(), eventId = AN_EVENT_ID) + + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo("💬 > ${roomId.value}") + } + + @Test + fun `formatDisplayText - formats message mention with filled cache`() = runTest { + val roomId = A_ROOM_ID + val roomSummary = aRoomSummary( + roomId = roomId, + name = "my room" + ) + roomNamesCache.replace(listOf(roomSummary)) + + val mentionType = MentionType.Message(roomId.toRoomIdOrAlias(), eventId = AN_EVENT_ID) + + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo("💬 > #my room") + } + + @Test + fun `formatDisplayText - formats everyone mention`() = runTest { + val mentionType = MentionType.Everyone + + val result = formatter.formatDisplayText(mentionType) + + assertThat(result.toString()).isEqualTo("@room") + } +} diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt new file mode 100644 index 0000000000..cc40a56bf4 --- /dev/null +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.textcomposer.impl.mentions + +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.textcomposer.mentions.MentionSpanFormatter +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme +import io.element.android.libraries.textcomposer.mentions.MentionType + +fun aMentionSpanProvider( + permalinkParser: PermalinkParser = FakePermalinkParser(), + mentionSpanFormatter: MentionSpanFormatter = object : MentionSpanFormatter { + override fun formatDisplayText(mentionType: MentionType): CharSequence { + return mentionType.toString() + } + }, + mentionSpanTheme: MentionSpanTheme = MentionSpanTheme(A_USER_ID), +): MentionSpanProvider { + return MentionSpanProvider( + permalinkParser = permalinkParser, + mentionSpanFormatter = mentionSpanFormatter, + mentionSpanTheme = mentionSpanTheme, + ) +} diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt index 33156add6f..2c7905e2da 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt @@ -12,6 +12,8 @@ import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.IntentionalMention @@ -20,8 +22,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.textcomposer.impl.mentions.aMentionSpanProvider import io.element.android.libraries.textcomposer.mentions.MentionSpan -import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.libraries.textcomposer.mentions.MentionType import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.SuggestionType @@ -35,9 +38,8 @@ class MarkdownTextEditorStateTest { fun `insertMention - room alias - getMentions return empty list`() { val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true) val suggestion = aRoomAliasSuggestion() - val permalinkBuilder = FakePermalinkBuilder() val mentionSpanProvider = aMentionSpanProvider() - state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder) + state.insertSuggestion(suggestion, mentionSpanProvider) assertThat(state.getMentions()).isEmpty() } @@ -48,9 +50,8 @@ class MarkdownTextEditorStateTest { } val suggestion = aRoomAliasSuggestion() val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) }) - val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.failure(IllegalStateException("Failed")) }) val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) - state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder) + state.insertSuggestion(suggestion, mentionSpanProvider) } @Test @@ -60,9 +61,8 @@ class MarkdownTextEditorStateTest { } val suggestion = aRoomAliasSuggestion() val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) }) - val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/${A_ROOM_ALIAS.value}") }) val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) - state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder) + state.insertSuggestion(suggestion, mentionSpanProvider) } @Test @@ -70,31 +70,11 @@ class MarkdownTextEditorStateTest { val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true) val member = aRoomMember() val mention = ResolvedSuggestion.Member(member) - val permalinkBuilder = FakePermalinkBuilder() val mentionSpanProvider = aMentionSpanProvider() - - state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder) - + state.insertSuggestion(mention, mentionSpanProvider) assertThat(state.getMentions()).isEmpty() } - @Test - fun `insertSuggestion - with member but failed PermalinkBuilder result`() { - val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply { - currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "") - } - val member = aRoomMember() - val mention = ResolvedSuggestion.Member(member) - val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) }) - val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.failure(IllegalStateException("Failed")) }) - val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) - - state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder) - - val mentions = state.getMentions() - assertThat(mentions).isEmpty() - } - @Test fun `insertSuggestion - with member`() { val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply { @@ -103,10 +83,9 @@ class MarkdownTextEditorStateTest { val member = aRoomMember() val mention = ResolvedSuggestion.Member(member) val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) }) - val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/${member.userId}") }) val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) - state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder) + state.insertSuggestion(mention, mentionSpanProvider) val mentions = state.getMentions() assertThat(mentions).isNotEmpty() @@ -119,11 +98,10 @@ class MarkdownTextEditorStateTest { currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "") } val mention = ResolvedSuggestion.AtRoom - val permalinkBuilder = FakePermalinkBuilder() val permalinkParser = FakePermalinkParser(result = { PermalinkData.FallbackLink(Uri.EMPTY, false) }) val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser) - state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder) + state.insertSuggestion(mention, mentionSpanProvider) val mentions = state.getMentions() assertThat(mentions).isNotEmpty() @@ -177,16 +155,10 @@ class MarkdownTextEditorStateTest { assertThat(mentions.lastOrNull()).isInstanceOf(IntentionalMention.Room::class.java) } - private fun aMentionSpanProvider( - permalinkParser: FakePermalinkParser = FakePermalinkParser(), - ): MentionSpanProvider { - return MentionSpanProvider(permalinkParser) - } - private fun aMarkdownTextWithMentions(): CharSequence { - val userMentionSpan = MentionSpan("@Alice", "@alice:matrix.org", MentionSpan.Type.USER) - val atRoomMentionSpan = MentionSpan("@room", "@room", MentionSpan.Type.EVERYONE) - val roomMentionSpan = MentionSpan("#room:domain.org", "#room:domain.org", MentionSpan.Type.ROOM) + val userMentionSpan = MentionSpan(MentionType.User(UserId("@alice:matrix.org"))) + val atRoomMentionSpan = MentionSpan(MentionType.Everyone) + val roomMentionSpan = MentionSpan(MentionType.Room(RoomAlias("#room:domain.org").toRoomIdOrAlias())) return buildSpannedString { append("Hello ") inSpans(userMentionSpan) { 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 e62257aa3e..018921416f 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -1,5 +1,6 @@ + "Profilový obrázek" "Smazat" "zadána %1$d číslice" @@ -7,11 +8,13 @@ "zadáno %1$d číslic" "Skrýt heslo" + "Připojit se k hovoru" "Přejít dolů" "Pouze zmínky" "Ztišeno" "Strana %1$d" "Pozastavit" + "Hlasová zpráva, délka: %1$s, aktuální pozice: %2$s" "Pole pro PIN" "Přehrát" "Hlasování" @@ -32,6 +35,7 @@ "Zahájit hovor" "Uživatelské menu" "Zobrazit podrobnosti" + "Hlasová zpráva, délka: %1$s" "Nahrajte hlasovou zprávu." "Zastavit nahrávání" "Přijmout" @@ -61,6 +65,7 @@ "Odstranit hlasování" "Zakázat" "Vyřadit" + "Zavřít" "Hotovo" "Upravit" "Upravit titulek" @@ -205,6 +210,7 @@ Důvod: %1$s." "%1$s (%2$s)" "Žádné výsledky" "Žádný název místnosti" + "Nešifrováno" "Offline" "Licence s otevřeným zdrojovým kódem" "nebo" @@ -280,18 +286,18 @@ Důvod: %1$s." "Ověření se nezdařilo" "Ověřeno" "Ověřit zařízení" - "Ověření totožnosti" + "Ověření identity" "Ověřit uživatele" "Video" "Hlasová zpráva" "Čekání…" "Čekání na dešifrovací klíč" "Vy" - "Zdá se, že se identita %1$s změnila. %2$s" - "Zdá se, že identita %1$s %2$s se změnila. %3$s" + "Identita uživatele %1$s se změnila. %2$s" + "Identita uživatele %1$s %2$s se změnila. %3$s" "(%1$s)" - "Ověřená identita %1$s se změnila." - "Ověřená identita uživatele %1$s %2$s se změnila. %3$s" + "Identita uživatele %1$s se změnila." + "Identita uživatele %1$s %2$s se změnila. %3$s" "Zrušit ověření" "Odkaz %1$s vás přesměruje na jinou stránku %2$s @@ -375,5 +381,5 @@ Opravdu chcete pokračovat?" "Pro přístup k historickým zprávám musíte toto zařízení ověřit" "Nemáte přístup k této zprávě" "Nelze dešifrovat zprávu" - "Tato zpráva byla zablokována buď proto, že jste neověřili své zařízení, nebo proto, že odesílatel potřebuje ověřit vaši totožnost." + "Tato zpráva byla zablokována buď proto, že jste neověřili své zařízení, nebo proto, že odesílatel potřebuje ověřit vaši identitu." 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 cc9e660018..c60ed87896 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -1,16 +1,19 @@ + "Avatar" "Löschen" "%1$d eingegebene Ziffer" "%1$d eingegebene Ziffern" "Passwort verbergen" + "Anruf beitreten" "Nach unten springen" "Nur Erwähnungen" "Stummgeschaltet" "Seite %1$d" "Pausieren" + "Sprachnachricht, Dauer:%1$s, aktuelle Position: %2$s" "PIN-Feld" "Abspielen" "Umfrage" @@ -30,6 +33,7 @@ "Anruf starten" "Benutzermenü" "Details anzeigen" + "Sprachnachricht, Dauer: %1$s" "Sprachnachricht aufnehmen." "Aufnahme beenden" "Akzeptieren" @@ -59,6 +63,7 @@ "Umfrage löschen" "Deaktivieren" "Verwerfen" + "Schließen" "Erledigt" "Bearbeiten" "Bildunterschrift bearbeiten" @@ -202,6 +207,7 @@ Grund: %1$s." "%1$s(%2$s)" "Keine Ergebnisse" "Kein Raumname" + "Nicht verschlüsselt" "Offline" "Open-Source-Lizenzen" "oder" @@ -283,11 +289,11 @@ Grund: %1$s." "Warten…" "Warte auf diese Nachricht" "Sie" - "%1$s\'s Identität has sich offenbar geändert. %2$s" - "%1$s\'s %2$s Identität hat sich offenbar geändert. %3$s" + "%1$s\'s Identität has sich geändert. %2$s" + "%1$s\'s %2$s Identität hat sich geändert. %3$s" "(%1$s)" - "Die verifizierte Identität von %1$s hat sich geändert." - "Die verifizierte Identität von %1$s\'s %2$s hat sich geändert. %3$s" + "Die Identität von %1$s hat sich geändert." + "Die Identität von %1$s\'s %2$s hat sich geändert. %3$s" "Verifizierung zurückziehen" "Der Link%1$s führt Sie zu einer anderen Seite%2$s. @@ -339,7 +345,7 @@ Möchten Sie wirklich fortfahren?" "Kannst du das nicht bestätigen? Gehe zu deinem Konto, um deine Identität zurückzusetzen." "Verifizierung zurückziehen und senden" "Du kannst deine Verifizierung zurückziehen und diese Nachricht trotzdem senden, oder du kannst sie vorerst abbrechen und es später noch einmal versuchen, nachdem du %1$s erneut verifiziert hast." - "Deine Nachricht wurde nicht gesendet, da sich die verifizierte Identität von %1$s geändert hat" + "Ihre Nachricht wurde nicht gesendet, da die verifizierte Identität von %1$s zurückgesetzt wurde" "Nachricht trotzdem senden" "%1$s verwendet wenigstens ein nicht verifiziertes Gerät. Du kannst die Nachricht trotzdem verschicken, oder vorerst abbrechen und später erneut versuchen, nachdem %2$s alle Geräte verifiziert hat." "Deine Nachricht wurde nicht gesendet, weil %1$s nicht alle Geräte verifiziert hat" diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index 1b792ea712..baff4d30c2 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -127,6 +127,7 @@ "Προβολή στο χρονοδιάγραμμα" "Προβολή πηγής" "Ναι" + "Ναι, δοκιμή ξανά" "Ο διακομιστής σου υποστηρίζει τώρα ένα νέο, ταχύτερο πρωτόκολλο. Αποσυνδέσου και συνδέσου ξανά για αναβάθμιση τώρα. Κάνοντας αυτό τώρα θα σε βοηθήσει να αποφύγεις μια αναγκαστική αποσύνδεση όταν το παλιό πρωτόκολλο καταργηθεί αργότερα." "Διαθέσιμη αναβάθμιση" "Σχετικά" @@ -252,7 +253,7 @@ "Δεν είναι δυνατή η αποκρυπτογράφηση" "Στάλθηκε από μια μη ασφαλής συσκευή" "Δεν έχεις πρόσβαση σε αυτό το μήνυμα" - "Η επαληθευμένη ταυτότητα του αποστολέα έχει αλλάξει" + "Η επαληθευμένη ταυτότητα του αποστολέα έχει επαναφερθεί" "Δεν ήταν δυνατή η αποστολή προσκλήσεων σε έναν ή περισσότερους χρήστες." "Δεν είναι δυνατή η αποστολή προσκλήσεων" "Ξεκλείδωμα" @@ -271,8 +272,8 @@ "Αναμονή…" "Αναμονή για αυτό το μήνυμα" "Εσύ" - "Η ταυτότητα του χρήστη %1$s φαίνεται να έχει αλλάξει. %2$s" - "Η ταυτότητα του %1$s %2$s φαίνεται να έχει αλλάξει. %3$s" + "Η ταυτότητα του χρήστη %1$s επαναφέρθηκε. %2$s" + "Η ταυτότητα του %1$s %2$s επαναφέρθηκε. %3$s" "(%1$s)" "Επιβεβαίωση" "Σφάλμα" @@ -319,7 +320,7 @@ "Δεν μπορείς να επιβεβαιώσεις; Πήγαινε στον λογαριασμό σου για να επαναφέρεις την ταυτότητά σου." "Ανάκληση επαλήθευσης και αποστολή" "Μπορείτε να ανακαλέσεις την επαλήθευσή σου και να στείλεις αυτό το μήνυμα όπως και να \'χει ή μπορείς να το ακυρώσεις προς το παρόν και να προσπαθήσεις ξανά αργότερα μετά την επαλήθευση του χρήστη %1$s." - "Το μήνυμά σου δεν στάλθηκε επειδή η επαληθευμένη ταυτότητα του χρήστη %1$s έχει αλλάξει" + "Το μήνυμά σου δεν στάλθηκε επειδή η επαληθευμένη ταυτότητα του χρήστη %1$s έχει επαναφερθεί" "Αποστολή μηνύματος ούτως ή άλλως" "Ο χρήστης %1$s χρησιμοποιεί τουλάχιστον μία μη επαληθευμένη συσκευή. Μπορείς να στείλεις το μήνυμα όπως και να \'χει ή μπορείς να το ακυρώσεις προς το παρόν και να δοκιμάσεις ξανά αργότερα αφού ο χρήστης %2$s επαληθεύσει όλες τις συσκευές του." "Το μήνυμά σου δεν στάλθηκε επειδή ο χρήστης %1$s δεν έχει επαληθεύσει όλες τις συσκευές" @@ -339,7 +340,7 @@ "Άνοιγμα στο Google Maps" "Άνοιγμα στο OpenStreetMap" "Κοινή χρήση αυτής της τοποθεσίας" - "Το μήνυμα δεν στάλθηκε επειδή η επαληθευμένη ταυτότητα του χρήστη %1$s έχει αλλάξει." + "Το μήνυμα δεν στάλθηκε γιατί έγινε επαναφορά της επαληθευμένης ταυτότητας του χρήστη %1$s." "Το μήνυμα δεν στάλθηκε επειδή ο χρήστης %1$s δεν έχει επαληθεύσει όλες τις συσκευές." "Το μήνυμα δεν στάλθηκε επειδή δεν έχεις επαληθεύσει τουλάχιστον μία από τις συσκευές σου." "Τοποθεσία" diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index e91e8a1ec4..df27c2e79d 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -1,16 +1,19 @@ + "Tunnuspilt" "Kustuta" "%1$d number sisestatud" "%1$d numbrit sisestatud" "Peida salasõna" + "Liitu kõnega" "Mine lõppu" "Ainult mainimised" "Summutatud" "%1$d. lehekülg" "Peata" + "Häälsõnum, kestus:%1$s, praegune asukoht: %2$s" "PIN-koodi väli" "Esita" "Küsitlus" @@ -30,6 +33,7 @@ "Helista" "Kasutajamenüü" "Vaata üksikasju" + "Häälsõnum, kestus:%1$s" "Salvesta häälsõnum." "Lõpeta salvestamine" "Nõustu" @@ -56,9 +60,11 @@ "Eemalda konto" "Eemalda konto kasutusest" "Keeldu" + "Keeldu ja blokeeri" "Kustuta küsitlus" "Lülita välja" "Loobu" + "Lõpeta" "Valmis" "Muuda" "Muuda selgitust" @@ -100,8 +106,10 @@ "Eemalda sõnum" "Vasta" "Vasta jutulõngas" + "Teata" "Teata veast" "Teata sisust haldurile" + "Teata jututoast" "Lähtesta" "Lähtesta oma identiteet" "Proovi uuesti" @@ -202,6 +210,7 @@ Põhjus: %1$s." "%1$s (%2$s)" "Otsingul pole tulemusi" "Jututoal puudub nimi" + "Krüptimata" "Võrgust väljas" "Avatud lähtekoodiga litsentsid" "või" @@ -210,6 +219,7 @@ Põhjus: %1$s." "Püsilink" "Õigus" "Esiletõstetud" + "Palun kontrolli oma nutiseadme internetiühendust" "Palun oota…" "Kas oled kindel, et soovid selle küsitluse lõpetada?" "Küsitlus: %1$s" @@ -222,6 +232,7 @@ Põhjus: %1$s." "Privaatsuspoliitika" "Reaktsioon" "Reaktsioonid" + "Põhjus" "Taastevõti" "Värskendame andmeid…" "Vastates kasutajale %1$s" @@ -263,7 +274,7 @@ Põhjus: %1$s." "Dekrüptimine ei olnud võimalik" "Saadetud ebaturvalisest seadmest" "Sul pole ligipääsu antud sõnumile" - "Saatja verifitseeritud identiteet on muutunud" + "Saatja verifitseeritud identiteet on lähtestatud" "Kutset polnud võimalik saata ühele või enamale kasutajale." "Kutse(te) saatmine ei õnnestunud" "Eemalda lukustus" @@ -283,16 +294,18 @@ Põhjus: %1$s." "Ootame…" "Ootame selle sõnumi dekrüptimisvõtit" "Sina" - "Kasutaja %1$s võrguidentiteet tundub olema muutunud. %2$s" - "Kasutaja %1$s %2$s võrguidentiteet tundub olema muutunud. %3$s" + "Kasutaja %1$s võrguidentiteet on lähtestatud. %2$s" + "Kasutaja %1$s %2$s võrguidentiteet on lähtestatud. %3$s" "(%1$s)" - "%1$s kasutaja verifitseeritud identiteet on muutunud." - "%1$s kasutaja (%2$s kasutajanimi) verifitseeritud identiteet on muutunud. %3$s" + "%1$s kasutaja verifitseeritud identiteet on lähtestatud." + "%1$s kasutaja (%2$s kasutajanimi) verifitseeritud identiteet on lähtestatud. %3$s" "Võta verifitseerimine tagasi" "%1$s link viib sind teise veebisaiti %2$s Kas sa oled kindel, et soovid jätkata?" "Palun kontrolli seda linki mõttega" + "Teatasid jututoast" + "Teatasid jututoast ja lahkusid sealt" "Kinnitus" "Viga" "Õnnestus" @@ -324,6 +337,10 @@ Kas sa oled kindel, et soovid jätkata?" "Hei, suhtle minuga %1$s võrgus: %2$s" "%1$s Android" "Veast teatamiseks raputa nutiseadet ägedalt" + "Sa ei näe enam selle kasutaja saadetud sõnumeid ja jututubade kutseid" + "Blokeeri kasutaja" + "Teata sellest jututoast oma teenusepakkujale." + "Keeldu ja blokeeri" "Meediafaili valimine ei õnnestunud. Palun proovi uuesti." "Selgitused ja alapealkirjad ei pruugi olla nähtavad vanemate rakenduste kasutajatele." "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." @@ -335,11 +352,16 @@ Kas sa oled kindel, et soovid jätkata?" "%1$d esiletõstetud sõnumit" "Esiletõstetud sõnumid" + "Jututoast haldajale teatamine õnnestus, kuid jututost lahkumisel tekkis viga. Palun proovi uuesti lahkuda." + "Pole võimalik lahkuda jututoast" + "Teata sellest jututoast süsteemi haldajale. Kui sõnumid on krüptitud, ei saa haldaja neid lugeda." + "Kirjelda põhjust…" + "Teata jututoast" "Oma võrguidentiteedi lähtestamiseks suuname sind %1$s kasutajakonto halduse lehele. Hiljem suunatakse sind tagasi sama rakenduse juurde." "Sa ei saa seda kinnitada? Ava oma kasutajakonto haldus ja lähtesta oma võrguidentiteet." "Unusta verifitseerimine ja saada ikkagi" "Sa võid jätta verifitseerimisvea tähelepanuta ja sõnumi ikkagi saata või katkestad saatmise ja peale kasutaja %1$s verifitseerimist proovid seda uuesti." - "Sinu sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on muutunud." + "Sinu sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on lähtestatud." "Saada sõnum ikkagi" "%1$s kasutab ühte või enamat verifitseerimata seadet. Sa võid sõnumi ikkagi saata või katkestad selle ning ootad kuni %2$s on kõik oma seadmed verifitseerinud ning proovid seejärel uuesti." "Sinu sõnum on saatmata, kuna %1$s pole verifitseerinud kõiki oma seadmeid" @@ -360,7 +382,7 @@ Kas sa oled kindel, et soovid jätkata?" "Ava Google Mapsis" "Ava OpenStreetMapis" "Jaga seda asukohta" - "Sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on muutunud." + "Sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on lähtestatud." "Sõnum on saatmata, kuna %1$s pole verifitseerinud kõiki oma seadmeid." "Kuna sa pole üks või enamgi oma seadet verifitseerinud, siis sinu sõnum on saatmata." "Asukoht" diff --git a/libraries/ui-strings/src/main/res/values-eu/translations.xml b/libraries/ui-strings/src/main/res/values-eu/translations.xml index 2cd454d5a7..5311d9bd59 100644 --- a/libraries/ui-strings/src/main/res/values-eu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-eu/translations.xml @@ -178,6 +178,7 @@ Arrazoia: %1$s." "Matrix IDa ezin da topatu eta, beraz, litekeena da gonbidapena ez jasotzea." "Gelatik ateratzen" "Argia" + "Lerroa arbelean kopiatu da" "Esteka arbelean kopiatu da" "Kargatzen…" "Gehiago kargatzen…" @@ -186,12 +187,14 @@ Arrazoia: %1$s." "%1$d kide" "Mezua" + "Mezuen ekintzak" "Mezuen antolaketa" "Mezua kendu da" "Modernoa" "Mututu" "Emaitzarik ez" "Gelak ez du izenik" + "Zifratu gabe" "Deskonektatuta" "Kode irekiko lizentziak" "edo" @@ -315,4 +318,6 @@ Arrazoia: %1$s." "Kokapena" "Bertsioa: %1$s (%2$s)" "eu" + "Iraganeko mezuak ez daude gailu honetan eskuragarri" + "Ezin da mezua deszifratu" 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 d6cb2dc086..fe664422b3 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -1,16 +1,19 @@ + "Avatar" "Supprimer" "%1$d chiffre saisi" "%1$d chiffres saisis" "Masquer le mot de passe" + "Rejoindre l’appel" "Retourner à la fin de la conversation" "Mentions uniquement" "En sourdine" "Page %1$d" "Pause" + "Message vocal, durée: %1$s, position actuelle: %2$s" "Code PIN" "Lecture" "Sondage" @@ -30,6 +33,7 @@ "Démarrer un appel" "Menu utilisateur" "Afficher les détails" + "Message vocal, durée: %1$s" "Enregistrer un message vocal." "Arrêter l’enregistrement" "Accepter" 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 6967eab8ca..9ab1c58d37 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -1,16 +1,19 @@ + "Profilkép" "Törlés" "%1$d megadott számjegy" "%1$d megadott számjegy" "Jelszó elrejtése" + "Csatlakozás a híváshoz" "Ugrás az aljára" "Csak megemlítések" "Némítva" "%1$d. oldal" "Szüneteltetés" + "Hangüzenet, időtartam:%1$s, jelenlegi pozíció:%2$s" "PIN-mező" "Lejátszás" "Szavazás" @@ -30,6 +33,7 @@ "Hanghívás indítása" "Felhasználói menü" "Részletek megtekintése" + "Hangüzenet, időtartam: %1$s" "Hangüzenet felvétele." "Rögzítés leállítása" "Elfogadás" @@ -224,6 +228,7 @@ Ok: %1$s." "Adatvédelmi nyilatkozat" "Reakció" "Reakciók" + "Ok" "Helyreállítási kulcs" "Frissítés…" "Válasz %1$s számára" diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index f624fd4856..d811f5499c 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -248,7 +248,7 @@ Alasan: %1$s." "Tidak dapat mendekripsi" "Dikirim dari perangkat yang tidak aman" "Anda tidak memiliki akses ke pesan ini" - "Identitas terverifikasi pengirim telah berubah" + "Identitas terverifikasi pengirim diatur ulang" "Undangan tidak dapat dikirim ke satu atau beberapa pengguna." "Tidak dapat mengirim undangan" "Buka kunci" @@ -267,8 +267,8 @@ Alasan: %1$s." "Menunggu…" "Menunggu pesan ini" "Anda" - "Identitas %1$s tampaknya telah berubah. %2$s" - "Identitas %1$s yang %2$s tampaknya telah berubah. %3$s" + "Identitas %1$s telah diatur ulang. %2$s" + "Identitas %2$s %1$s telah diatur ulang. %3$s" "(%1$s)" "Konfirmasi" "Eror" @@ -314,7 +314,7 @@ Alasan: %1$s." "Tidak dapat mengonfirmasi? Buka akun Anda untuk mengatur ulang identitas Anda." "Tarik verifikasi dan kirim" "Anda dapat menarik verifikasi dan tetap mengirim pesan ini, atau Anda dapat membatalkan untuk saat ini dan mencoba lagi nanti setelah memverifikasi ulang %1$s." - "Pesan Anda tidak terkirim karena identitas terverifikasi %1$s telah berubah" + "Pesan Anda tidak terkirim karena identitas terverifikasi %1$s telah diatur ulang" "Kirim pesan saja" "%1$s menggunakan satu atau beberapa perangkat yang belum diverifikasi. Anda tetap dapat mengirim pesan, atau Anda dapat membatalkan untuk saat ini dan mencoba lagi nanti setelah %2$s telah memverifikasi semua perangkat mereka." "Pesan Anda tidak terkirim karena %1$s belum memverifikasi semua perangkat" @@ -334,7 +334,7 @@ Alasan: %1$s." "Buka di Google Maps" "Buka di OpenStreetMap" "Bagikan lokasi ini" - "Pesan tidak terkirim karena identitas terverifikasi %1$s telah berubah." + "Pesan tidak terkirim karena identitas terverifikasi %1$s telah diatur ulang." "Pesan tidak terkirim karena %1$s belum memverifikasi semua perangkat." "Pesan tidak terkirim karena Anda belum memverifikasi satu atau beberapa perangkat Anda." "Lokasi" 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 933bb0c32d..bf11478b9e 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -59,6 +59,7 @@ "Elimina sondaggio" "Disabilita" "Annulla" + "Chiudi" "Fine" "Modifica" "Modifica didascalia" @@ -145,7 +146,9 @@ "Copiato negli appunti" "Copyright" "Creazione stanza…" + "Richiesta annullata" "Hai lasciato la stanza" + "Invito rifiutato" "Scuro" "Errore di decrittazione" "Opzioni sviluppatore" @@ -158,6 +161,7 @@ "Modifica in corso" "Modifica didascalia" "* %1$s %2$s" + "File vuoto" "Crittografia" "Crittografia abilitata" "Inserisci il PIN" @@ -182,6 +186,7 @@ Motivo:. %1$s" "Questo ID Matrix non può essere trovato, quindi l\'invito potrebbe non essere ricevuto." "Lascio la stanza" "Chiaro" + "Riga copiata negli appunti" "Collegamento copiato negli appunti" "Caricamento…" "Caricamento in corso…" @@ -198,6 +203,7 @@ Motivo:. %1$s" "%1$s (%2$s)" "Nessun risultato" "Nessun nome della stanza" + "Non cifrata" "Non in linea" "Licenze open source" "o" @@ -259,7 +265,7 @@ Motivo:. %1$s" "Impossibile decrittografare" "Inviato da un dispositivo non sicuro" "Non hai accesso a questo messaggio" - "L\'identità verificata del mittente è cambiata" + "L\'identità verificata del mittente è stata reimpostata" "Non è stato possibile spedire inviti a uno o più utenti." "Impossibile inviare inviti" "Sblocca" @@ -279,12 +285,16 @@ Motivo:. %1$s" "In attesa…" "In attesa del messaggio" "Tu" - "L\'identità di %1$s sembra essere cambiata. %2$s" - "L\'identità di %1$s %2$s sembra essere cambiata. %3$s" + "L\'identità di %1$s è stata reimpostata. %2$s" + "L\'identità %2$s di %1$s sembra essere cambiata. %3$s" "(%1$s)" - "L\'identità verificata di %1$s è cambiata." - "L\'identità %2$s verificata di %1$s è cambiata. %3$s" + "L\'identità di %1$s è stata reimpostata." + "L\'identità %2$s di %1$s è stata reimpostata. %3$s" "Ritira verifica" + "Il link %1$s ti porta ad un altro sito %2$s + +Sei sicuro di voler continuare?" + "Ricontrolla questo link" "Conferma" "Errore" "Operazione riuscita" @@ -331,7 +341,7 @@ Motivo:. %1$s" "Non riesci a confermare? Vai al tuo account per ripristinare la tua identità." "Ritira la verifica e invia" "Puoi ritirare la tua verifica e inviare comunque questo messaggio, oppure annullarlo per ora e riprovare più tardi dopo aver riverificato %1$s." - "Il tuo messaggio non è stato inviato perché l\'identità verificata di %1$s è cambiata." + "Il tuo messaggio non è stato inviato perché l\'identità verificata di %1$s è stata reimpostata." "Invia comunque il messaggio" "%1$s sta usando uno o più dispositivi non verificati. Puoi inviare il messaggio in ogni caso, oppure annullarlo e riprovare più tardi quando %2$s avrà verificato tutti i suoi dispositivi." "Il tuo messaggio non è stato inviato perché %1$s non ha verificato tutti i dispositivi." @@ -352,7 +362,7 @@ Motivo:. %1$s" "Apri in Google Maps" "Apri in OpenStreetMap" "Condividi questa posizione" - "Messaggio non inviato perché l\'identità verificata di %1$s è cambiata." + "Messaggio non inviato perché l\'identità verificata di %1$s è stata reimpostata." "Messaggio non inviato perché %1$s non ha verificato tutti i dispositivi." "Messaggio non inviato perché non hai verificato uno o più dispositivi." "Posizione" diff --git a/libraries/ui-strings/src/main/res/values-nb/translations.xml b/libraries/ui-strings/src/main/res/values-nb/translations.xml index 7d4e65b5ac..a059b9fe51 100644 --- a/libraries/ui-strings/src/main/res/values-nb/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nb/translations.xml @@ -300,7 +300,9 @@ "🔐️ Bli med meg på %1$s" "Hei, snakk med meg på %1$s: %2$s" "%1$s Android" + "Rageshake for å rapportere feil" "Kunne ikke velge medium, prøv igjen." + "Teksting er kanskje ikke synlig for personer som bruker eldre apper." "Kunne ikke behandle medier for opplasting, vennligst prøv igjen." "Opplasting av medier mislyktes, vennligst prøv igjen." "Fest viktige meldinger slik at de lett kan ses" diff --git a/libraries/ui-strings/src/main/res/values-pl/translations.xml b/libraries/ui-strings/src/main/res/values-pl/translations.xml index d4087c537e..eccfbbdb2f 100644 --- a/libraries/ui-strings/src/main/res/values-pl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pl/translations.xml @@ -147,7 +147,9 @@ "Skopiowano do schowka" "Prawa autorskie" "Tworzenie pokoju…" + "Anulowano żądanie" "Opuszczono pokój" + "Odrzucono zaproszenie" "Ciemny" "Błąd deszyfrowania" "Opcje programisty" @@ -160,6 +162,7 @@ "Edytowanie" "Edytowanie opisu" "* %1$s %2$s" + "Pusty plik" "Szyfrowanie" "Szyfrowanie włączone" "Wprowadź kod PIN" @@ -184,6 +187,7 @@ Powód: %1$s." "Nie można znaleźć identyfikatora Matrix ID, zaproszenie mogło nie dotrzeć." "Opuszczanie pokoju" "Jasny" + "Wiersz skopiowany do schowka" "Link został skopiowany do schowka" "Ładowanie…" "Ładuję więcej…" @@ -289,6 +293,10 @@ Powód: %1$s." "Zweryfikowana tożsamość %1$s uległa zmianie" "Zweryfikowana tożsamość %1$s %2$s uległa zmianie. %3$s" "Wycofaj weryfikację" + "Link %1$s prowadzi Cię do innej witryny %2$s + +Czy na pewno chcesz kontynuować?" + "Sprawdź dwukrotnie ten link" "Potwierdzenie" "Błąd" "Sukces" 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 e80a688803..3a768dffc4 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -1,5 +1,6 @@ + "Obrázok" "Vymazať" "%1$d zadaná číslica" @@ -7,11 +8,13 @@ "%1$d zadaných číslic" "Skryť heslo" + "Pripojiť sa k hovoru" "Prejsť na spodok" "Iba zmienky" "Stlmené" "Strana %1$d" "Pozastaviť" + "Hlasová správa, dĺžka:%1$s, aktuálna pozícia: %2$s" "Pole PIN" "Prehrať" "Anketa" @@ -32,6 +35,7 @@ "Začať hovor" "Používateľské menu" "Zobraziť podrobnosti" + "Hlasová správa, dĺžka: %1$s" "Nahrať hlasovú správu." "Zastaviť nahrávanie" "Prijať" @@ -61,6 +65,7 @@ "Odstrániť anketu" "Vypnúť" "Zahodiť" + "Zamietnuť" "Hotovo" "Upraviť" "Upraviť titulok" @@ -205,6 +210,7 @@ Dôvod: %1$s." "%1$s (%2$s)" "Žiadne výsledky" "Žiadny názov miestnosti" + "Nešifrované" "Offline" "Licencie s otvoreným zdrojom" "alebo" @@ -226,6 +232,7 @@ Dôvod: %1$s." "Zásady ochrany osobných údajov" "Reakcia" "Reakcie" + "Dôvod" "Kľúč na obnovenie" "Obnovuje sa…" "Odpoveď na %1$s" @@ -267,7 +274,7 @@ Dôvod: %1$s." "Nie je možné dešifrovať" "Odoslané z nezabezpečeného zariadenia" "Nemáte prístup k tejto správe" - "Overená totožnosť odosielateľa sa zmenila" + "Overená totožnosť odosielateľa bola obnovená" "Pozvánky nebolo možné odoslať jednému alebo viacerým používateľom." "Nie je možné odoslať pozvánku/ky" "Odomknúť" @@ -287,11 +294,11 @@ Dôvod: %1$s." "Čaká sa…" "Čaká sa na dešifrovací kľúč" "Vy" - "Zdá sa, že totožnosť používateľa %1$s sa zmenila.%2$s" - "Zdá sa, že identita %2$s používateľa %1$s sa zmenila. %3$s" + "Totožnosť používateľa %1$s sa obnovila.%2$s" + "Totožnosť používateľa %1$s %2$s bola obnovená. %3$s" "(%1$s)" - "Overená totožnosť používateľa %1$s sa zmenila." - "Overená identita používateľa %1$s %2$s sa zmenila. %3$s" + "Totožnosť používateľa %1$s bola obnovená." + "Totožnosť používateľa %1$s %2$s bola obnovená. %3$s" "Zrušiť overenie" "Odkaz %1$s vás presmeruje na inú stránku %2$s diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index c94c028884..abf33b622a 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -1,16 +1,19 @@ + "Avatar" "Radera" "%1$d siffra angiven" "%1$d siffror angivna" "Dölj lösenord" + "Anslut till samtal" "Hoppa till botten" "Endast omnämningar" "Tystad" "Sida %1$d" "Pausa" + "Röstmeddelande, varaktighet:%1$s, nuvarande position: %2$s" "PIN-fält" "Spela upp" "Omröstning" @@ -30,6 +33,7 @@ "Starta ett samtal" "Användarmeny" "Visa detaljer" + "Röstmeddelande, varaktighet: %1$s" "Spela in röstmeddelande." "Stoppa inspelning" "Godkänn" @@ -101,8 +105,10 @@ "Ta bort meddelande" "Svara" "Svara i tråd" + "Anmäl" "Rapportera bugg" "Rapportera innehåll" + "Anmäl rum" "Återställ" "Återställ identitet" "Försök igen" @@ -224,6 +230,7 @@ Anledning:%1$s." "Integritetspolicy" "Reaktion" "Reaktioner" + "Orsak" "Återställningsnyckel" "Uppdaterar …" "Svarar till %1$s" @@ -295,6 +302,8 @@ Anledning:%1$s." Är du säker på att du vill fortsätta?" "Dubbelkolla den här länken" + "Rum anmält" + "Anmälde och lämnade rummet" "Bekräftelse" "Fel" "Lyckades" @@ -337,6 +346,10 @@ Anledning:%1$s." "%1$d Fästa meddelanden" "Fästa meddelanden" + "Kunde inte lämna rummet" + "Anmäl det här rummet till din administratör. Om meddelandena är krypterade kommer din administratör inte att kunna läsa dem." + "Beskriv anledningen …" + "Rapportera rum" "Du är på väg att gå till ditt %1$s-konto för att återställa din identitet. Därefter kommer du att tas tillbaka till appen." "Kan du inte bekräfta? Gå till ditt konto för att återställa din identitet." "Dra tillbaka verifieringen och skicka" diff --git a/libraries/ui-strings/src/main/res/values-uk/translations.xml b/libraries/ui-strings/src/main/res/values-uk/translations.xml index cc05a70f5d..d909d993be 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -61,6 +61,7 @@ "Видалити опитування" "Вимкнути" "Відкинути" + "Відхилити" "Готово" "Редагувати" "Редагувати підпис" @@ -162,6 +163,7 @@ "Редагування" "Редагування підпису" "* %1$s %2$s" + "Порожній файл" "Шифрування" "Шифрування ввімкнено" "Введіть свій PIN-код" @@ -186,6 +188,7 @@ "Цей Matrix-ID не знайдено, тому запрошення може не бути отримано." "Вихід з кімнати" "Світла" + "Рядок скопійовано до буфера обміну" "Посилання скопійовано в буфер обміну" "Завантаження" "Завантаження наступних…" @@ -203,6 +206,7 @@ "%1$s (%2$s)" "Немає результатів" "Немає назви кімнати" + "Не зашифровано" "Не в мережі" "Ліцензії відкритого коду" "або" @@ -265,7 +269,7 @@ "Неможливо розшифрувати" "Надіслано з незахищеного пристрою" "Ви не маєте доступу до цього повідомлення" - "Підтверджена особа відправника змінилася" + "Ідентичність відправника скинуто" "Не вдалося надіслати запрошення одному чи кільком користувачам." "Не вдалося надіслати запрошення" "Розблокувати" @@ -285,11 +289,11 @@ "Очікування…" "Чекаємо на це повідомлення" "Ви" - "Ідентичність %1$s, схоже, змінилася. %2$s" - "Ідентичність %1$s %2$s схоже, змінилася. %3$s" + "Ідентичність %1$s скинуто. %2$s" + "Ідентичність %1$s %2$s скинуто. %3$s" "(%1$s)" - "Верифікована ідентичність %1$s змінилася." - "Верифіковано особистість %1$s %2$s змінилася. %3$s" + "Ідентичність %1$s скинуто." + "Ідентичність %1$s %2$s скинуто. %3$s" "Відкликати верифікацію" "Посилання %1$s спрямовує вас на інший сайт %2$s @@ -342,7 +346,7 @@ "Не можете підтвердити? Перейдіть до свого облікового запису, щоб скинути облікові дані." "Відкликати верифікацію та відправити" "Ви все одно можете відкликати підтвердження та надіслати це повідомлення, або ви можете скасувати підписку на даний момент і спробувати пізніше після повторної перевірки %1$s." - "Ваше повідомлення не було надіслано, оскільки підтверджена особистість %1$s змінилася" + "Ваше повідомлення не надіслано, оскільки підтверджену особистість %1$s скинуто" "Надіслати повідомлення в будь-якому випадку" "%1$s використовує один або кілька неперевірених пристроїв. Ви можете відправити повідомлення в будь-якому випадку, або ж скасувати відправку і спробувати пізніше, коли %2$s перевірить всі пристрої." "Ваше повідомлення не було надіслано, тому що %1$s не перевірив усі пристрої" @@ -363,7 +367,7 @@ "Відкрити в Картах Google" "Відкрити в OpenStreetMap" "Поділитися цим місцем перебування" - "Повідомлення не надіслано, оскільки підтверджена особистість %1$s змінилася." + "Повідомлення не надіслано, оскільки підтверджену особистість %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 8e387ead44..a4121570f0 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -1,16 +1,19 @@ + "Avatar" "Delete" "%1$d digit entered" "%1$d digits entered" "Hide password" + "Join call" "Jump to bottom" "Mentions only" "Muted" "Page %1$d" "Pause" + "Voice message, duration: %1$s, current position: %2$s" "PIN field" "Play" "Poll" @@ -30,6 +33,7 @@ "Start a call" "User menu" "View details" + "Voice message, duration: %1$s" "Record voice message." "Stop recording" "Accept" @@ -56,6 +60,7 @@ "Deactivate" "Deactivate account" "Decline" + "Decline and block" "Delete Poll" "Disable" "Discard" @@ -101,8 +106,10 @@ "Remove message" "Reply" "Reply in thread" + "Report" "Report bug" "Report content" + "Report room" "Reset" "Reset identity" "Retry" @@ -212,6 +219,7 @@ Reason: %1$s." "Permalink" "Permission" "Pinned" + "Please check your internet connection" "Please wait…" "Are you sure you want to end this poll?" "Poll: %1$s" @@ -224,6 +232,7 @@ Reason: %1$s." "Privacy policy" "Reaction" "Reactions" + "Reason" "Recovery key" "Refreshing…" "Replying to %1$s" @@ -295,6 +304,8 @@ Reason: %1$s." Are you sure you want to continue?" "Double-check this link" + "Room reported" + "Reported and left room" "Confirmation" "Error" "Success" @@ -326,6 +337,10 @@ Are you sure you want to continue?" "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" + "You will not see any messages or room invites from this user" + "Block user" + "Report this room to your account provider." + "Decline and block" "Failed selecting media, please try again." "Captions might not be visible to people using older apps." "Failed processing media to upload, please try again." @@ -337,6 +352,11 @@ Are you sure you want to continue?" "%1$d Pinned messages" "Pinned messages" + "Your report was submitted successfully, but we encountered an issue while trying to leave the room. Please try again." + "Unable to Leave Room" + "Report this room to your admin. If the messages are encrypted, your admin will not be able to read them." + "Describe the reason…" + "Report room" "You\'re about to go to your %1$s account to reset your identity. Afterwards you\'ll be taken back to the app." "Can\'t confirm? Go to your account to reset your identity." "Withdraw verification and send" diff --git a/libraries/ui-utils/build.gradle.kts b/libraries/ui-utils/build.gradle.kts index ec75f6627b..40530645dc 100644 --- a/libraries/ui-utils/build.gradle.kts +++ b/libraries/ui-utils/build.gradle.kts @@ -6,7 +6,7 @@ */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt new file mode 100644 index 0000000000..580807f987 --- /dev/null +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.ui.utils.time + +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.key + +/** + * Extension property to get the digit character from a KeyEvent. + * This handles both regular digit keys and numpad keys. + */ +val KeyEvent.digit: Char? get() { + val char = nativeKeyEvent.unicodeChar.toChar() + return when { + Character.isDigit(char) -> char + key == Key.NumPad0 -> '0' + key == Key.NumPad1 -> '1' + key == Key.NumPad2 -> '2' + key == Key.NumPad3 -> '3' + key == Key.NumPad4 -> '4' + key == Key.NumPad5 -> '5' + key == Key.NumPad6 -> '6' + key == Key.NumPad7 -> '7' + key == Key.NumPad8 -> '8' + key == Key.NumPad9 -> '9' + else -> null + } +} diff --git a/plugins/src/main/kotlin/ModulesConfig.kt b/plugins/src/main/kotlin/ModulesConfig.kt index 323fbcf9ab..7575728c96 100644 --- a/plugins/src/main/kotlin/ModulesConfig.kt +++ b/plugins/src/main/kotlin/ModulesConfig.kt @@ -6,6 +6,7 @@ */ import config.AnalyticsConfig +import config.BuildTimeConfig import config.PushProvidersConfig object ModulesConfig { @@ -14,8 +15,27 @@ object ModulesConfig { includeUnifiedPush = true, ) - val analyticsConfig: AnalyticsConfig = AnalyticsConfig.Enabled( - withPosthog = true, - withSentry = true, - ) + val analyticsConfig: AnalyticsConfig = if (isEnterpriseBuild) { + // Is Posthog configuration available? + val withPosthog = BuildTimeConfig.SERVICES_POSTHOG_APIKEY.isNullOrEmpty().not() && + BuildTimeConfig.SERVICES_POSTHOG_HOST.isNullOrEmpty().not() + // Is Sentry configuration available? + val withSentry = BuildTimeConfig.SERVICES_SENTRY_DSN.isNullOrEmpty().not() + if (withPosthog || withSentry) { + println("Analytics enabled with Posthog: $withPosthog, Sentry: $withSentry") + AnalyticsConfig.Enabled( + withPosthog = withPosthog, + withSentry = withSentry, + ) + } else { + println("Analytics disabled") + AnalyticsConfig.Disabled + } + } else { + println("Analytics enabled with Posthog and Sentry") + AnalyticsConfig.Enabled( + withPosthog = true, + withSentry = true, + ) + } } diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 0e732137cf..b2c69fc1a3 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -29,10 +29,10 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion */ private const val versionYear = 25 -private const val versionMonth = 3 +private const val versionMonth = 4 // Note: must be in [0,99] -private const val versionReleaseNumber = 4 +private const val versionReleaseNumber = 0 object Versions { const val VERSION_CODE = (2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber diff --git a/plugins/src/main/kotlin/config/BuildTimeConfig.kt b/plugins/src/main/kotlin/config/BuildTimeConfig.kt index bac0c94d0b..e1fd78c4bb 100644 --- a/plugins/src/main/kotlin/config/BuildTimeConfig.kt +++ b/plugins/src/main/kotlin/config/BuildTimeConfig.kt @@ -14,7 +14,21 @@ object BuildTimeConfig { const val GOOGLE_APP_ID_DEBUG = "1:912726360885:android:def0a4e454042e9b00427c" const val GOOGLE_APP_ID_NIGHTLY = "1:912726360885:android:e17435e0beb0303000427c" + val METADATA_HOST: String? = null + val URL_WEBSITE: String? = null + val URL_LOGO: String? = null + val URL_COPYRIGHT: String? = null + val URL_ACCEPTABLE_USE: String? = null + val URL_PRIVACY: String? = null + val URL_POLICY: String? = null + val SUPPORT_EMAIL_ADDRESS: String? = null + val SERVICES_MAPTILER_BASE_URL: String? = null val SERVICES_MAPTILER_APIKEY: String? = null val SERVICES_MAPTILER_LIGHT_MAPID: String? = null val SERVICES_MAPTILER_DARK_MAPID: String? = null + val SERVICES_POSTHOG_HOST: String? = null + val SERVICES_POSTHOG_APIKEY: String? = null + val SERVICES_SENTRY_DSN: String? = null + val BUG_REPORT_URL: String? = null + val BUG_REPORT_APP_NAME: String? = null } diff --git a/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt b/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt new file mode 100644 index 0000000000..a7589f02a9 --- /dev/null +++ b/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package extension + +import com.android.build.api.dsl.VariantDimension + +fun VariantDimension.buildConfigFieldStr( + name: String, + value: String, +) { + buildConfigField( + type = "String", + name = name, + value = "\"$value\"" + ) +} + +fun VariantDimension.buildConfigFieldBoolean( + name: String, + value: Boolean, +) { + buildConfigField( + type = "boolean", + name = name, + value = value.toString() + ) +} diff --git a/screenshots/de/features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_de.png b/screenshots/de/features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_de.png new file mode 100644 index 0000000000..1b639af6f1 --- /dev/null +++ b/screenshots/de/features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f94d7539ded3cd2494371dd8e7df89036946157c31c7cdbf5d646ea901942733 +size 20796 diff --git a/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png b/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png new file mode 100644 index 0000000000..8bcae86780 --- /dev/null +++ b/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5a6d3e1fafa9aad0e21c4875eeb12daa69a141fcfee07c4650b1702adbaec0a +size 83723 diff --git a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png index 44373b5952..9f16ca802b 100644 --- a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png +++ b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2f621af124e1e4b9e843da434787e2cb27f2b4190a19f6da430924cc1e52a1c -size 29963 +oid sha256:9fb2f05e2eea4d25a5bb87f3fc6391df2bf0923967d6ad973152557517fa7819 +size 29864 diff --git a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png index 6481e55289..725423f7ad 100644 --- a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png +++ b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7f81a4ca9cdf84e23ac4030c1de78ad5337303d6f532a49c29a0463cda0a34c -size 31164 +oid sha256:c5c32861bbae702e9f45263ed3ee710d450b4eb66f1919a4f11eb5ccce7a0354 +size 31120 diff --git a/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_de.png b/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_de.png index 39555bdd05..f006ac08d3 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb6855006f988e1a37a61067c1cb3525ec3e9b9b387cafd3d0253dd779923adc -size 25949 +oid sha256:a2a9e4c1bf333604bb0a1b17c83af340e7d17a4dd3c465e8a49394dd236c3b45 +size 25844 diff --git a/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_de.png b/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_de.png index 50087485c8..8a2a525f26 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb7c032b25761eab114463409a119ee61c4697b4ae69ef1c34b323f73fab083c -size 31136 +oid sha256:59eb255368d39bf029237c84977c84a616994193b5c1d1ed43db403bb63e289b +size 29959 diff --git a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png index 1571627d84..129987cf6d 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a56642579318271960d92b28d7037c123b70b1983d59351c6bd026a7c136188e -size 55001 +oid sha256:24abc4b97bde6956806e35a9afd12780371bb0071b1c23598e5f0a6abbb218ac +size 54991 diff --git a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png index cf5d80de9a..e8693e5f62 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8267d92b66f9e4aec78d904cd618d55143ecf931fbf6cc540f1776551280b099 -size 65857 +oid sha256:bb13812558aa295627e05908477108c8c1bc9cdb0fae5288ca668488cd49d536 +size 65667 diff --git a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png index e25284622c..ab80c73355 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:931ff76e4b1cea3e7a739bd2bdf0acd4ea3d30aa3b8e7a61118060daf879fad6 -size 68815 +oid sha256:927c67c23735b8eaae8873aaf8e62e0678f9a5418fbfcabf8ee436ee5785a647 +size 67317 diff --git a/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png b/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png index affefe4253..97457f6c23 100644 --- a/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a840bdc2209053422999babc51bbe56eb32bb6484601f426489f2939eadccaba -size 66992 +oid sha256:0cefe61188c6cad40a9f9ae28c28e5f7f82cbf40467d8f84fa03e8c96df3a02a +size 67095 diff --git a/screenshots/de/features.messages.impl.link_LinkView_Day_1_de.png b/screenshots/de/features.messages.impl.link_LinkView_Day_1_de.png new file mode 100644 index 0000000000..ac0cb24cc0 --- /dev/null +++ b/screenshots/de/features.messages.impl.link_LinkView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd7ba46af8c9846828dcd4711915044038201591853c714ed707266e51f19a92 +size 33770 diff --git a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png index f8dcf51960..3afb335f1b 100644 --- a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png +++ b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a35ec296f86fc93f9e7c7a6c02775435fa07bdc7637641e95bb514ee16559309 -size 42785 +oid sha256:f08c84f2f4e4334e1e3062f3971e2e9ef0211a9af6c76de7ec90f34a470d0119 +size 42777 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_de.png index 903928e368..7b5265b4b1 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bde168175cbe02263bc47ce77072a9a254c152127f05a4e0e51873da861372f1 -size 34072 +oid sha256:b63e95b0309690eb2907f5390d068dba3199e9f91de0693f2b2a1e1c387344e3 +size 34026 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png index 3504696996..c17fee2d09 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6a4905be0d15aa62408a7dd7f45ed15b1692be48db380b8df37b7f2853937b3 -size 35448 +oid sha256:82665bcfa8bac9fd29058bb093fe4cbfbd119a8b446f3104a78cbfca6df422d3 +size 35413 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png index 357feead03..757be6e7b1 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:882a66be42030bb5d9f2375ff7510f6e12efcb151a5b0b420a2a67b65ec69fa5 -size 11215 +oid sha256:e4c20415dd48053f55274361837c3b181d70c050c4a5b5fc3300fdcbe4114e65 +size 11201 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png index cc454d6d09..c1f2f42f5f 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d1ff60c4459b00fb1066d3ecfec6e3f01cbe4e0614969cb09ceeda0659ef51b -size 38018 +oid sha256:a45e8eb20c82ba49ff1c0a1343cc62f03e9b3185a9a9fa9baf01e42b20b729bf +size 37991 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png index fc1c6596d6..5ccb5b1d48 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f568ccc087c0df15189f28860ed589b2ec1e20e3c3f8992af87dbd586a60a672 -size 79346 +oid sha256:5b62274b645a661271cdd83dae6c55947d76f19aee580d701a096b46d625089e +size 79353 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png index 7496926c6e..a80ddaa421 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:267aa60c445389e85128ffb56f4e88002adc11ee3d998571239abed4c15a81a1 -size 151338 +oid sha256:b2a9305f6faaf67eaac59f762e21c77070fbb572561df1cf92ec8d6bca0d0dce +size 151343 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png index 2e7672d3d4..94127b5426 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:839f90abd145051b9f96eb8d01e006a40a52f2df86d240b349e306d54e437595 -size 157214 +oid sha256:8032ef23e046995a32eb8ac61a735fc0960da6b4904464c3dee027c3bd1b4f9b +size 157194 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png index 1b6a5bd47f..8c2d51c040 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b4578b4e8455cd1674906ca99a91b5ecb16e1273e4a9bd2f94efc706623cbc0 -size 150548 +oid sha256:0e7beb51d718e2a5e0532fbe8d6e2118f50e742194366da48df3dba6d6545d72 +size 150535 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png index 0af6cbcc34..5c9e4cd489 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71fdacdd0336fc10c1b15bb591a81440252fcc74794ad251b39d9b3f392f6e67 -size 152709 +oid sha256:d40a925f1672f1c1c6287b9b9b16c34564a5c40d56a28135fa84e1fec748e39b +size 152711 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png index afcf3fed5d..55e117e656 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5c83f84e0b4f4f4ad9134e0cb0f724c1537a1c10f6233c3a7a39f7e28fa4b55 -size 54517 +oid sha256:0db1de722615d9320694873a12af1bfa6347ffba937587c11248fde4e4845fbf +size 54499 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png b/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png index 6328c9eea4..a058128637 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8753f05cf2d0e44a29df0788e4c8e7074b1d622c060176b403738e0af6cd6d72 -size 37576 +oid sha256:696794f694b55ba1e21d0031b22d10537be836188d8eaa6ea1dfbad4be0ca616 +size 37566 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png index ad5be81f19..812a37e1a9 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:776fcb3c193baa33c8da6d204e0de2258d974a42071a2524d673d21425525f12 -size 50439 +oid sha256:9a6d76006cefee9da331704ebdd847efdd0f833569db0177a61d2115a43858ce +size 50431 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png index 1de2b75ef7..fc9527c1a6 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a30639b3fab635f8ae26afd7f59453eba8a88b39ddcfbb462fdd4ee69de86728 -size 51916 +oid sha256:a51aa3750c9174ced3bf8d50784fb64a1cc568ad81f271881adea87a753952ed +size 51907 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png index 7b9b856d61..03defa6d0f 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d26fc4ca3568a7c36c5cb35a60b1a5d0e7ad98c44060ae70c1adf3504815ce4 -size 63475 +oid sha256:a3b4a5e7816b5ea30e6d11142aec791b3099770302a082cf93462c33c57e8d25 +size 63467 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png index 44dec4d07b..880fdca99b 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45878b12e1d3ae1051082c443175e3fac64f560e941527fba008a4e7d269cd74 -size 48328 +oid sha256:60ddfc47e4f1065935045277669facc066160d86f2b74ef4b909bacbb3d08ae1 +size 48321 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png index 790f101cc7..178d082f00 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89babdeea68e6d0299a0d60b3a469ecd79a8325326d320e1fb41033f08632d8e -size 72085 +oid sha256:34d53f3e263791f2c432aefcd566d27fffb4034268f35e92333c932947c7f698 +size 72075 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png index 42182d9326..579bcf9954 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2b39abad7f689af11669f8076587ebdd097bf394b8b577c2ed9aca0104653fa -size 58065 +oid sha256:404480e7e7e5cddd60d1c097d8892520b51f70030c393b3a16bff3286f16fcf1 +size 58061 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png index 56dc4117a4..007d075c36 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7de1093816b10cc68eac1888bfcdba714183a0aae8d5bbab346e74f6b1cecdf2 -size 64376 +oid sha256:24e32b2c8207e0aaf37d21d82cf0c920e20a56e6a4ba01ade77115b6574cbc64 +size 64357 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png index faf47c830a..5c46087fe4 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa629bd30f052f74fc4ea848901c9d0e825f28e53c68bb91367c0325d412344f -size 71833 +oid sha256:f9c0079dbf4dffcf7ee49d66da6403d35e78768a375e04975c5a9a07bd637d4a +size 71828 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png index c447f5e31b..547f4aa714 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03befdea443eb71f166974cd3c6cc10216e887b9e30e8d5b5219a10911ede004 -size 70545 +oid sha256:58196ecddea174d5e941fac0766f40f04e64e7bb2cd82c1540537fb2e9f04bbe +size 70528 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png index e8a887a4f0..7ea447b8fb 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20b21ee5b51d910f15f7db4a928f4ca84db228916b40d232a6b645b6b561b560 -size 73607 +oid sha256:eb5472ae58f7f4eb4e278d2c93c4eeaad0df2721ec7811027f76bb3f602edbc3 +size 73590 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png index 2b89daf0d1..e728d86f6f 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:584aa114463a3c55bece015e959c519fdf779709ab72dc275cd85d7b62472501 -size 53428 +oid sha256:f74a7a73434450cf5d984a476433140a4857b8ee958498db528a2e32f3b23f56 +size 53423 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png index 97d7007038..6f776b670b 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:641cff9b10ab1ca8a084ad0b0382dd3b450a603ac7072c016d37c8d824114ad6 -size 57840 +oid sha256:ada5d53903f469cb0b0b981546826b6664ce3f983e5dcab74b7d99bc8d568cbc +size 57832 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png index d1ae1005cc..36b43e7db7 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03214da03b4aa5476f7e05a2d8dadd582abf3d5ee3bbf75a3eb97d427c5ff7e7 -size 57868 +oid sha256:092117db4970bb43f37aa04ae4ea3e91129bae8ab3ed05fd594fc3fb964bb317 +size 57860 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png index 450889e5fe..f2e38e02d8 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac69e0b114b9459700c35d9473b2bf7ce6f6cc53f4c3c07cab75a54ee5bdd72a -size 61187 +oid sha256:47eb746b2ab54a1228e213d4f6920105cb863afa3bc98be46ba7d5f1a186d8a7 +size 61180 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png index 89f61dc3ff..f4cbcfc1a5 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b226411cc571b61e289d6570c3e939c512c9a6626caaedc0ce5372c40dbe58d -size 60626 +oid sha256:73033c71b99d30498239bb98c959f27cc37d7217e4bf1c0fcf3f56665cfc8357 +size 60151 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_13_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_13_de.png index 78ad979650..3f21c36631 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_13_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f43aa779948dcaac20a5f7801418f95697ad0d2a113bfb41a07e03d479e01b1c -size 60514 +oid sha256:2029d42a8c696f597438c9dfa38639eadd1f10a00ee404ccb07bd17c9f480f4e +size 60078 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png index 36b7a6b0f5..1f6447f380 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:670d8a563187d4f5a0229873c14dc18ed788519eca4c13072e201a165d4ebe12 -size 57033 +oid sha256:ffce521170b76b53868a9b9a2b51216dce35bebe616acc097b2593977974184b +size 57024 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png index fbf673311f..b822f7d106 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79f7255d2b2022a4bcc1b71dbf0ee0973918a3970247b9806c8973ce45043cc4 -size 60414 +oid sha256:164f3bacc2ca1c35a93fe5ec1f8fb6356c5fca7507422ee6400f28cfb6556743 +size 60419 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png index 01091aed83..f0fc9507fe 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e616713ebba2e6b6baa3b9c050205c8a7f8165d4ccf85bf4ddb4873d2d9cdab5 -size 55788 +oid sha256:723700294f08916e6f448898ea4c7d633a59c9b2d007f6ba8281c73be39128a7 +size 55769 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png index 1ef37f5295..1c6f613b11 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:314db3286f8098503818cc45eeebc0c89987748f0f575cfaea465b430bb87c72 -size 55652 +oid sha256:1ed6c7840ad790fc808a6c51bebbfd5ebf0cc1735fa0dea5ce692d99ca0178a5 +size 55646 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png index 54b7d86aac..ff5440ae92 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a12f1f767b92975cefe0c1382980794c301517d151892d78be0f6dced876a605 -size 54191 +oid sha256:fa561a80f58818de91d0daa8fdb5b5324b1b94de7f2dc61d4568b51825142d93 +size 54183 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png index 6f1bf5f715..112279203a 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbffd005a99dac5e62b094d2994c2aabb883b8e5a231e64656caedad6b0c14a6 -size 59693 +oid sha256:a87cc7029191fb410f72868115f27ec4863e53b23f4ff29536a77a77608c19be +size 59689 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png index 538e936a19..a25c8f3724 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb1ee601ccdad413b35bd2fffe4fdef8a341b6ee19253f5a038c94f71dabc6cb -size 60311 +oid sha256:f6a19f900b395bc2f9f4b7eb1ebca10d4ed9d685fec3cb211f102345c345be1c +size 60303 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png index edea260cae..b032acd935 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:097c4ccc5256b3c479d6df8dc3b4bb033e87b7b23293a0c5043d94f16d63bc6f -size 47871 +oid sha256:62b2496a0b101ad4d9aa48b5bde9f70c2d18fbea3eef7b743a27383a42c9b32f +size 47866 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png index 735b7b6834..88ce7f8165 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:832fe4b9ed7b53c161374cc72438c49f254569b33d64ded5f661152976b0d883 -size 315128 +oid sha256:e71425e752a8ecd40579d23be5bb300ef1745e0d28436bfa6553af74590b1154 +size 318115 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png index 43fe6a5e8e..4478d30a0a 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6eae17fb9f4bc1dd4b1321e26901c2acf4e0ab4d43c006ffeab987705d180fb8 -size 310729 +oid sha256:3e35411d5bdd3070aa61621989dcd75bc4625103c83488a64dacaa32f56e5351 +size 319571 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png index b77eaee61d..706aac2785 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9dff767d267c198d1cdcdc79c313b7fd04d1d90436dd754fc0e07b69a2dddc5 -size 313727 +oid sha256:369c3358762a39c41ed62144ab2b5e15077c0cc35d860d409d3dc07ec91014be +size 316905 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png index 4261a03f76..b82143a424 100644 --- a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb661c897842a0eedb95e2460205eafe34038eca930a6096a6efdd3125cdbca6 -size 307939 +oid sha256:f1d640e2499bb791232c179a913cd07cc629254d975e02f5679811e929fbfd20 +size 311421 diff --git a/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png new file mode 100644 index 0000000000..4261a03f76 --- /dev/null +++ b/screenshots/de/features.onboarding.impl_OnBoardingView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb661c897842a0eedb95e2460205eafe34038eca930a6096a6efdd3125cdbca6 +size 307939 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_de.png index b9df84aae0..6950ee7b49 100644 --- a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_de.png +++ b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7ed8b8a8190f00ed028a901e23abaf6497b91718afd3f64af94e1d75e9562ff -size 9601 +oid sha256:607f0410ab4482574395083969389b4e7f02ed797a4ccfc182d1dcb1b3d346ae +size 31530 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_de.png index 46bcee2f62..b9df84aae0 100644 --- a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_de.png +++ b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8a335d4f61371a5ba7f609446ba93c80b53ce06ca47aa011ab89ef43d3cfce3 -size 9926 +oid sha256:d7ed8b8a8190f00ed028a901e23abaf6497b91718afd3f64af94e1d75e9562ff +size 9601 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_de.png new file mode 100644 index 0000000000..dd2ad2a206 --- /dev/null +++ b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79d235afe2b0a6aab372e445f89c0b4c79f7be0980b460c08ee7fb25275c55b7 +size 38145 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_de.png index e15d3745bb..46bcee2f62 100644 --- a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_de.png +++ b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc604387773fc50481303c325a57813f354ea14724a2f0ea2d46f059ef9e5204 -size 9294 +oid sha256:a8a335d4f61371a5ba7f609446ba93c80b53ce06ca47aa011ab89ef43d3cfce3 +size 9926 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_de.png deleted file mode 100644 index cb2b0d0502..0000000000 --- a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5da94309ef91e21e8058de89a135ab6efafa2c9c176df606f7cc62a9857122d -size 34612 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_de.png index f01a1fe8f3..e15d3745bb 100644 --- a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_de.png +++ b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:244eb76ef937bac78604faa38d8fbbbd1ce9ed7d51cf979f7533f7e23ef95471 -size 28531 +oid sha256:cc604387773fc50481303c325a57813f354ea14724a2f0ea2d46f059ef9e5204 +size 9294 diff --git a/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_de.png b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_de.png new file mode 100644 index 0000000000..f01a1fe8f3 --- /dev/null +++ b/screenshots/de/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:244eb76ef937bac78604faa38d8fbbbd1ce9ed7d51cf979f7533f7e23ef95471 +size 28531 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png index 1d3cfbf6d0..5e205681ad 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63fed56c9fc1d4553d39d7139b4bf22a5f68c0937bf38fef91b100ef36c6d47f -size 45891 +oid sha256:064fc91cbf9c394ecc415a0c407b198266c18eeed58e2211e77a99b2566c6b4c +size 45992 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png index 4c1688a458..b1b5c44bbe 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddc40eb87892667798159d8527dc92e63f4f50fc09da65f5b6cd94ed771f511e -size 44774 +oid sha256:1f287d2d55e3ed88bcfd7029e04850b9afb608bff4cdb307a2107ef3492263ea +size 44873 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png index 71823e69ae..b3f166f0f9 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f22d0630fba2b489cd76c2599617898b9b02599a14568c7d85e1dcf396c2233 -size 47034 +oid sha256:f9095463651ea679d8bf78366da02023071a802d551874c56b333cdd9870eb1d +size 47131 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png index 69d5e38955..3ca9500e98 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57dd92d89f9d9501dae6a2d86ea09aca8c90778d089ef12b1f5f8167e2e99fed -size 45123 +oid sha256:e0a2bae68fcaea24090d504a42bae48d4b540e0e7d8cc41b184645c432ff7f77 +size 45223 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png index 8774259edc..e065b4bf53 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a26db933f780f800524a73ceb44bde810144fade08b422f7f01d086ba52a78f4 -size 45632 +oid sha256:791a579b913e406287a37962145cf54c02d68dfc14c8d4a7ae864017de3e5629 +size 45737 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png index 064c2df5e7..fec0f1ac74 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96de74399c8eaa9ea09d1864a2b5984dd082004696300ad1ff59aa0a47bb85fa -size 46213 +oid sha256:c19498feaf3cf309a105b611531c13d596da8c0d33623c393b2a5dbf95c12c87 +size 46314 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png index d2627d7694..77e4be20af 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24e9b81b9bcd5f879d0bc47244a7b9938912d223e15f050dc503e871a4a192fe -size 45451 +oid sha256:157f747b71d355493b7255b22e88742be69df779b6d9a1df6acf01069adab820 +size 45553 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png index 5225709c8e..9a88fe6e2a 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68c3b3ce803a898c5121dc1c1eb19741dbb42d6dcfc4bed65aedf7ef91800124 -size 36350 +oid sha256:4f7f3f083b27f726c4ddf17a6ecffe21d8865c122d55e2879b8b1145438fe7b2 +size 36463 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png index b86aaac933..1e903b4df9 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5c3457ac233e54c18ebdf8baf0d331003be80fd342a6f91bd0257bb89f79a84 -size 38958 +oid sha256:ea2473f89bca4a8e4909b2cd3ded7919a68bce80a3f85373bd40581b6b532464 +size 39067 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png index 13dde4af94..55745b3f8d 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5edc6988867158bf6617bbf5988b0cc942b5d70eba1b4c70ef96b54712064117 -size 45369 +oid sha256:1f1c37582bb9b3f680b302f9dea4f27213b562bdac7210d7d51247abe5b931ba +size 45558 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png index 2b8fbd332a..3a762f83af 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c555ed0912499e49c304e04e907beeedd3772b70795e875416a8208a441403bf -size 44348 +oid sha256:0eeddb05018851f7a7bc0b61bbf83edb11f53dfc16c45df5698cffbf02e4ee7d +size 44441 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png index 4b6ec61cb4..6fa141d6c4 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c00dfa334b1cae6046910f4e372a57f04e1e093e21568b513bb004740dd95b9 -size 45082 +oid sha256:56875192f27f48df3d694effa375e7f703cc5ad820f2dd52b4c0cedea16771d5 +size 45201 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png index 0e8d76f4cb..d31c2858fb 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9340f8533b032a3aa342dfecc4af6593729782c09e1b2909f622d83318bc057f -size 46352 +oid sha256:088695f25c715eff4442f65b285471cbc6e6efbb051c47fad738135146a89e04 +size 46464 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png index 300da10027..fda497dde4 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e342cdbd1c5328b988d722f58cf8e4f456fa2d04647cb7206ae6d1abbcd5277c -size 45146 +oid sha256:e5c948a7593cb3347fb43030a3456a6f1aef4fec0384eb9caacad8349d17626c +size 45248 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png index 775105c877..fa72ce58af 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97e9cfc23cec7a266d2e4f5de78a9f26eb1f4aa5383ac0888e43445a39c6f124 -size 43929 +oid sha256:f51413349aa28bc6619a55b0ea9cbf36ad8f0f3ec6e30ba80f8f752eb2107668 +size 44033 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png index df75672834..d6af09b4c7 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:234d20bccc37f0d6a28a3dbb181088a4bab5b6a74ded1709285510b6ae7018ce -size 47053 +oid sha256:692d00e0a8597682bf414f91342ae2b7c9b68dc38944e77f783ef5e5776c2240 +size 47041 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png index 52164d70e7..bf9ed2a3cc 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53286513a6842be215198c9bc0996caa3d75d74d622b07c328c59733096e4f90 -size 45888 +oid sha256:c37ddb76e6d41c0b781569f482fad117ce48367847e0061972694030a9413085 +size 45882 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png index 1b6d35e2ae..52e4702c1b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3970140bb648ac70889dd165eebdef1cf609d89805af448f32665de6a7ab81a -size 47718 +oid sha256:292dcae768ff8534f82e6bbe075a8fb4b4b57718d10a0f93d4d45b84e72becfe +size 47714 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png index 3952181b5d..36499ecb66 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:149b76592b74cb2d2215e6de6604c43b6602ca55e037a041f938a69ba10f8d64 -size 46217 +oid sha256:b8f1513a55fa0d5b74657bcd224bac851151b2da10dd7f624cd92778e6ab0618 +size 46205 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png index 8adcf955d1..b6fd155e9a 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b34833ab2ff3b95885edbc113668409f27c410530ee27fe32f1d2d02ba473e22 -size 46721 +oid sha256:9d97e781bf08ff3c519d693ccd2dca3269706667a45ee06c866903b7a95a07da +size 46711 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png index 594027c0b6..8987195d54 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc5cbcf1c9d1e479b202f64e1bbc2969ec6b0d2c3e75c005f61b27c63a4b5c93 -size 47313 +oid sha256:5b7f88d28b9820258ebac7c375cdf4e64f9ad4a62c195ce4e7253722ced29d5e +size 47302 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png index 4a640ec48e..0fe04d6d9c 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f3cae332439b8608cb2a6610e8febc6c8fed5349c8e722e58a43a93d4467146 -size 46566 +oid sha256:6126c10ff145a34ccedf9c7179c7786af1b6820a1dc2c3684aa649883e4833ba +size 46555 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png index 4b2d354878..91e9215b3d 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8adb2a2795c647189bb9d6d0ebe8bc71e6f8869f11f13e80f6c075e3019a29cc -size 37394 +oid sha256:66d469f100cb1dc04dad3859c0e8e3f9bb50a98b11947a328424f9c11b581b45 +size 37400 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png index 0bb1e00462..8a32c9c66c 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfa82fd5e8cedd060cc45f5383e66f1826995d227e7df70b6254a288a9cc7035 -size 40130 +oid sha256:9c38ede826c8a472d00d6132be5e80760434774603afca44b61d4d7afcd68877 +size 40131 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png index 7cc4eafcd2..13dc66abec 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2ff6c7c3c3fa28bc6d4772179ea9011d6e8dad6fc3e8ca12de3210fa6398591 -size 46468 +oid sha256:86764932b950fb55ae017b870b79ab32e96cc3f88248c0fac5db8f618ca8b2b3 +size 46454 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png index 6ac7d80460..bcf1141910 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12c5d18e668bdc14dc48dc4654df23a9da1287cfa12fc77d192c3174de3b123d -size 45406 +oid sha256:c35256f5869fea4d42aa672f49f48dc073f179ead72004baa25e07b4420d5665 +size 45401 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png index 8b56afe5b6..f6e0a5a0ee 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7dfb941ee934e9b4085e63d47da08b1fdc0d3afbae6d9f39acbe4f120d13f161 -size 46251 +oid sha256:d7b2e6e6461b04a9476e57ccebf35f79de29bf7c43c5936fc7bd497f8c238ac2 +size 46243 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png index de77906f44..e093b35424 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0581402b82ade016a325f9995f2c57266bf5550cacd48fab946c37844c60eb31 -size 47538 +oid sha256:9e8262293955839ab0827b0d9bb04f7528e51513354a45bdd3738c601f5fcba8 +size 47542 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png index e954250c55..581cc59363 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b049e087d71fdcc8dcce50930354095a493d4246578147616865a35a4a96e65 -size 46316 +oid sha256:f3b57566b133fd7cd56b905470b744dc3d98e7a6112f3556b9490cb26c1c4092 +size 46305 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png index 3786a782fd..608967f681 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fc879b5aebe89c33300e31081c06376b144d8b555e6cae9c7702d814e24cab4 -size 44963 +oid sha256:a9df573a195b9e8475ac9f95e4aed49ff4946846683b3b7cf00a102b2f8311a9 +size 44951 diff --git a/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_29_de.png b/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_29_de.png index e5a2899c68..8cf571f814 100644 --- a/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_29_de.png +++ b/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_29_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0774bcd58a29a30201977cc738c7635637b20a633ec90b3add6dd63b2a02325c -size 25403 +oid sha256:5a8f4b3792370cc2a11d236d3d8a2b31fd326369a1682db0708428c37c1d2575 +size 22729 diff --git a/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_30_de.png b/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_30_de.png index 44479cb175..2f32a78319 100644 --- a/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_30_de.png +++ b/screenshots/de/features.roomlist.impl.components_RoomSummaryRow_Day_30_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5381f248d6731dd75627bd74df015af82f4cc0d15cfa9a84571ad2c83ee8d436 -size 16803 +oid sha256:dd5f51e5d8f363aefa9d77c893d371a29d5d6a53eae2ccc69e432fded1b95c9c +size 16441 diff --git a/screenshots/de/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_de.png b/screenshots/de/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_de.png index f2e0575f49..0347f23cbe 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3dc3fadf9738dee8425469ddaa0a142ae2933f91bbedbfadc0cf83a16a02297a -size 26786 +oid sha256:7fd007d20fc02d44d2230aa5471c2b7242cc3aff862025517a11a3ec2b8acfb8 +size 25057 diff --git a/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png b/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png index 97877dcb47..c80c1adae1 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3a07ae4167cf29cfe165d3acc04e64b0c614d81bc542d57ed1d7a3b68605342 -size 15442 +oid sha256:c317599d84833c06a9931e41c0f20eacae4ea86dd1aa47f44c365acf34842707 +size 15395 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png index 7aed4754d7..0fc6b92e29 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4e22f909849f9b237c9d26db4bd6fed0bf84da7db6086b8557a29e8736e06ff -size 26263 +oid sha256:4945f14720c561504ee460fb88294683881f96ec6038008806100a76f528a308 +size 26163 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png index fd4228a1d9..7fa7119dfe 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:582e49a18b7b0289467db2bf508714ee22a88ef559aa203d6359cba932244985 -size 24210 +oid sha256:c2fd05a1ff0319b83914fa09677e5347bfa73cb9926cfef0e935bb91f6ea8135 +size 24100 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png index 16c35f3b51..23370ab623 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:828b8226d3601606ec58f0fa0dc883c90ec5f21bfbdabe3fe458ab8817c8cd78 -size 25274 +oid sha256:d2592e29284d3324b3fc3cfca87ac6722ee12cf0a88724ad85a47347f6485e03 +size 25174 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png index 4fdc2e7aa2..9c009c53cd 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ca641bcf4aaa1e97893e97395edda4af40eb07bb94cc4998a512e158717c753 -size 45161 +oid sha256:80af780db60b2804086e405cc33a3a05d511ec9cc9582b930d1ea4828467d153 +size 45134 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png index 71aae98e23..cafaf6d7fe 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cde506275d63f7b078fad1b30799fda8f73ed6fadc44a8444f38c773ec74eeb -size 37356 +oid sha256:d4efaf4cfae0738d3ea3e6640e4fa0b2f6ccfd4b5c5a48505571e8442f63765d +size 37323 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png index 9bb477e78d..83c27f4994 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20f32362f398663698fd1d4fe27b0d6104cbdaa8c43b4d971ce9742561573efd -size 23295 +oid sha256:52ec49829640d5dc45f5b3f57d0bf8ecefcca2b4423d43c13718fb9ee3988f04 +size 23206 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png index 2494e35600..2d54ddb4c0 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dba4059e750861096ba06be122b8c0b9e260de5349708223d323b9d45f356b79 -size 25720 +oid sha256:d0b871a866f4ad090f4559ef636e709b332dbfdbe002caaa6fb162d6edde63c7 +size 25680 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png index 394a306cd1..0f162ab3ea 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c14ca5d1fc33c203fa2b37d9c332bde076d55ae2695ff66463dc885a87b08ad6 -size 27362 +oid sha256:9bc59d332e03ec30c2400420d20a0dda23face75ac40462282f36ecf0042128d +size 27262 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png index a26006e652..3f043c2172 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cd4dec110d60cbf0a3f3418a66942e4cc22042543ac10b73d96d1d8957b8a9c -size 37361 +oid sha256:67a063a65537c571c9ce463c26304478d0ddf8557c1a2ce1ace6b4b4291aa3f2 +size 37306 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png index e5b1205cd6..070569718d 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db2b1cd6f89552691e28f252e45ba54e1049e4f22ce4ffbc98a8f1dd903bb827 -size 36614 +oid sha256:39ad338c3bc4280fc1d55e2be7fad89ab82a067169df6b03375e9bd6e82b5523 +size 34587 diff --git a/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png b/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png new file mode 100644 index 0000000000..bde5f11c9c --- /dev/null +++ b/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff9f674b9d6ac618a59f8ba9a1b7d85218e720f40c7a313f8bf06254f0b9de1 +size 19237 diff --git a/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_de.png b/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_de.png new file mode 100644 index 0000000000..2ebb09a927 --- /dev/null +++ b/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caccab75ed386837ef568242893d41bc235b0cb33c62fa02d298a86b2bd5e491 +size 13869 diff --git a/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png b/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png index 5cb2d38b7b..d0f08ffe52 100644 --- a/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d669231924e75be81e8bc77ab83cbca830d9ac4931bbc232f2002d45ae0a7137 -size 55492 +oid sha256:af4e734f66b3d9e1158d4276f43ab8b6915cf8005c512b59831dd581a3cb438e +size 55494 diff --git a/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png index 74da77c750..b45b6697ba 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75d446990ba2ec1184daac6adfcdbe9fe0fdd57cebc8762ba59120a0fb4edf09 -size 61241 +oid sha256:9a1864554b9626633423c4e201973a1ec0a18c916d6e3ea898889cc7cfa2a25d +size 61478 diff --git a/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png index 31cbd9061c..6634b3c317 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef0769a9b70db32af32aa3eab10cd2283c4d84786d5c6f8442dd408b69997a49 -size 48943 +oid sha256:d76a6371efb5eb1aebf305b3ee85067f63707e0b16066acc10c2d0f750952f48 +size 48921 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png index 829a0ff81b..1f38c4d566 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:585420c98488d2723a89251bb2128f559875b6b691d98e17e1d27414764eeb11 -size 61344 +oid sha256:75c1832be486d6dfba30f7bf115be324caef1242c0796651f655e2e14318b2ad +size 61656 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png new file mode 100644 index 0000000000..768f400cda --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f9dcea0ff31cd53affbe4e6adcae2681ec98570d3f26ef9e79b014c75a57919 +size 67937 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png index 5cb2d38b7b..d0f08ffe52 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d669231924e75be81e8bc77ab83cbca830d9ac4931bbc232f2002d45ae0a7137 -size 55492 +oid sha256:af4e734f66b3d9e1158d4276f43ab8b6915cf8005c512b59831dd581a3cb438e +size 55494 diff --git a/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png new file mode 100644 index 0000000000..ed4394b6fa --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bccb7232766f51b2166b9d9621f53f893cf133861f1e77331789a5e2cbc96c1 +size 66072 diff --git a/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png index fef50a045d..ec74e4e56d 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:957d3b10f0165461fb30cccd0891731ad30598f3808803f3fd194f62632f2522 -size 53407 +oid sha256:b860e3abcf9d5a4265ab3dd17316a00d96d46da5e6ed6f7bb60eaf5e5d0122da +size 53402 diff --git a/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_de.png index 6cd878104e..817e9368ee 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9465ae95a76d3c51c086ec9dc8b75e72005af324d6d358356ef361c48545bad -size 14760 +oid sha256:19eff09a3190f13352919e820f64a831544d3bcb5d45904bbb953be8d49f8e10 +size 14868 diff --git a/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_de.png index d5211b270d..e1c4bd661b 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:082070d50d9aaa75dc3a9bae767cdb3955e303583b442c9cb99496c0c095b64f -size 15926 +oid sha256:796d1da0f461fe6a9544b98acb73a408feddb754f0fd1f595e773a65591d59b0 +size 16341 diff --git a/screenshots/de/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_de.png index c033287b5e..aca05f1877 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33fb10df2e336c5ed144f6a18cbcefac5a1478d87bc4c53b00ce45d495c3700f -size 19638 +oid sha256:7167d05769b82a6c42face1fd25ecf82a96770fb0279834ffa99bd01d7ed53d9 +size 19969 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png new file mode 100644 index 0000000000..23421c86c1 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5649deae47b73edf1f57c9e0836285d67b1e3c15de4e8d6587abe635789de05b +size 74898 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png new file mode 100644 index 0000000000..efb98584bd --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:022ebe25ae12c40682454d668ea24dbe749946f0edeee55ddf28ca22d99c360c +size 61519 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png new file mode 100644 index 0000000000..2873584aae --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba32f8394882b4491b345852817e238b30504c2d364a2ab60748e310ecf5f1bd +size 74291 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png new file mode 100644 index 0000000000..d704ded910 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c61e5013cef3e1ebe06c51e24e816d3cdf7fcf04857bc7d02a86a480d83844b +size 82400 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png new file mode 100644 index 0000000000..cda1620f8c --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e40c4bf8e1a08d8a023067681f86ba887b97f54716aef24e0feb8cde2d34975 +size 63999 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png new file mode 100644 index 0000000000..c736dc8169 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:344b99b0df98328ac83711647ab162fa6b652eac9b05640938f682e4de47d4d0 +size 62896 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png new file mode 100644 index 0000000000..177f5135f3 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ea10ab065f00bd96553e64dcfe582274bdb075db2fcb882aeeab3710f7b77f9 +size 68530 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png new file mode 100644 index 0000000000..2a405463e7 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bbb1d73be9f2f9dd999a42e2e75f95e16a8ca610759bd37ef0ba80c5f564b3b +size 61663 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png new file mode 100644 index 0000000000..1eab364bad --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76741feeb6e436310715d45f8fcbe01a1987800f10db5ed9fed478b30a77bd0c +size 62390 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png new file mode 100644 index 0000000000..ced6e2c206 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d33c1114f55bd699ec160b1ebb518fb809b41ca74e145a1cc894b0a54d1a3ee3 +size 63519 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png new file mode 100644 index 0000000000..5505f6779e --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97235ce5cb23cc144a124192cc6849e44beeed331521f289d8d5444df95fc58e +size 71403 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png new file mode 100644 index 0000000000..cf0160a1bf --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61d7d88797f535e24d84ea5fceb15f8362b31d6a08510e38214288e978bad0e5 +size 61926 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png index d4187835c3..1cf257b5df 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f18ce9ded3bedf206ba07aca96c9e16cf926ce5ddc586784dd5c3f47dec8184 -size 75949 +oid sha256:483a47b2450370d7e5cfc267c4c6d21fb6bf91cc09b734215355276bf21d6516 +size 75980 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png index f5e26a4e0e..9f238e3d0f 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c83569820b8ee4e64dc840c8731c306e44f4af0f2e96905db27b19c34804967b -size 58944 +oid sha256:e03d4fb9d9f61c7be275c35f2147f7fe6eb4e0f8185c81ae0e8c92e3a649b705 +size 59483 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png index 37cdd5f0d4..7088990852 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:260d9da9bf947755948d1a6b348194f142879409bb9cdb86fe1d00751fa8ac3f -size 74020 +oid sha256:2b21561e0a75416139e6cea48147fcf8f94fcb82470c083de2386bc09b6bb638 +size 74179 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png index 847c9e3deb..7343ba3a68 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79d6c9484a7eebbd53b0d6ed7933eb9cc74e976069f71bb8ee39bde52bd047db -size 85407 +oid sha256:72cf358766cac6a25856d67b64231e0f197d822d528e6b2677121c939c3af009 +size 85364 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png index 2fa288ec23..b62091a933 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cac51fdd3736bf9fae45c28ae4df283dda473acd0baf0ee2ae5415ed73d22327 -size 62157 +oid sha256:8e97d95e3879db231a277be5811fcb8ad9c4781266180a17ee051b23c70b7d35 +size 62594 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png index 85bbb9cc75..4ad99eaff1 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db2f2d07e4d23b316f9309cacfe5e402cf5e230dcb7bc4efef4566e40d798f42 -size 61245 +oid sha256:04ef750b7bf937ec4f37bfef7b8fe01d8ad5dae3a0e82220a5058a72c2747799 +size 61642 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png index 0165e92451..6ec236e181 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b27d85232cba50fbeeb5758ecee65594a4660fa8ec670d55b2ccbc0b97d50a9 -size 68483 +oid sha256:ceb13e2468d332613c9876f5b1b8b0f2d1d0308f60f8375d1157a28aab6af6a8 +size 69022 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png index 19ad78ebb1..3794879115 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cc2e5d586e6296641c37635a8e316f2527c92cf1c21f7ef9915dab01da195e5 -size 59362 +oid sha256:4c213578bdbc2495a4a26251264d803c51f6c4c4cd6547d46f0738a52fd3af3b +size 59895 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png index c166ee4899..c9f64c9762 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0daee317f93a38becad1776e9fbdfc98df8a5b27fdef2befd32e51a556b8ab01 -size 60172 +oid sha256:a15da02688f8f096b84d61579901b0b1b951a3e3ad185a41959f8bf51a12ba02 +size 60667 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png index b55a0df772..4b5ed412cc 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d01926e2e41de43b6e5bfc691a3d21fc7aea31ee62e578130076c8c74554e6b -size 62377 +oid sha256:5c6b9ef3e544c8b43b037d1d85bffb1c20c19d211ebeae8fde3c1832e2454759 +size 62717 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png index afc45ef5c5..a020e8e929 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbb6197fb4f6eeaea613ef2d609fac94e027d5fcc5172718bc1ac9fc925b4031 -size 70498 +oid sha256:5bcfd88322da25f0222dc25941b3c05a3e1b40bf9c43d6d6479e8c2e9c1590d5 +size 71118 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png index 1008408a72..5ed4c0135b 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb7fb190e297edbdc2cd3b715bda9e65bfe366dc4e4c334eb05a713e59948c88 -size 59622 +oid sha256:38722413573171f3e32a273e18d1e07d887dc031b56dbc337495e74b972f8d5e +size 60175 diff --git a/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png new file mode 100644 index 0000000000..55c39845a0 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bf747f7a203f82715af67bfbe1d7833781122f4df5bb1edee6c69b964bd5354 +size 58386 diff --git a/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png index aac16a3f92..795ba6a267 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2faa715f5cf4af59c20c6126d6eaccce631f974e746ad595da8c62000b6138dd -size 46301 +oid sha256:38ec4ada1a2d0f5ca18b8d11d30477be907bc9947cd42e93cf546a86cb75e188 +size 46540 diff --git a/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png new file mode 100644 index 0000000000..20a33a20a3 --- /dev/null +++ b/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:954c6df99d354ed3984f4a62f330f8389d07d15fc94190226a598a0e235e0ced +size 39572 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index bf1c8da024..8947f3de9f 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,61 +1,63 @@ // Generated file, do not edit export const screenshots = [ ["en","en-dark","de",], -["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20168,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20182,], ["features.invite.impl.response_AcceptDeclineInviteView_Day_0_en","features.invite.impl.response_AcceptDeclineInviteView_Night_0_en",0,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",20168,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",20168,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",20168,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",20168,], -["features.invite.impl.response_AcceptDeclineInviteView_Day_5_en","features.invite.impl.response_AcceptDeclineInviteView_Night_5_en",20168,], -["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20168,], -["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20168,], -["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20168,], -["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20168,], -["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20168,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_1_en","features.invite.impl.response_AcceptDeclineInviteView_Night_1_en",20182,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_2_en","features.invite.impl.response_AcceptDeclineInviteView_Night_2_en",20182,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_3_en","features.invite.impl.response_AcceptDeclineInviteView_Night_3_en",20182,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_4_en","features.invite.impl.response_AcceptDeclineInviteView_Night_4_en",20182,], +["features.invite.impl.response_AcceptDeclineInviteView_Day_5_en","features.invite.impl.response_AcceptDeclineInviteView_Night_5_en",20182,], +["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20182,], +["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20182,], +["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20182,], +["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20182,], +["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20182,], ["features.login.impl.accountprovider_AccountProviderView_Day_0_en","features.login.impl.accountprovider_AccountProviderView_Night_0_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_1_en","features.login.impl.accountprovider_AccountProviderView_Night_1_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_2_en","features.login.impl.accountprovider_AccountProviderView_Night_2_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_3_en","features.login.impl.accountprovider_AccountProviderView_Night_3_en",0,], ["features.messages.impl.actionlist_ActionListViewContent_Day_0_en","features.messages.impl.actionlist_ActionListViewContent_Night_0_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20168,], +["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20182,], ["features.messages.impl.actionlist_ActionListViewContent_Day_1_en","features.messages.impl.actionlist_ActionListViewContent_Night_1_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20168,], -["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20168,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20168,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20168,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20168,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20168,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",20168,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",20168,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",20168,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",20168,], -["features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en",20168,], -["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20168,], -["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20168,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20168,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20168,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20168,], +["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20182,], +["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20182,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20182,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20182,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20182,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20182,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en",20182,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en",20182,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en",20182,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en",20182,], +["features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en","features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en",20182,], +["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20182,], +["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20182,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20182,], +["features.analytics.impl_AnalyticsOptInView_Day_1_en","features.analytics.impl_AnalyticsOptInView_Night_1_en",20182,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20182,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en",20182,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20182,], ["libraries.designsystem.components_Announcement_Day_0_en","libraries.designsystem.components_Announcement_Night_0_en",0,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20168,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20182,], ["libraries.designsystem.components.async_AsyncActionView_Day_0_en","libraries.designsystem.components.async_AsyncActionView_Night_0_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20168,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20182,], ["libraries.designsystem.components.async_AsyncActionView_Day_2_en","libraries.designsystem.components.async_AsyncActionView_Night_2_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20168,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20182,], ["libraries.designsystem.components.async_AsyncActionView_Day_4_en","libraries.designsystem.components.async_AsyncActionView_Night_4_en",0,], -["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20168,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20182,], ["libraries.designsystem.components.async_AsyncIndicatorFailure_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorFailure_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncIndicatorLoading_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorLoading_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncLoading_Day_0_en","libraries.designsystem.components.async_AsyncLoading_Night_0_en",0,], -["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20168,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20182,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_0_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_0_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_1_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_1_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_2_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_2_en",0,], @@ -65,18 +67,18 @@ export const screenshots = [ ["libraries.matrix.ui.components_AttachmentThumbnail_Day_6_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_6_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_7_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_7_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_8_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_8_en",0,], -["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20168,], -["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20168,], -["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20168,], -["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20168,], -["features.messages.impl.attachments.preview_AttachmentsView_4_en","",20168,], -["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20168,], +["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20182,], +["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20182,], +["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20182,], +["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20182,], +["features.messages.impl.attachments.preview_AttachmentsView_4_en","",20182,], +["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20182,], ["features.messages.impl.attachments.preview_AttachmentsView_6_en","",0,], -["features.messages.impl.attachments.preview_AttachmentsView_7_en","",20168,], +["features.messages.impl.attachments.preview_AttachmentsView_7_en","",20182,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en",0,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en",0,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20168,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20182,], ["features.knockrequests.impl.banner_AvatarRowRtl_Day_0_en","features.knockrequests.impl.banner_AvatarRowRtl_Night_0_en",0,], ["features.knockrequests.impl.banner_AvatarRowRtl_Day_1_en","features.knockrequests.impl.banner_AvatarRowRtl_Night_1_en",0,], ["features.knockrequests.impl.banner_AvatarRowRtl_Day_2_en","features.knockrequests.impl.banner_AvatarRowRtl_Night_2_en",0,], @@ -184,13 +186,13 @@ export const screenshots = [ ["libraries.designsystem.components_Badge_Day_0_en","libraries.designsystem.components_Badge_Night_0_en",0,], ["libraries.designsystem.components_BigCheckmark_Day_0_en","libraries.designsystem.components_BigCheckmark_Night_0_en",0,], ["libraries.designsystem.components_BigIcon_Day_0_en","libraries.designsystem.components_BigIcon_Night_0_en",0,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20168,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20168,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20168,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20168,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20168,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20168,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20168,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20182,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20182,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20182,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20182,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20182,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20182,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20182,], ["libraries.designsystem.components_BloomInitials_Day_0_en","libraries.designsystem.components_BloomInitials_Night_0_en",0,], ["libraries.designsystem.components_BloomInitials_Day_1_en","libraries.designsystem.components_BloomInitials_Night_1_en",0,], ["libraries.designsystem.components_BloomInitials_Day_2_en","libraries.designsystem.components_BloomInitials_Night_2_en",0,], @@ -201,130 +203,130 @@ export const screenshots = [ ["libraries.designsystem.components_BloomInitials_Day_7_en","libraries.designsystem.components_BloomInitials_Night_7_en",0,], ["libraries.designsystem.components_Bloom_Day_0_en","libraries.designsystem.components_Bloom_Night_0_en",0,], ["libraries.designsystem.theme.components_BottomSheetDragHandle_Day_0_en","libraries.designsystem.theme.components_BottomSheetDragHandle_Night_0_en",0,], -["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20168,], -["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20168,], -["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20168,], -["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20168,], -["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20168,], +["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20182,], +["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20182,], +["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20182,], +["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20182,], +["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20182,], ["libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.molecules_ButtonRowMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonRowMolecule_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_0_en","features.messages.impl.timeline.components_CallMenuItem_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_1_en","features.messages.impl.timeline.components_CallMenuItem_Night_1_en",0,], -["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20168,], -["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20168,], +["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20182,], +["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20182,], ["features.messages.impl.timeline.components_CallMenuItem_Day_4_en","features.messages.impl.timeline.components_CallMenuItem_Night_4_en",0,], ["features.call.impl.ui_CallScreenPipView_Day_0_en","features.call.impl.ui_CallScreenPipView_Night_0_en",0,], ["features.call.impl.ui_CallScreenPipView_Day_1_en","features.call.impl.ui_CallScreenPipView_Night_1_en",0,], ["features.call.impl.ui_CallScreenView_Day_0_en","features.call.impl.ui_CallScreenView_Night_0_en",0,], -["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20168,], -["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20168,], -["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20168,], -["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20168,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20168,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20168,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20182,], +["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20182,], +["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20182,], +["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20182,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_0_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en","features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20182,], +["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20182,], ["features.login.impl.changeserver_ChangeServerView_Day_0_en","features.login.impl.changeserver_ChangeServerView_Night_0_en",0,], -["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20168,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20168,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20182,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20182,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20168,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20182,], ["libraries.designsystem.theme.components_Checkboxes_Toggles_en","",0,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en",20168,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en",20168,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en",20168,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en",20168,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en",20182,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en",20182,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en",20182,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en",20182,], ["libraries.designsystem.theme.components_CircularProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components_ClickableLinkText_Text_en","",0,], ["libraries.designsystem.theme_ColorAliases_Day_0_en","libraries.designsystem.theme_ColorAliases_Night_0_en",0,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20168,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20168,], -["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20168,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20182,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20182,], +["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20182,], ["libraries.textcomposer_ComposerModeView_Day_1_en","libraries.textcomposer_ComposerModeView_Night_1_en",0,], ["libraries.textcomposer_ComposerModeView_Day_2_en","libraries.textcomposer_ComposerModeView_Night_2_en",0,], ["libraries.textcomposer_ComposerModeView_Day_3_en","libraries.textcomposer_ComposerModeView_Night_3_en",0,], ["libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en","",0,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20168,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20168,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20168,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20168,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20168,], -["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20168,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20182,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20182,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20182,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20182,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20182,], +["features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20182,], ["libraries.designsystem.components.dialogs_ConfirmationDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ConfirmationDialog_Day_0_en","libraries.designsystem.components.dialogs_ConfirmationDialog_Night_0_en",0,], ["features.networkmonitor.api.ui_ConnectivityIndicatorView_Day_0_en","features.networkmonitor.api.ui_ConnectivityIndicatorView_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en","libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en",0,], -["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20168,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20168,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20168,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20168,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20168,], -["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_0_en",20168,], -["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_1_en",20168,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20168,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20168,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20168,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20168,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20168,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20168,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20168,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20168,], -["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",20168,], -["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",20168,], -["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",20168,], -["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",20168,], -["features.createroom.impl.root_CreateRoomRootView_Day_4_en","features.createroom.impl.root_CreateRoomRootView_Night_4_en",20168,], -["features.createroom.impl.root_CreateRoomRootView_Day_5_en","features.createroom.impl.root_CreateRoomRootView_Night_5_en",20168,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20168,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20168,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20168,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20168,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20168,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20182,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20182,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20182,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20182,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20182,], +["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_0_en",20182,], +["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_1_en",20182,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20182,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20182,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20182,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20182,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20182,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20182,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20182,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20182,], +["features.createroom.impl.root_CreateRoomRootView_Day_0_en","features.createroom.impl.root_CreateRoomRootView_Night_0_en",20182,], +["features.createroom.impl.root_CreateRoomRootView_Day_1_en","features.createroom.impl.root_CreateRoomRootView_Night_1_en",20182,], +["features.createroom.impl.root_CreateRoomRootView_Day_2_en","features.createroom.impl.root_CreateRoomRootView_Night_2_en",20182,], +["features.createroom.impl.root_CreateRoomRootView_Day_3_en","features.createroom.impl.root_CreateRoomRootView_Night_3_en",20182,], +["features.createroom.impl.root_CreateRoomRootView_Day_4_en","features.createroom.impl.root_CreateRoomRootView_Night_4_en",20182,], +["features.createroom.impl.root_CreateRoomRootView_Day_5_en","features.createroom.impl.root_CreateRoomRootView_Night_5_en",20182,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20182,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20182,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20182,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20182,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20182,], ["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en",0,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20168,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20168,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20182,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20182,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_0_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_0_en",0,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20168,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20168,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20168,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20182,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20182,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20182,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_4_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_4_en",0,], -["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20168,], -["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20168,], -["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",20168,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20182,], +["features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20182,], +["features.roomlist.impl.components_DefaultRoomListTopBar_Day_0_en","features.roomlist.impl.components_DefaultRoomListTopBar_Night_0_en",20182,], ["features.licenses.impl.details_DependenciesDetailsView_Day_0_en","features.licenses.impl.details_DependenciesDetailsView_Night_0_en",0,], -["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20168,], -["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20168,], -["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20168,], -["features.licenses.impl.list_DependencyLicensesListView_Day_3_en","features.licenses.impl.list_DependencyLicensesListView_Night_3_en",20168,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20168,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20168,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20168,], +["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20182,], +["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20182,], +["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20182,], +["features.licenses.impl.list_DependencyLicensesListView_Day_3_en","features.licenses.impl.list_DependencyLicensesListView_Night_3_en",20182,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20182,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20182,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20182,], ["libraries.designsystem.theme.components_DialogWithDestructiveButton_Dialog_with_destructive_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithOnlyMessageAndOkButton_Dialog_with_only_message_and_ok_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithThirdButton_Dialog_with_third_button_Dialogs_en","",0,], @@ -337,17 +339,17 @@ export const screenshots = [ ["libraries.designsystem.text_DpScale_1_0f__en","",0,], ["libraries.designsystem.text_DpScale_1_5f__en","",0,], ["libraries.designsystem.theme.components_DropdownMenuItem_Menus_en","",0,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20168,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20168,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20168,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20168,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20168,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_0_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_0_en",20168,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_1_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_1_en",20168,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_2_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_2_en",20168,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_3_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_3_en",20168,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_4_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_4_en",20168,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20168,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20182,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20182,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20182,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20182,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20182,], +["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_0_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_0_en",20182,], +["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_1_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_1_en",20182,], +["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_2_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_2_en",20182,], +["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_3_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_3_en",20182,], +["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_4_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_4_en",20182,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20182,], ["libraries.matrix.ui.components_EditableAvatarView_Day_0_en","libraries.matrix.ui.components_EditableAvatarView_Night_0_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_1_en","libraries.matrix.ui.components_EditableAvatarView_Night_1_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_2_en","libraries.matrix.ui.components_EditableAvatarView_Night_2_en",0,], @@ -357,9 +359,9 @@ export const screenshots = [ ["libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiItem_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiItem_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiPicker_Night_0_en",0,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20168,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20168,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20168,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20182,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20182,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20182,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_FileItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_FileItemView_Night_0_en",0,], @@ -377,16 +379,16 @@ export const screenshots = [ ["libraries.designsystem.theme.components_FloatingActionButton_Floating_Action_Buttons_en","",0,], ["libraries.designsystem.atomic.pages_FlowStepPage_Day_0_en","libraries.designsystem.atomic.pages_FlowStepPage_Night_0_en",0,], ["features.messages.impl.timeline.focus_FocusRequestStateView_Day_0_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_0_en",0,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20168,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20168,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20168,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20182,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20182,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20182,], ["features.messages.impl.timeline.components_FocusedEvent_Day_0_en","features.messages.impl.timeline.components_FocusedEvent_Night_0_en",0,], ["libraries.textcomposer.components_FormattingOption_Day_0_en","libraries.textcomposer.components_FormattingOption_Night_0_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_0_en","features.messages.impl.forward_ForwardMessagesView_Night_0_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_1_en","features.messages.impl.forward_ForwardMessagesView_Night_1_en",0,], ["features.messages.impl.forward_ForwardMessagesView_Day_2_en","features.messages.impl.forward_ForwardMessagesView_Night_2_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20168,], -["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20168,], +["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20182,], +["features.roomlist.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.roomlist.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20182,], ["libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Night_0_en",0,], ["libraries.designsystem.components.button_GradientFloatingActionButton_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButton_Night_0_en",0,], ["features.messages.impl.timeline.components.group_GroupHeaderView_Day_0_en","features.messages.impl.timeline.components.group_GroupHeaderView_Night_0_en",0,], @@ -410,8 +412,8 @@ export const screenshots = [ ["libraries.designsystem.icons_IconsCompound_Day_5_en","libraries.designsystem.icons_IconsCompound_Night_5_en",0,], ["libraries.designsystem.icons_IconsOther_Day_0_en","libraries.designsystem.icons_IconsOther_Night_0_en",0,], ["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_0_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_0_en",0,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20168,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20168,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20182,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20182,], ["libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_0_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_10_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_10_en",0,], @@ -419,82 +421,84 @@ export const screenshots = [ ["libraries.matrix.ui.messages.reply_InReplyToView_Day_1_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_1_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_2_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_2_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_3_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_3_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20168,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20182,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_5_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_5_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_6_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_6_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_7_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_7_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20168,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20182,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_9_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_9_en",0,], -["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en",20168,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en",20168,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en",20182,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en",20182,], ["features.networkmonitor.api.ui_Indicator_Day_0_en","features.networkmonitor.api.ui_Indicator_Night_0_en",0,], ["libraries.designsystem.atomic.molecules_InfoListItemMolecule_Day_0_en","libraries.designsystem.atomic.molecules_InfoListItemMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.organisms_InfoListOrganism_Day_0_en","libraries.designsystem.atomic.organisms_InfoListOrganism_Night_0_en",0,], -["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20168,], -["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en",20168,], -["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en",20168,], -["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en",20168,], -["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en",20168,], -["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en",20168,], -["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en",20168,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20182,], +["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en",20182,], +["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en",20182,], +["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en",20182,], +["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en",20182,], +["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en",20182,], +["features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en","features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en",20182,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_13_en","features.joinroom.impl_JoinRoomView_Night_13_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_14_en","features.joinroom.impl_JoinRoomView_Night_14_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_15_en","features.joinroom.impl_JoinRoomView_Night_15_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_16_en","features.joinroom.impl_JoinRoomView_Night_16_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20168,], -["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20168,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_10_en","features.knockrequests.impl.list_KnockRequestsListView_Night_10_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20168,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20168,], +["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_13_en","features.joinroom.impl_JoinRoomView_Night_13_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_14_en","features.joinroom.impl_JoinRoomView_Night_14_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_15_en","features.joinroom.impl_JoinRoomView_Night_15_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_16_en","features.joinroom.impl_JoinRoomView_Night_16_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20182,], +["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20182,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_10_en","features.knockrequests.impl.list_KnockRequestsListView_Night_10_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20182,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20182,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], ["features.leaveroom.api_LeaveRoomView_Day_0_en","features.leaveroom.api_LeaveRoomView_Night_0_en",0,], -["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",20168,], -["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",20168,], -["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",20168,], -["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",20168,], -["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",20168,], -["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",20168,], +["features.leaveroom.api_LeaveRoomView_Day_1_en","features.leaveroom.api_LeaveRoomView_Night_1_en",20182,], +["features.leaveroom.api_LeaveRoomView_Day_2_en","features.leaveroom.api_LeaveRoomView_Night_2_en",20182,], +["features.leaveroom.api_LeaveRoomView_Day_3_en","features.leaveroom.api_LeaveRoomView_Night_3_en",20182,], +["features.leaveroom.api_LeaveRoomView_Day_4_en","features.leaveroom.api_LeaveRoomView_Night_4_en",20182,], +["features.leaveroom.api_LeaveRoomView_Day_5_en","features.leaveroom.api_LeaveRoomView_Night_5_en",20182,], +["features.leaveroom.api_LeaveRoomView_Day_6_en","features.leaveroom.api_LeaveRoomView_Night_6_en",20182,], ["libraries.designsystem.background_LightGradientBackground_Day_0_en","libraries.designsystem.background_LightGradientBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_LinearProgressIndicator_Progress_Indicators_en","",0,], +["features.messages.impl.link_LinkView_Day_0_en","features.messages.impl.link_LinkView_Night_0_en",0,], +["features.messages.impl.link_LinkView_Day_1_en","features.messages.impl.link_LinkView_Night_1_en",20182,], ["libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ListDialog_Day_0_en","libraries.designsystem.components.dialogs_ListDialog_Night_0_en",0,], ["libraries.designsystem.theme.components_ListItemPrimaryActionWithIcon_List_item_-_Primary_action_&_Icon_List_items_en","",0,], @@ -549,30 +553,31 @@ export const screenshots = [ ["libraries.designsystem.theme.components_ListSupportingTextSmallPadding_List_supporting_text_-_small_padding_List_sections_en","",0,], ["libraries.textcomposer.components_LiveWaveformView_Day_0_en","libraries.textcomposer.components_LiveWaveformView_Night_0_en",0,], ["appnav.room.joined_LoadingRoomNodeView_Day_0_en","appnav.room.joined_LoadingRoomNodeView_Night_0_en",0,], -["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20168,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20168,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20168,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20168,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20182,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20182,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20182,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20182,], ["appnav.loggedin_LoggedInView_Day_0_en","appnav.loggedin_LoggedInView_Night_0_en",0,], -["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20168,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20168,], -["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20168,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20168,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20168,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20168,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20168,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20168,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20168,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20168,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20168,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20168,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20168,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20168,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20168,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20168,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20182,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20182,], +["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20182,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20182,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20182,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20182,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20182,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20182,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20182,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20182,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20182,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20182,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20182,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20182,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20182,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20182,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20168,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20182,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], +["libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNeutral_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNeutral_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Night_0_en",0,], @@ -583,22 +588,22 @@ export const screenshots = [ ["libraries.matrix.ui.components_MatrixUserRow_Day_1_en","libraries.matrix.ui.components_MatrixUserRow_Night_1_en",0,], ["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en",0,], -["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20168,], -["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20168,], +["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20182,], +["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20182,], ["libraries.mediaviewer.impl.local.file_MediaFileView_Day_0_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_0_en",0,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_11_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_12_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20168,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20168,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_11_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_12_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20182,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20182,], ["libraries.mediaviewer.impl.local.image_MediaImageView_Day_0_en","libraries.mediaviewer.impl.local.image_MediaImageView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en",0,], @@ -606,14 +611,14 @@ export const screenshots = [ ["libraries.mediaviewer.impl.local.video_MediaVideoView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaVideoView_Night_0_en",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_0_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_10_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20168,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20168,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20182,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20182,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_13_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_14_en","",20168,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_14_en","",20182,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_15_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_16_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20168,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20182,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_3_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_4_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_5_en","",0,], @@ -625,7 +630,7 @@ export const screenshots = [ ["libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en","libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en",0,], ["libraries.designsystem.theme.components.previews_Menu_Menus_en","",0,], ["features.messages.impl.messagecomposer_MessageComposerViewVoice_Day_0_en","features.messages.impl.messagecomposer_MessageComposerViewVoice_Night_0_en",0,], -["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20168,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20182,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_0_en","features.messages.impl.timeline.components_MessageEventBubble_Night_0_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_10_en","features.messages.impl.timeline.components_MessageEventBubble_Night_10_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_11_en","features.messages.impl.timeline.components_MessageEventBubble_Night_11_en",0,], @@ -642,7 +647,7 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessageEventBubble_Day_7_en","features.messages.impl.timeline.components_MessageEventBubble_Night_7_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_8_en","features.messages.impl.timeline.components_MessageEventBubble_Night_8_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_9_en","features.messages.impl.timeline.components_MessageEventBubble_Night_9_en",0,], -["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20168,], +["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20182,], ["features.messages.impl.timeline.components_MessageStateEventContainer_Day_0_en","features.messages.impl.timeline.components_MessageStateEventContainer_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonAdd_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonAdd_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonExtra_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonExtra_Night_0_en",0,], @@ -650,25 +655,25 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessagesReactionButton_Day_1_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_1_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_2_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_2_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_3_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_3_en",0,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20168,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20168,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20168,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20168,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20168,], -["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20168,], -["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",20168,], -["features.messages.impl_MessagesView_Day_13_en","features.messages.impl_MessagesView_Night_13_en",20171,], -["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20168,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20168,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20168,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20168,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20168,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20168,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20168,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20168,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20168,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20182,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20182,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20182,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20182,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20182,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20182,], +["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",20182,], +["features.messages.impl_MessagesView_Day_13_en","features.messages.impl_MessagesView_Night_13_en",20182,], +["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20182,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20182,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20182,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20182,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20182,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20182,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20182,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20182,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20182,], ["features.migration.impl_MigrationView_Day_0_en","features.migration.impl_MigrationView_Night_0_en",0,], -["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20168,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20182,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom_Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom_Sheets_en","",0,], ["appicon.element_MonochromeIcon_en","",0,], @@ -677,28 +682,29 @@ export const screenshots = [ ["libraries.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_Multiple_selection_List_item_-_selection_in_trailing_content_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItemSelected_Multiple_selection_List_item_-_selection_in_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItem_Multiple_selection_List_item_-_no_selection_List_items_en","",0,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_13_en","features.preferences.impl.notifications_NotificationSettingsView_Night_13_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20168,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20168,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20168,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_13_en","features.preferences.impl.notifications_NotificationSettingsView_Night_13_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20182,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20182,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20182,], ["libraries.oidc.impl.webview_OidcView_Day_0_en","libraries.oidc.impl.webview_OidcView_Night_0_en",0,], ["libraries.oidc.impl.webview_OidcView_Day_1_en","libraries.oidc.impl.webview_OidcView_Night_1_en",0,], ["libraries.designsystem.atomic.pages_OnBoardingPage_Day_0_en","libraries.designsystem.atomic.pages_OnBoardingPage_Night_0_en",0,], -["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",20168,], -["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",20168,], -["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",20168,], -["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",20168,], +["features.onboarding.impl_OnBoardingView_Day_0_en","features.onboarding.impl_OnBoardingView_Night_0_en",20182,], +["features.onboarding.impl_OnBoardingView_Day_1_en","features.onboarding.impl_OnBoardingView_Night_1_en",20182,], +["features.onboarding.impl_OnBoardingView_Day_2_en","features.onboarding.impl_OnBoardingView_Night_2_en",20182,], +["features.onboarding.impl_OnBoardingView_Day_3_en","features.onboarding.impl_OnBoardingView_Night_3_en",20182,], +["features.onboarding.impl_OnBoardingView_Day_4_en","features.onboarding.impl_OnBoardingView_Night_4_en",20182,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_OutlinedButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], @@ -713,65 +719,65 @@ export const screenshots = [ ["libraries.designsystem.components_PageTitleWithIconFull_Day_5_en","libraries.designsystem.components_PageTitleWithIconFull_Night_5_en",0,], ["libraries.designsystem.components_PageTitleWithIconFull_Day_6_en","libraries.designsystem.components_PageTitleWithIconFull_Night_6_en",0,], ["libraries.designsystem.components_PageTitleWithIconMinimal_Day_0_en","libraries.designsystem.components_PageTitleWithIconMinimal_Night_0_en",0,], -["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20168,], -["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",20168,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20168,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20168,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20168,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20168,], +["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20182,], +["features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Day_0_en","features.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_Night_0_en",20182,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20182,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20182,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20182,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20182,], ["features.lockscreen.impl.components_PinEntryTextField_Day_0_en","features.lockscreen.impl.components_PinEntryTextField_Night_0_en",0,], ["libraries.designsystem.components_PinIcon_Day_0_en","libraries.designsystem.components_PinIcon_Night_0_en",0,], ["features.lockscreen.impl.unlock.keypad_PinKeypad_Day_0_en","features.lockscreen.impl.unlock.keypad_PinKeypad_Night_0_en",0,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20168,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20168,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20182,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20182,], ["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_0_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_0_en",0,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20168,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20168,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20168,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20168,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20168,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20168,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20182,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20182,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20182,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20182,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20182,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20182,], ["libraries.designsystem.atomic.atoms_PlaceholderAtom_Day_0_en","libraries.designsystem.atomic.atoms_PlaceholderAtom_Night_0_en",0,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20168,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20168,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20168,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20168,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20168,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20182,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20182,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20182,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20182,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20182,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en",0,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Night_0_en",0,], -["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20168,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20168,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20168,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20168,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20168,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20168,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20168,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20168,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20168,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20168,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20168,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20182,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20182,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20182,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20182,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20182,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20182,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20182,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20182,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20182,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20182,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20182,], ["features.poll.api.pollcontent_PollTitleView_Day_0_en","features.poll.api.pollcontent_PollTitleView_Night_0_en",0,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], @@ -785,207 +791,208 @@ export const screenshots = [ ["libraries.designsystem.components.preferences_PreferenceRow_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceSlide_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceSwitch_Preferences_en","",0,], -["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20168,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20168,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20168,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20168,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20182,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20182,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20182,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20182,], ["features.messages.impl.timeline.components.event_ProgressButton_Day_0_en","features.messages.impl.timeline.components.event_ProgressButton_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20168,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20168,], -["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20168,], -["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20168,], -["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20168,], -["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20168,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20168,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20168,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20168,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20168,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20168,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20168,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20168,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20168,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20168,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20168,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20182,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20182,], +["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20182,], +["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20182,], +["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20182,], +["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20182,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20182,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20182,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20182,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20182,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20182,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20182,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20182,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20182,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20182,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20182,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20168,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20168,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20182,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20182,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], ["features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Night_0_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20168,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20168,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20168,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20168,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20168,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20168,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20168,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20182,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20182,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20182,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20182,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20182,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20182,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20182,], ["libraries.designsystem.atomic.atoms_RedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_RedIndicatorAtom_Night_0_en",0,], ["features.messages.impl.timeline.components_ReplySwipeIndicator_Day_0_en","features.messages.impl.timeline.components_ReplySwipeIndicator_Night_0_en",0,], -["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20168,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20168,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20168,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20168,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20168,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20168,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20168,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20168,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20168,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20168,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20168,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20168,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20182,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20182,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20182,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20182,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20182,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20182,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20182,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20182,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20182,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20182,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20182,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20182,], ["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_0_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_0_en",0,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20168,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20168,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20168,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20168,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20168,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20182,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20182,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20182,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20182,], +["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20182,], ["libraries.matrix.ui.room.address_RoomAddressField_Day_0_en","libraries.matrix.ui.room.address_RoomAddressField_Night_0_en",0,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_0_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_0_en",0,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",20168,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20168,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_13_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_14_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_15_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_16_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_17_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_18_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",20168,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20168,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20168,], -["features.roomdetails.impl_RoomDetails_0_en","",20168,], -["features.roomdetails.impl_RoomDetails_10_en","",20168,], -["features.roomdetails.impl_RoomDetails_11_en","",20168,], -["features.roomdetails.impl_RoomDetails_12_en","",20168,], -["features.roomdetails.impl_RoomDetails_13_en","",20168,], -["features.roomdetails.impl_RoomDetails_14_en","",20168,], -["features.roomdetails.impl_RoomDetails_15_en","",20168,], -["features.roomdetails.impl_RoomDetails_16_en","",20168,], -["features.roomdetails.impl_RoomDetails_17_en","",20168,], -["features.roomdetails.impl_RoomDetails_18_en","",20168,], -["features.roomdetails.impl_RoomDetails_1_en","",20168,], -["features.roomdetails.impl_RoomDetails_2_en","",20168,], -["features.roomdetails.impl_RoomDetails_3_en","",20168,], -["features.roomdetails.impl_RoomDetails_4_en","",20168,], -["features.roomdetails.impl_RoomDetails_5_en","",20168,], -["features.roomdetails.impl_RoomDetails_6_en","",20168,], -["features.roomdetails.impl_RoomDetails_7_en","",20168,], -["features.roomdetails.impl_RoomDetails_8_en","",20168,], -["features.roomdetails.impl_RoomDetails_9_en","",20168,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20168,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20168,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",20168,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",20168,], -["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",20168,], -["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",20168,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",20182,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20182,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_13_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_14_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_15_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_16_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_17_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_18_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",20182,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20182,], +["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20182,], +["features.roomdetails.impl_RoomDetails_0_en","",20182,], +["features.roomdetails.impl_RoomDetails_10_en","",20182,], +["features.roomdetails.impl_RoomDetails_11_en","",20182,], +["features.roomdetails.impl_RoomDetails_12_en","",20182,], +["features.roomdetails.impl_RoomDetails_13_en","",20182,], +["features.roomdetails.impl_RoomDetails_14_en","",20182,], +["features.roomdetails.impl_RoomDetails_15_en","",20182,], +["features.roomdetails.impl_RoomDetails_16_en","",20182,], +["features.roomdetails.impl_RoomDetails_17_en","",20182,], +["features.roomdetails.impl_RoomDetails_18_en","",20182,], +["features.roomdetails.impl_RoomDetails_1_en","",20182,], +["features.roomdetails.impl_RoomDetails_2_en","",20182,], +["features.roomdetails.impl_RoomDetails_3_en","",20182,], +["features.roomdetails.impl_RoomDetails_4_en","",20182,], +["features.roomdetails.impl_RoomDetails_5_en","",20182,], +["features.roomdetails.impl_RoomDetails_6_en","",20182,], +["features.roomdetails.impl_RoomDetails_7_en","",20182,], +["features.roomdetails.impl_RoomDetails_8_en","",20182,], +["features.roomdetails.impl_RoomDetails_9_en","",20182,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20182,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20182,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_4_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_4_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_5_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_5_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_6_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_6_en",20182,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_7_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_7_en",20182,], +["features.roomlist.impl.components_RoomListContentView_Day_0_en","features.roomlist.impl.components_RoomListContentView_Night_0_en",20182,], +["features.roomlist.impl.components_RoomListContentView_Day_1_en","features.roomlist.impl.components_RoomListContentView_Night_1_en",20182,], ["features.roomlist.impl.components_RoomListContentView_Day_2_en","features.roomlist.impl.components_RoomListContentView_Night_2_en",0,], -["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",20168,], -["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",20168,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",20168,], -["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",20168,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",20168,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",20168,], -["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",20168,], +["features.roomlist.impl.components_RoomListContentView_Day_3_en","features.roomlist.impl.components_RoomListContentView_Night_3_en",20182,], +["features.roomlist.impl.components_RoomListContentView_Day_4_en","features.roomlist.impl.components_RoomListContentView_Night_4_en",20182,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_0_en","features.roomlist.impl.filters_RoomListFiltersView_Night_0_en",20182,], +["features.roomlist.impl.filters_RoomListFiltersView_Day_1_en","features.roomlist.impl.filters_RoomListFiltersView_Night_1_en",20182,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_0_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_0_en",20182,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_1_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_1_en",20182,], +["features.roomlist.impl_RoomListModalBottomSheetContent_Day_2_en","features.roomlist.impl_RoomListModalBottomSheetContent_Night_2_en",20182,], ["features.roomlist.impl.search_RoomListSearchContent_Day_0_en","features.roomlist.impl.search_RoomListSearchContent_Night_0_en",0,], -["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",20168,], -["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",20168,], -["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",20168,], -["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",20168,], -["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",20168,], -["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",20168,], -["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",20168,], -["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",20168,], -["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",20168,], -["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",20168,], +["features.roomlist.impl.search_RoomListSearchContent_Day_1_en","features.roomlist.impl.search_RoomListSearchContent_Night_1_en",20182,], +["features.roomlist.impl_RoomListView_Day_0_en","features.roomlist.impl_RoomListView_Night_0_en",20182,], +["features.roomlist.impl_RoomListView_Day_10_en","features.roomlist.impl_RoomListView_Night_10_en",20182,], +["features.roomlist.impl_RoomListView_Day_1_en","features.roomlist.impl_RoomListView_Night_1_en",20182,], +["features.roomlist.impl_RoomListView_Day_2_en","features.roomlist.impl_RoomListView_Night_2_en",20182,], +["features.roomlist.impl_RoomListView_Day_3_en","features.roomlist.impl_RoomListView_Night_3_en",20182,], +["features.roomlist.impl_RoomListView_Day_4_en","features.roomlist.impl_RoomListView_Night_4_en",20182,], +["features.roomlist.impl_RoomListView_Day_5_en","features.roomlist.impl_RoomListView_Night_5_en",20182,], +["features.roomlist.impl_RoomListView_Day_6_en","features.roomlist.impl_RoomListView_Night_6_en",20182,], +["features.roomlist.impl_RoomListView_Day_7_en","features.roomlist.impl_RoomListView_Night_7_en",20182,], ["features.roomlist.impl_RoomListView_Day_8_en","features.roomlist.impl_RoomListView_Night_8_en",0,], ["features.roomlist.impl_RoomListView_Day_9_en","features.roomlist.impl_RoomListView_Night_9_en",0,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20168,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20168,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",20168,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20182,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20182,], +["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",20182,], ["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",0,], -["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20168,], -["features.roomdetails.impl.members_RoomMemberListView_Day_9_en","features.roomdetails.impl.members_RoomMemberListView_Night_9_en",20168,], +["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20182,], +["features.roomdetails.impl.members_RoomMemberListView_Day_9_en","features.roomdetails.impl.members_RoomMemberListView_Night_9_en",20182,], ["libraries.designsystem.atomic.molecules_RoomMembersCountMolecule_Day_0_en","libraries.designsystem.atomic.molecules_RoomMembersCountMolecule_Night_0_en",0,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",0,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",20168,], -["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en",0,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20168,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20168,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20168,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20168,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20168,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20168,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20168,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20168,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en",0,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en",20185,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en",0,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en",20182,], +["features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en","features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en",20185,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20182,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20182,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20182,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20182,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20182,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20182,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20182,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20182,], ["features.roomlist.impl.components_RoomSummaryPlaceholderRow_Day_0_en","features.roomlist.impl.components_RoomSummaryPlaceholderRow_Night_0_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_0_en","features.roomlist.impl.components_RoomSummaryRow_Night_0_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_10_en","features.roomlist.impl.components_RoomSummaryRow_Night_10_en",0,], @@ -1008,12 +1015,12 @@ export const screenshots = [ ["features.roomlist.impl.components_RoomSummaryRow_Day_26_en","features.roomlist.impl.components_RoomSummaryRow_Night_26_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_27_en","features.roomlist.impl.components_RoomSummaryRow_Night_27_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_28_en","features.roomlist.impl.components_RoomSummaryRow_Night_28_en",0,], -["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",20168,], -["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",20168,], -["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",20168,], -["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",20168,], -["features.roomlist.impl.components_RoomSummaryRow_Day_32_en","features.roomlist.impl.components_RoomSummaryRow_Night_32_en",20168,], -["features.roomlist.impl.components_RoomSummaryRow_Day_33_en","features.roomlist.impl.components_RoomSummaryRow_Night_33_en",20168,], +["features.roomlist.impl.components_RoomSummaryRow_Day_29_en","features.roomlist.impl.components_RoomSummaryRow_Night_29_en",20182,], +["features.roomlist.impl.components_RoomSummaryRow_Day_2_en","features.roomlist.impl.components_RoomSummaryRow_Night_2_en",20182,], +["features.roomlist.impl.components_RoomSummaryRow_Day_30_en","features.roomlist.impl.components_RoomSummaryRow_Night_30_en",20182,], +["features.roomlist.impl.components_RoomSummaryRow_Day_31_en","features.roomlist.impl.components_RoomSummaryRow_Night_31_en",20182,], +["features.roomlist.impl.components_RoomSummaryRow_Day_32_en","features.roomlist.impl.components_RoomSummaryRow_Night_32_en",20182,], +["features.roomlist.impl.components_RoomSummaryRow_Day_33_en","features.roomlist.impl.components_RoomSummaryRow_Night_33_en",20182,], ["features.roomlist.impl.components_RoomSummaryRow_Day_3_en","features.roomlist.impl.components_RoomSummaryRow_Night_3_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_4_en","features.roomlist.impl.components_RoomSummaryRow_Night_4_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_5_en","features.roomlist.impl.components_RoomSummaryRow_Night_5_en",0,], @@ -1021,77 +1028,77 @@ export const screenshots = [ ["features.roomlist.impl.components_RoomSummaryRow_Day_7_en","features.roomlist.impl.components_RoomSummaryRow_Night_7_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_8_en","features.roomlist.impl.components_RoomSummaryRow_Night_8_en",0,], ["features.roomlist.impl.components_RoomSummaryRow_Day_9_en","features.roomlist.impl.components_RoomSummaryRow_Night_9_en",0,], -["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20168,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20168,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20168,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20182,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20182,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20182,], ["appicon.enterprise_RoundIcon_en","",0,], ["appicon.element_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20168,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20168,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20168,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20182,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20182,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20182,], ["libraries.designsystem.theme.components_SearchBarActiveNoneQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithContent_Search_views_en","",0,], -["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20168,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20182,], ["libraries.designsystem.theme.components_SearchBarActiveWithQueryNoBackButton_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarInactive_Search_views_en","",0,], -["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",20168,], -["features.createroom.impl.components_SearchSingleUserResultItem_en","",20168,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20168,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20168,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20168,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20168,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20168,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20168,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20168,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20168,], -["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20168,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_0_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_1_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_2_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_4_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_5_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_6_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_7_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_8_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_0_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_1_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_2_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_4_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_5_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_6_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_7_en","",20168,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_8_en","",20168,], +["features.createroom.impl.components_SearchMultipleUsersResultItem_en","",20182,], +["features.createroom.impl.components_SearchSingleUserResultItem_en","",20182,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20182,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20182,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20182,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20182,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20182,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20182,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20182,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20182,], +["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20182,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_0_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_1_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_2_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_4_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_5_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_6_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_7_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_8_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_0_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_1_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_2_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_4_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_5_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_6_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_7_en","",20182,], +["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_8_en","",20182,], ["libraries.matrix.ui.components_SelectedRoom_Day_0_en","libraries.matrix.ui.components_SelectedRoom_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoom_Day_1_en","libraries.matrix.ui.components_SelectedRoom_Night_1_en",0,], ["libraries.matrix.ui.components_SelectedRoom_Day_2_en","libraries.matrix.ui.components_SelectedRoom_Night_2_en",0,], @@ -1099,11 +1106,11 @@ export const screenshots = [ ["libraries.matrix.ui.components_SelectedUser_Day_0_en","libraries.matrix.ui.components_SelectedUser_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedUsersRowList_Day_0_en","libraries.matrix.ui.components_SelectedUsersRowList_Night_0_en",0,], ["libraries.textcomposer.components_SendButton_Day_0_en","libraries.textcomposer.components_SendButton_Night_0_en",0,], -["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20168,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20168,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20168,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20168,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20168,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20182,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20182,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20182,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20182,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20182,], ["libraries.matrix.ui.messages.sender_SenderName_Day_0_en","libraries.matrix.ui.messages.sender_SenderName_Night_0_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_1_en","libraries.matrix.ui.messages.sender_SenderName_Night_1_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_2_en","libraries.matrix.ui.messages.sender_SenderName_Night_2_en",0,], @@ -1113,27 +1120,27 @@ export const screenshots = [ ["libraries.matrix.ui.messages.sender_SenderName_Day_6_en","libraries.matrix.ui.messages.sender_SenderName_Night_6_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_7_en","libraries.matrix.ui.messages.sender_SenderName_Night_7_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_8_en","libraries.matrix.ui.messages.sender_SenderName_Night_8_en",0,], -["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20168,], -["features.roomlist.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20168,], -["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20168,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20168,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20168,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20168,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20168,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20168,], +["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20182,], +["features.roomlist.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.roomlist.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20182,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20182,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20182,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20182,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20182,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20182,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20182,], ["features.share.impl_ShareView_Day_0_en","features.share.impl_ShareView_Night_0_en",0,], ["features.share.impl_ShareView_Day_1_en","features.share.impl_ShareView_Night_1_en",0,], ["features.share.impl_ShareView_Day_2_en","features.share.impl_ShareView_Night_2_en",0,], -["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20168,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20168,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20168,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20168,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20168,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20168,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20168,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20168,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20168,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20168,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20182,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20182,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20182,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20182,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20182,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20182,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20182,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20182,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20182,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20182,], ["libraries.designsystem.components.dialogs_SingleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_SingleSelectionDialog_Night_0_en",0,], ["libraries.designsystem.components.list_SingleSelectionListItemCustomFormattert_Single_selection_List_item_-_custom_formatter_List_items_en","",0,], @@ -1142,7 +1149,7 @@ export const screenshots = [ ["libraries.designsystem.components.list_SingleSelectionListItemUnselectedWithSupportingText_Single_selection_List_item_-_no_selection,_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_SingleSelectionListItem_Single_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_Sliders_Sliders_en","",0,], -["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20168,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20182,], ["libraries.designsystem.theme.components_SnackbarWithActionAndCloseButton_Snackbar_with_action_and_close_button_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLineAndCloseButton_Snackbar_with_action_and_close_button_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLine_Snackbar_with_action_on_new_line_Snackbars_en","",0,], @@ -1151,42 +1158,60 @@ export const screenshots = [ ["libraries.designsystem.modifiers_SquareSizeModifierInsideSquare_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeHeight_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeWidth_en","",0,], -["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",20171,], +["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",20182,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], -["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20168,], +["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20182,], ["libraries.designsystem.atomic.pages_SunsetPage_Day_0_en","libraries.designsystem.atomic.pages_SunsetPage_Night_0_en",0,], ["libraries.designsystem.components.button_SuperButton_Day_0_en","libraries.designsystem.components.button_SuperButton_Night_0_en",0,], ["libraries.designsystem.theme.components_Surface_en","",0,], ["libraries.designsystem.theme.components_Switch_Toggles_en","",0,], -["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20168,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20182,], ["libraries.designsystem.theme.components_TextButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20168,], -["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20168,], -["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20168,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20168,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20168,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20168,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20168,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20168,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20168,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20168,], +["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20182,], +["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20182,], +["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20182,], +["libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerEditNotEncrypted_Night_0_en",20182,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20182,], +["libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en",20182,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20182,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20182,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20182,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_0_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_10_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_11_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_1_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_2_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_3_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_4_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_5_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_6_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_7_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_8_en",20182,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_9_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20182,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20182,], +["libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerSimpleNotEncrypted_Night_0_en",20182,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20182,], +["libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerVoiceNotEncrypted_Night_0_en",20182,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], +["libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en",20185,], +["libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en",20185,], ["libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en","",0,], ["libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en","",0,], ["libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en","",0,], @@ -1198,14 +1223,14 @@ export const screenshots = [ ["libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_3_en","libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_3_en",0,], ["libraries.textcomposer.components_TextFormatting_Day_0_en","libraries.textcomposer.components_TextFormatting_Night_0_en",0,], ["libraries.designsystem.theme.components_TextLight_Text_en","",0,], -["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20168,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20168,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20168,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20182,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20182,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20182,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_0_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_1_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_2_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20168,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20168,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20182,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20182,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_5_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_6_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_7_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_7_en",0,], @@ -1215,18 +1240,18 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20168,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20182,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_0_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_1_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en",20168,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en",20182,], ["features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en","",0,], @@ -1234,18 +1259,18 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20168,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20168,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20182,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20182,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en",20171,], -["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20168,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20168,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en",20182,], +["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20182,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20182,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20168,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20168,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20182,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20182,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en",0,], @@ -1254,40 +1279,40 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20168,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20182,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20168,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20182,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20168,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20182,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20168,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20168,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20182,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20182,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemInformativeView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemInformativeView_Night_0_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20168,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20182,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20168,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20168,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20182,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20182,], ["features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20168,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20168,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20182,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20182,], ["features.messages.impl.timeline.components_TimelineItemReactionsView_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsView_Night_0_en",0,], -["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20168,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20182,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_0_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_0_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_1_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_1_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_2_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_2_en",0,], @@ -1296,8 +1321,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_5_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_5_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_6_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_6_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_7_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_7_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20168,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20168,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20182,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20182,], ["features.messages.impl.timeline.components_TimelineItemStateEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemStateEventRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStateView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStateView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStickerView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStickerView_Night_0_en",0,], @@ -1312,8 +1337,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_4_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_5_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20168,], -["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20168,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20182,], +["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20182,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_2_en",0,], @@ -1336,95 +1361,95 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemVoiceView_Day_9_en","features.messages.impl.timeline.components.event_TimelineItemVoiceView_Night_9_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en",0,], -["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20168,], +["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20182,], ["features.messages.impl.timeline_TimelineView_Day_10_en","features.messages.impl.timeline_TimelineView_Night_10_en",0,], -["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20168,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20168,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20182,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20182,], ["features.messages.impl.timeline_TimelineView_Day_2_en","features.messages.impl.timeline_TimelineView_Night_2_en",0,], ["features.messages.impl.timeline_TimelineView_Day_3_en","features.messages.impl.timeline_TimelineView_Night_3_en",0,], -["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20168,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20182,], ["features.messages.impl.timeline_TimelineView_Day_5_en","features.messages.impl.timeline_TimelineView_Night_5_en",0,], -["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20168,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20182,], ["features.messages.impl.timeline_TimelineView_Day_7_en","features.messages.impl.timeline_TimelineView_Night_7_en",0,], -["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20168,], +["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20182,], ["features.messages.impl.timeline_TimelineView_Day_9_en","features.messages.impl.timeline_TimelineView_Night_9_en",0,], ["libraries.designsystem.theme.components_TopAppBar_App_Bars_en","",0,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20168,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20168,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20182,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20182,], ["features.messages.impl.typing_TypingNotificationView_Day_0_en","features.messages.impl.typing_TypingNotificationView_Night_0_en",0,], -["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20168,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20168,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20168,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20168,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20168,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20168,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20182,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20182,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20182,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20182,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20182,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20182,], ["features.messages.impl.typing_TypingNotificationView_Day_7_en","features.messages.impl.typing_TypingNotificationView_Night_7_en",0,], ["features.messages.impl.typing_TypingNotificationView_Day_8_en","features.messages.impl.typing_TypingNotificationView_Night_8_en",0,], ["libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en",0,], -["libraries.matrix.ui.components_UnresolvedUserRow_en","",20168,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",20182,], ["libraries.matrix.ui.components_UnsavedAvatar_Day_0_en","libraries.matrix.ui.components_UnsavedAvatar_Night_0_en",0,], ["libraries.designsystem.components.avatar_UserAvatarColors_Day_0_en","libraries.designsystem.components.avatar_UserAvatarColors_Night_0_en",0,], -["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20168,], -["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",20168,], -["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",20168,], -["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",20168,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20182,], +["features.createroom.impl.components_UserListView_Day_0_en","features.createroom.impl.components_UserListView_Night_0_en",20182,], +["features.createroom.impl.components_UserListView_Day_1_en","features.createroom.impl.components_UserListView_Night_1_en",20182,], +["features.createroom.impl.components_UserListView_Day_2_en","features.createroom.impl.components_UserListView_Night_2_en",20182,], ["features.createroom.impl.components_UserListView_Day_3_en","features.createroom.impl.components_UserListView_Night_3_en",0,], ["features.createroom.impl.components_UserListView_Day_4_en","features.createroom.impl.components_UserListView_Night_4_en",0,], ["features.createroom.impl.components_UserListView_Day_5_en","features.createroom.impl.components_UserListView_Night_5_en",0,], ["features.createroom.impl.components_UserListView_Day_6_en","features.createroom.impl.components_UserListView_Night_6_en",0,], -["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",20168,], +["features.createroom.impl.components_UserListView_Day_7_en","features.createroom.impl.components_UserListView_Night_7_en",20182,], ["features.createroom.impl.components_UserListView_Day_8_en","features.createroom.impl.components_UserListView_Night_8_en",0,], -["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",20168,], +["features.createroom.impl.components_UserListView_Day_9_en","features.createroom.impl.components_UserListView_Night_9_en",20182,], ["features.preferences.impl.user_UserPreferences_Day_0_en","features.preferences.impl.user_UserPreferences_Night_0_en",0,], ["features.preferences.impl.user_UserPreferences_Day_1_en","features.preferences.impl.user_UserPreferences_Night_1_en",0,], ["features.preferences.impl.user_UserPreferences_Day_2_en","features.preferences.impl.user_UserPreferences_Night_2_en",0,], -["features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en","features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en",20168,], -["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20168,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20168,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20168,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20168,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20168,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20168,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20168,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20168,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20168,], -["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",20168,], -["features.userprofile.shared_UserProfileView_Day_9_en","features.userprofile.shared_UserProfileView_Night_9_en",20168,], +["features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en","features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en",20182,], +["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20182,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20182,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20182,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20182,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20182,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20182,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20182,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20182,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20182,], +["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",20182,], +["features.userprofile.shared_UserProfileView_Day_9_en","features.userprofile.shared_UserProfileView_Night_9_en",20182,], ["features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en","features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en",0,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en",20168,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en",20182,], ["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_12_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_12_en",0,], ["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en",0,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en",20168,], -["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en",20168,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en",20182,], +["features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en","features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en",20182,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_2_en","features.viewfolder.impl.file_ViewFileView_Night_2_en",0,], -["features.viewfolder.impl.file_ViewFileView_Day_3_en","features.viewfolder.impl.file_ViewFileView_Night_3_en",20171,], +["features.viewfolder.impl.file_ViewFileView_Day_3_en","features.viewfolder.impl.file_ViewFileView_Night_3_en",20182,], ["features.viewfolder.impl.file_ViewFileView_Day_4_en","features.viewfolder.impl.file_ViewFileView_Night_4_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_5_en","features.viewfolder.impl.file_ViewFileView_Night_5_en",0,], ["features.viewfolder.impl.folder_ViewFolderView_Day_0_en","features.viewfolder.impl.folder_ViewFolderView_Night_0_en",0,], @@ -1443,6 +1468,6 @@ export const screenshots = [ ["libraries.textcomposer.components_VoiceMessageRecording_Day_0_en","libraries.textcomposer.components_VoiceMessageRecording_Night_0_en",0,], ["libraries.textcomposer.components_VoiceMessage_Day_0_en","libraries.textcomposer.components_VoiceMessage_Night_0_en",0,], ["libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en","libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en",0,], -["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",20168,], +["features.ftue.impl.welcome_WelcomeView_Day_0_en","features.ftue.impl.welcome_WelcomeView_Night_0_en",20182,], ["libraries.designsystem.ruler_WithRulers_Day_0_en","libraries.designsystem.ruler_WithRulers_Night_0_en",0,], ]; diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt index 9ff4053b33..750a6d1d17 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt @@ -19,9 +19,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { fun getAvailableAnalyticsProviders(): Set /** - * Return a Flow of Boolean, true if the user has given their consent. + * A Flow of Boolean, true if the user has given their consent. */ - fun getUserConsent(): Flow + val userConsentFlow: Flow /** * Update the user consent value. @@ -29,9 +29,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { suspend fun setUserConsent(userConsent: Boolean) /** - * Return a Flow of Boolean, true if the user has been asked for their consent. + * A Flow of Boolean, true if the user has been asked for their consent. */ - fun didAskUserConsent(): Flow + val didAskUserConsentFlow: Flow /** * Store the fact that the user has been asked for their consent. @@ -39,9 +39,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { suspend fun setDidAskUserConsent() /** - * Return a Flow of String, used for analytics Id. + * A Flow of String, used for analytics Id. */ - fun getAnalyticsId(): Flow + val analyticsIdFlow: Flow /** * Update analyticsId from the AccountData. diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt index 6dafd7f334..5ee74860aa 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt @@ -43,6 +43,10 @@ class DefaultAnalyticsService @Inject constructor( // Cache for the properties to send private var pendingUserProperties: UserProperties? = null + override val userConsentFlow: Flow = analyticsStore.userConsentFlow + override val didAskUserConsentFlow: Flow = analyticsStore.didAskUserConsentFlow + override val analyticsIdFlow: Flow = analyticsStore.analyticsIdFlow + init { observeUserConsent() observeSessions() @@ -52,19 +56,11 @@ class DefaultAnalyticsService @Inject constructor( return analyticsProviders } - override fun getUserConsent(): Flow { - return analyticsStore.userConsentFlow - } - override suspend fun setUserConsent(userConsent: Boolean) { Timber.tag(analyticsTag.value).d("setUserConsent($userConsent)") analyticsStore.setUserConsent(userConsent) } - override fun didAskUserConsent(): Flow { - return analyticsStore.didAskUserConsentFlow - } - override suspend fun setDidAskUserConsent() { Timber.tag(analyticsTag.value).d("setDidAskUserConsent()") analyticsStore.setDidAskUserConsent() @@ -74,10 +70,6 @@ class DefaultAnalyticsService @Inject constructor( analyticsStore.setDidAskUserConsent(false) } - override fun getAnalyticsId(): Flow { - return analyticsStore.analyticsIdFlow - } - override suspend fun setAnalyticsId(analyticsId: String) { Timber.tag(analyticsTag.value).d("setAnalyticsId($analyticsId)") analyticsStore.setAnalyticsId(analyticsId) @@ -93,7 +85,7 @@ class DefaultAnalyticsService @Inject constructor( } private fun observeUserConsent() { - getUserConsent() + userConsentFlow .onEach { consent -> Timber.tag(analyticsTag.value).d("User consent updated to $consent") userConsent.set(consent) diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt index 1b00b238eb..e05a6e4208 100644 --- a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt @@ -132,10 +132,10 @@ class DefaultAnalyticsServiceTest { analyticsStore = store, ) assertThat(store.userConsentFlow.first()).isFalse() - assertThat(sut.getUserConsent().first()).isFalse() + assertThat(sut.userConsentFlow.first()).isFalse() sut.setUserConsent(true) assertThat(store.userConsentFlow.first()).isTrue() - assertThat(sut.getUserConsent().first()).isTrue() + assertThat(sut.userConsentFlow.first()).isTrue() } @Test @@ -146,10 +146,10 @@ class DefaultAnalyticsServiceTest { analyticsStore = store, ) assertThat(store.analyticsIdFlow.first()).isEqualTo("") - assertThat(sut.getAnalyticsId().first()).isEqualTo("") + assertThat(sut.analyticsIdFlow.first()).isEqualTo("") sut.setAnalyticsId(AN_ID) assertThat(store.analyticsIdFlow.first()).isEqualTo(AN_ID) - assertThat(sut.getAnalyticsId().first()).isEqualTo(AN_ID) + assertThat(sut.analyticsIdFlow.first()).isEqualTo(AN_ID) } @Test @@ -160,10 +160,10 @@ class DefaultAnalyticsServiceTest { analyticsStore = store, ) assertThat(store.didAskUserConsentFlow.first()).isFalse() - assertThat(sut.didAskUserConsent().first()).isFalse() + assertThat(sut.didAskUserConsentFlow.first()).isFalse() sut.setDidAskUserConsent() assertThat(store.didAskUserConsentFlow.first()).isTrue() - assertThat(sut.didAskUserConsent().first()).isTrue() + assertThat(sut.didAskUserConsentFlow.first()).isTrue() } @Test diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt index eb05c5c151..9af15543f0 100644 --- a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt @@ -24,11 +24,11 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class NoopAnalyticsService @Inject constructor() : AnalyticsService { override fun getAvailableAnalyticsProviders(): Set = emptySet() - override fun getUserConsent(): Flow = flowOf(false) + override val userConsentFlow: Flow = flowOf(false) override suspend fun setUserConsent(userConsent: Boolean) = Unit - override fun didAskUserConsent(): Flow = flowOf(true) + override val didAskUserConsentFlow: Flow = flowOf(true) override suspend fun setDidAskUserConsent() = Unit - override fun getAnalyticsId(): Flow = flowOf("") + override val analyticsIdFlow: Flow = flowOf("") override suspend fun setAnalyticsId(analyticsId: String) = Unit override suspend fun reset() = Unit override fun capture(event: VectorAnalyticsEvent) = Unit diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index d60cc8f633..081f66d4e6 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -15,6 +15,7 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.AnalyticsProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow class FakeAnalyticsService( isEnabled: Boolean = false, @@ -22,7 +23,7 @@ class FakeAnalyticsService( private val resetLambda: () -> Unit = {}, ) : AnalyticsService { private val isEnabledFlow = MutableStateFlow(isEnabled) - private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) + override val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) val capturedEvents = mutableListOf() val screenEvents = mutableListOf() val trackedErrors = mutableListOf() @@ -30,19 +31,17 @@ class FakeAnalyticsService( override fun getAvailableAnalyticsProviders(): Set = emptySet() - override fun getUserConsent(): Flow = isEnabledFlow + override val userConsentFlow: Flow = isEnabledFlow.asStateFlow() override suspend fun setUserConsent(userConsent: Boolean) { isEnabledFlow.value = userConsent } - override fun didAskUserConsent(): Flow = didAskUserConsentFlow - override suspend fun setDidAskUserConsent() { didAskUserConsentFlow.value = true } - override fun getAnalyticsId(): Flow = MutableStateFlow("") + override val analyticsIdFlow: Flow = MutableStateFlow("") override suspend fun setAnalyticsId(analyticsId: String) { } diff --git a/services/analyticsproviders/posthog/build.gradle.kts b/services/analyticsproviders/posthog/build.gradle.kts index fb59fb842e..01e6dbc836 100644 --- a/services/analyticsproviders/posthog/build.gradle.kts +++ b/services/analyticsproviders/posthog/build.gradle.kts @@ -1,3 +1,5 @@ +import config.BuildTimeConfig +import extension.buildConfigFieldStr import extension.setupAnvil /* @@ -12,6 +14,21 @@ plugins { android { namespace = "io.element.android.services.analyticsproviders.posthog" + + buildFeatures { + buildConfig = true + } + + defaultConfig { + buildConfigFieldStr( + name = "POSTHOG_HOST", + value = BuildTimeConfig.SERVICES_POSTHOG_HOST.takeIf { isEnterpriseBuild } ?: "" + ) + buildConfigFieldStr( + name = "POSTHOG_APIKEY", + value = BuildTimeConfig.SERVICES_POSTHOG_APIKEY.takeIf { isEnterpriseBuild } ?: "" + ) + } } setupAnvil() @@ -21,6 +38,7 @@ dependencies { implementation(libs.posthog) { exclude("com.android.support", "support-annotations") } + implementation(projects.features.enterprise.api) implementation(projects.libraries.core) implementation(projects.libraries.di) implementation(projects.services.analyticsproviders.api) diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt index 2e87236741..af450cfc84 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt @@ -11,7 +11,6 @@ import android.content.Context import com.posthog.PostHogInterface import com.posthog.android.PostHogAndroid import com.posthog.android.PostHogAndroidConfig -import io.element.android.libraries.core.extensions.isElement import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.ApplicationContext import javax.inject.Inject @@ -22,8 +21,7 @@ class PostHogFactory @Inject constructor( private val posthogEndpointConfigProvider: PosthogEndpointConfigProvider, ) { fun createPosthog(): PostHogInterface? { - if (!buildMeta.isElement()) return null - val endpoint = posthogEndpointConfigProvider.provide() + val endpoint = posthogEndpointConfigProvider.provide() ?: return null return PostHogAndroid.with( context, PostHogAndroidConfig( diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt index c699950f7e..6d14ab6b14 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt @@ -10,4 +10,6 @@ package io.element.android.services.analyticsproviders.posthog data class PosthogEndpointConfig( val host: String, val apiKey: String, -) +) { + val isValid = host.isNotBlank() && apiKey.isNotBlank() +} diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt index 2a4b80a542..da35d661e5 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt @@ -7,24 +7,40 @@ package io.element.android.services.analyticsproviders.posthog +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.core.extensions.isElement import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import javax.inject.Inject class PosthogEndpointConfigProvider @Inject constructor( private val buildMeta: BuildMeta, + private val enterpriseService: EnterpriseService, ) { - fun provide(): PosthogEndpointConfig { - return when (buildMeta.buildType) { - BuildType.RELEASE -> PosthogEndpointConfig( - host = "https://posthog.element.io", - apiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", - ) - BuildType.NIGHTLY, - BuildType.DEBUG -> PosthogEndpointConfig( - host = "https://posthog.element.dev", - apiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", - ) + fun provide(): PosthogEndpointConfig? { + return if (enterpriseService.isEnterpriseBuild) { + PosthogEndpointConfig( + host = BuildConfig.POSTHOG_HOST, + apiKey = BuildConfig.POSTHOG_APIKEY, + ).takeIf { + // Note that if the config is invalid, this module will not be included in the build. + // So the configuration should be always valid. + it.isValid + } + } else if (buildMeta.isElement()) { + when (buildMeta.buildType) { + BuildType.RELEASE -> PosthogEndpointConfig( + host = "https://posthog.element.io", + apiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", + ) + BuildType.NIGHTLY, + BuildType.DEBUG -> PosthogEndpointConfig( + host = "https://posthog.element.dev", + apiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", + ) + } + } else { + null } } } diff --git a/services/analyticsproviders/sentry/build.gradle.kts b/services/analyticsproviders/sentry/build.gradle.kts index e144cb67ea..1b1c351712 100644 --- a/services/analyticsproviders/sentry/build.gradle.kts +++ b/services/analyticsproviders/sentry/build.gradle.kts @@ -1,3 +1,5 @@ +import config.BuildTimeConfig +import extension.buildConfigFieldStr import extension.readLocalProperty import extension.setupAnvil @@ -19,13 +21,15 @@ android { } defaultConfig { - buildConfigField( - type = "String", + buildConfigFieldStr( name = "SENTRY_DSN", - value = (System.getenv("ELEMENT_ANDROID_SENTRY_DSN") - ?: readLocalProperty("services.analyticsproviders.sentry.dsn") + value = if (isEnterpriseBuild) { + BuildTimeConfig.SERVICES_SENTRY_DSN + } else { + System.getenv("ELEMENT_ANDROID_SENTRY_DSN") + ?: readLocalProperty("services.analyticsproviders.sentry.dsn") + } ?: "" - ).let { "\"$it\"" } ) } } diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt index ede40e3051..654d54f56b 100644 --- a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt @@ -41,9 +41,7 @@ class SentryAnalyticsProvider @Inject constructor( Timber.tag(analyticsTag.value).d("Initializing Sentry") if (Sentry.isEnabled()) return - val dsn = if (SentryConfig.DSN.isNotBlank()) { - SentryConfig.DSN - } else { + val dsn = SentryConfig.DSN.ifBlank { Timber.w("No Sentry DSN provided, Sentry will not be initialized") return } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt new file mode 100644 index 0000000000..b9cca40740 --- /dev/null +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.tests.konsist + +import androidx.compose.runtime.Composable +import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.ext.list.withAnnotationOf +import com.lemonappdev.konsist.api.verify.assertFalse +import org.junit.Test + +class KonsistFlowTest { + @Test + fun `flow must be remembered when it is collected as state`() { + // Match + // ```).collectAsState``` + // and + // ```) + // .collectAsState``` + val regex = "(.*)\\)(\n\\s*)*\\.collectAsState".toRegex() + + Konsist + .scopeFromProject() + .functions() + .withAnnotationOf(Composable::class) + .assertFalse( + additionalMessage = "Please check that the flow is remembered when it is collected as state." + + " Only val flows can be not remembered.", + ) { function -> + regex.matches(function.text) + } + } +} diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt index cb996db3b9..129f9150ed 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt @@ -24,4 +24,17 @@ class KonsistImportTest { it.name == "org.jetbrains.annotations.VisibleForTesting" } } + + @Test + fun `OutlinedTextField should not be used`() { + Konsist + .scopeFromProject() + .imports + .assertFalse( + additionalMessage = "Please use 'io.element.android.libraries.designsystem.theme.components.TextField' instead of " + + "'androidx.compose.material3.OutlinedTextField.", + ) { + it.name == "androidx.compose.material3.OutlinedTextField" + } + } } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index d3d7a1097b..3bf65574ed 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -120,6 +120,8 @@ class KonsistPreviewTest { "TextComposerSimpleNotEncryptedPreview", "TextComposerVoicePreview", "TextComposerVoiceNotEncryptedPreview", + "TextFieldDialogWithBorderPreview", + "TextFieldDialogWithErrorPreview", "TimelineImageWithCaptionRowPreview", "TimelineItemEventRowForDirectRoomPreview", "TimelineItemEventRowShieldPreview", diff --git a/tests/uitests/src/test/snapshots/images/appicon.element_Icon_en.png b/tests/uitests/src/test/snapshots/images/appicon.element_Icon_en.png index 97ef3ac651..243b33fecd 100644 --- a/tests/uitests/src/test/snapshots/images/appicon.element_Icon_en.png +++ b/tests/uitests/src/test/snapshots/images/appicon.element_Icon_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f43597d36fd123dc4ec478ed8ddc3160d383881623d613b8ab8045f042b80cb -size 44846 +oid sha256:96db51e34f64dc2fc5987b1b0ccfa94e866facb37821278cbd4d56247b2f63d2 +size 13785 diff --git a/tests/uitests/src/test/snapshots/images/appicon.element_RoundIcon_en.png b/tests/uitests/src/test/snapshots/images/appicon.element_RoundIcon_en.png index 5811dfb607..70625c9bbc 100644 --- a/tests/uitests/src/test/snapshots/images/appicon.element_RoundIcon_en.png +++ b/tests/uitests/src/test/snapshots/images/appicon.element_RoundIcon_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74af6c3ed723f027bb38b2fd7c4b0441ff26149abb587058bd2bb5be9adbe63b -size 40692 +oid sha256:8526150ec60c72842c4354e5c1d5f240813af6110e9f32f515e14954136ba514 +size 16123 diff --git a/tests/uitests/src/test/snapshots/images/features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en.png new file mode 100644 index 0000000000..76a832569b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6241486ded79ef2c079dcc45a0fe97bf01db66c05712ec7f74db035d76a91b5 +size 18115 diff --git a/tests/uitests/src/test/snapshots/images/features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en.png new file mode 100644 index 0000000000..af54ab708a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ca4b3953ec0c460052305d9cfa5c0b1db2a97e8d9a27b483e8b5d15d873d32c +size 17500 diff --git a/tests/uitests/src/test/snapshots/images/features.analytics.impl_AnalyticsOptInView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.analytics.impl_AnalyticsOptInView_Day_1_en.png new file mode 100644 index 0000000000..4ca3de0a14 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.analytics.impl_AnalyticsOptInView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:232fcbe898588450577fca29090873626c2df7cc4afba91a87d3956d29f0afbc +size 81150 diff --git a/tests/uitests/src/test/snapshots/images/features.analytics.impl_AnalyticsOptInView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.analytics.impl_AnalyticsOptInView_Night_1_en.png new file mode 100644 index 0000000000..c3a8af5c2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.analytics.impl_AnalyticsOptInView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26f4a151850c4c21ed57d0a1d36ea72aece585ed5ec6d54303edbeaa91d06df9 +size 73959 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png index dcb5fb0de3..e0851891b2 100644 --- a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5248793987bd2f6f98e571366e6fcc87085fdba5372a4a31314a52a13efcc78a -size 30160 +oid sha256:72a0143f03e008d0961066cd600d7567c5a64b36d8d4ed610324ae68b15660ef +size 30061 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png index bf50b6961e..debbf12ddf 100644 --- a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d835fa6c041ca10d362e7c0dd77fe784e2f3b903523b9a813e1cd47a04f53df -size 31365 +oid sha256:d6c2cbf04f228dc6d63660d4a0d5bd7a56e2dfa7aba0778e01860d99017d25e4 +size 31325 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png index af20cfd8a5..6fea0e40d1 100644 --- a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7363ab9655be9350ff4f10eda2278962489b8fd983a7b3f5a91b40f281bbb37f -size 29379 +oid sha256:fd5044ab9be16487d6637d80aa81d2d70f48596f9bd9db2049cc152145ab5b06 +size 29268 diff --git a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png index 743e065990..88a2f62577 100644 --- a/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.licenses.impl.list_DependencyLicensesListView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4290ae65632bee52d5c7ea6cb34de07e8fb585398edccb1a411d669410d8299 -size 30547 +oid sha256:8ecfa7ad0239499dfb3611b0049244d46d2dc795906bf5e5afdefd0bf9ede43f +size 30464 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.createaccount_CreateAccountView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.createaccount_CreateAccountView_Night_1_en.png index ecd3fa6368..635e8b6f9b 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.createaccount_CreateAccountView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.createaccount_CreateAccountView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:705f53f174aeaa0173b618adbb718d86b7d1a25767aa2801866a154f365acf9a -size 13361 +oid sha256:e7818e342a7550aa0c0a2375c84e7911ccea44371366e07efe17a5fdca688216 +size 13360 diff --git a/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Day_10_en.png new file mode 100644 index 0000000000..b159547818 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Day_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b005a634331a485e2f80fb773a147207c782eaf2d923d1a87c5b45597309c658 +size 25794 diff --git a/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Day_11_en.png new file mode 100644 index 0000000000..e6d14a0cd4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Day_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d800ec0c02e8e2f8fff755d12d2e657d6fbae10a8b9f070bb627b3ccf9d3e159 +size 30566 diff --git a/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_10_en.png new file mode 100644 index 0000000000..9e60f85a51 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27587909106650e33d7bc7854c0f2dd7ca6e2dad0aaf6487bf266753288ec6f6 +size 24898 diff --git a/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_11_en.png new file mode 100644 index 0000000000..960535f462 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e65635400bfda5d242d07ebe31113e3ef2d039f21208421023f099997d5e4f9 +size 29603 diff --git a/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_2_en.png index 00205ae669..33ced2e5b0 100644 --- a/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.logout.impl_LogoutView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fac219396f825debaace2abd3ca00cae6fc7452b4b8f5c5c7e43568a2ded41d -size 26494 +oid sha256:e184ee4a9687e98d24c6563319c9266cb5609db66d1fcbcdb02e9fca11504bee +size 26501 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png index e4e07f7992..b63cda070b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec8ac8528bdb81076828f1b3a87b968ca41b21e59b6ac9c2b5c28db969a08952 -size 54998 +oid sha256:f1e9382ed4ffc09e206928c33c667cb150dffc830f6215e06c9dd5ea520de4a8 +size 54986 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png index 837307a6e9..a829e942df 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61fb04355b0984f6a2b2cae6bb415936f9144993d988cea55c4db4f25efddb0c -size 63502 +oid sha256:955c4cce20c5e9f3457923ff76f22488445a891f145f0161cd0ec94c3da2bc55 +size 63485 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png index 4859bd03f0..c85914d89f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bef590d2520c22e8fc4cab2a2f39917ebcfa95e403bf461f0eaebe21b459f57 -size 63906 +oid sha256:47a2a121c571b1b33f24f1e60d00937bdd2f327726487b4a63b269ce3cdec73f +size 63891 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png index f3c3b76e87..92ed199400 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0abcce8207b0f2ff7690e80a64a377eefa9aacb3a2c49ea7d22b452c117d6ce0 -size 55455 +oid sha256:d1cdf81418927c64c9cb3bc97828f251da3116f09841ec0bfa2164e321058d67 +size 55435 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png index 39903af680..6e52c8dd16 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c82612aeb3d83ea4e619e0fbec4bffcbea4589d4af362028caef23346293beea -size 67399 +oid sha256:d568e0e6f80c6161ca33f51106268f5ef95e33bd21923c3dd734f8f67dd51609 +size 67392 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png index 1a8b108573..4f4c950f62 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b83f5593876ecfb9b94e648ca4ca95122e9a75ba6b660a2c529cea87c9d761f4 -size 66928 +oid sha256:5e09247630bfb3b01f581b347bf9a42e8bafa96de707187c5422bb19e8844d82 +size 66887 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en.png index d9867a8626..f2f01538d4 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f26c0bb0e010c6d44df2854537ef625712bfa4a6e3ba73960589fe7c811ce542 -size 42669 +oid sha256:622ce8af67b17db0b7f729422ccc311f14c258d900c469901e8ba646d42ad031 +size 42659 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en.png index ec76394776..e2835a19a1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ab7bda826642ec13184d7d5513924979e9b7534cad4a783f8d7a323be31b85a -size 41571 +oid sha256:29ddafc09a16e878cad38d5f1fbc8e18b9936f2ecb3b7dc188178f6a424ddd05 +size 41539 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en.png index dc5707034c..78d8d677b0 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e10183cfe96bd34339c29a1709a16b9a38f72235d484415b9f1c172d4456c798 -size 45704 +oid sha256:3e778a1a3590c849db350d2d801e600555fe46286d132833ae8243aa7cbaa70e +size 45696 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en.png index 8bc1bf8e42..4e58b49638 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ce5b2231af4331fb8d2d6b99778245b8d2972239a35fbdce6c35d7d6e53c1cd -size 47896 +oid sha256:dbf96dcc3c3e9b9cb6dc3300ebe00017d898515a07df85c0ac26a5cc452cab86 +size 47887 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en.png index a3f97b45e5..dda4ae9673 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09d954a08c15111f23012f37e9e8c7e2978660fb21c9bb4e1bb765086203725c -size 47898 +oid sha256:aeabb30b46e100487def9cedff50627d18a73371705dec42941db1bf9bfda5ec +size 47890 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en.png index 572a3dcc1f..df6dbe9e87 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa163abb68b57d6e123e5e58aacb1df194ded7f84ad56f813275d4ecd16ce131 -size 47979 +oid sha256:234750e0c8d262c520cea6b25866df2b23399643efb6ff9ab2c68e1edda4630c +size 47971 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png index 9401edfd5d..22c3782785 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a1e901fdbfdbf5621ad6446dec6f83b922ffcac40c649981306037776a288a7 -size 169049 +oid sha256:a5bb21840d22553fc7db6252f294a2a05971205bac00506b44a6255e93ba1590 +size 169035 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png index ecf9f575fe..864e2992db 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5dcb2ba2bfa0560f69ef6c7dd6a9f34c2a864d502d9401626666f263931f8a1 -size 168173 +oid sha256:d0f667300929a4961a4ac20196a010d001b5eba035086196bfdeb067f4b74f4d +size 168152 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en.png index cf4d2e1da5..bbdd286574 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85536b4f83b45d7688fdd367b9f8766a6febf55a35e033357116664f253ed7de -size 15779 +oid sha256:839f50a35d5e9f13051a233a4d4bb75cb5de245efd16937b856c6e05768165cd +size 15720 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en.png index 1e1d23d6cf..07bcfd74dd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:928e775b4328cbcdfc8f8c13099fd6a53048deac7a876a0651a772dd854cd02d -size 28576 +oid sha256:cd8fe1751c30b5cbc6a9d31227da02b0eb647fed993134ecdfee360f68198410 +size 28544 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en.png index 7150b21f46..5b5bf2452e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48be34a388856f526ab23f35203cc07939b6bdbfd5b9f3a4163117301411828a -size 31148 +oid sha256:f2ab83152164617d6a76f84fcb92bc6c16e102c58fb489bb504b3c914a326a20 +size 31111 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en.png index 248ed805a9..8a84354ddb 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3c8e90e16e566d2252f67ba727ac7b52dd77bbe139c06d365f298bc40bb531e -size 30263 +oid sha256:500cf2222f5f0d35a7773f42de8f547bd6a81e447dd5ca383ba3d0e3ee208c0e +size 30225 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en.png index 4c7f4dce27..863589bcc1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96ea6176a048b8d47637d96345e7d3f64cfa6f2c0942ee022dafeef080323f66 -size 32592 +oid sha256:afc37df583c55bd7ca8295da4dc6148bc5e8fdb01612b2563cdd8108151227ff +size 32541 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en.png index 37cb67763d..7f4ee2edb6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e77fd59aa1658b10257b03df34b08120b001515b23ee476789327575588d29e4 -size 33966 +oid sha256:2e50ab664fe28a486af410bbd93074d8d42dfa578627285f8ad22496755f01c5 +size 33929 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en.png index 9678512c21..18f3b8cbc1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7a5a6750213db875928a93c8116618a0194df089658763e570effd7a288b51d -size 30706 +oid sha256:87f484e7ee64d776f4874883bdac7fc321a04af86e3a29bcc93a4ca2cf2e70aa +size 30666 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en.png index 15266c43a7..10cc5a767f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f941e6251118c39749ef6a7d2357c1feb0789c80bd0086e0faba89e35768c7a0 -size 30577 +oid sha256:a7ba63ae1a0d204db4318a0ec1041532e1b0ce2327908f37f494da04c5a5b975 +size 30533 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en.png index 5bcb370105..b647981436 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8cf8e9260385fdc03b2bb611a7c79af93190b7f6c9cfbb53d45afebd0590d08 -size 11279 +oid sha256:c87881859fcb3b3c08e4e583604ca455e2f178ea605362f255af8950d069095d +size 11267 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en.png index 4f781b50e2..12c46fb9d1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:658f03aa4e13b408243826ff0f1658e354a44402c3b39eabd229e5cf086201df -size 29136 +oid sha256:8b03dfd48c56e2d0bb108a147b2ee04cc66c165bce1da7c724e1ecb4ddb4d286 +size 29139 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en.png index c50e6fddb9..8c64ac2fa6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bbc4e17b8c1965df0dcc01fd7b95e5eb16589822d3212199cf6402d44a2496c -size 31814 +oid sha256:561714bc8011f7f56d89565f1e0e529ea3626aa5162da47058d4469f16af0641 +size 31742 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en.png index 0eabe29425..d91da94837 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1072540b8301f94898c031591bc34b3dbd80de0e297d08fe70ad6beaca4d796b -size 30581 +oid sha256:94e94d793eb718cccc535a58053d5b4dc2de3b65b007017a8c5647112c78cedb +size 30527 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en.png index ec53631cee..60aee97b69 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c9fb8c24b7259894d5feee5c82b0d0e43487ce32ec37208bb4adf451988351a -size 32895 +oid sha256:a9d8e84329989a02b19269f6294b209fb66c5f3f6a35de7f9a2eee6da517dcdf +size 32903 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en.png index afc9b879a9..ca990cae35 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b51df4b986ff3ffcd15d2eebbac571ac9591fede464429d7d4eddc459f4b63c -size 33905 +oid sha256:25d0849402abdc3639c8bef1da49daf4de20124f367b3e1e413b0dcbf53bbff7 +size 33863 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en.png index f5863c8d04..8d28ce67f5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a5bc7b480b31995f7828229b6cc70b12688552cd08ed2ffd35e66fca5539101 -size 31076 +oid sha256:ee7292caede2b69969c7f62bf025b3cff5b59ab99eceac51ff041e6fddea9389 +size 31082 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en.png index 868cfa4cba..37791be7d2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c51a2fde76e7df75648b352068500488eed55128177be165e560737d6b1735a4 -size 30893 +oid sha256:898ff20bd42490e66ece05e6b6435ae951b004b69b44855ce9c1f0b0ab06d635 +size 30894 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en.png index d576503af4..bcf06c96f2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf4558bdae3a8f6e04a3390e15194cef3faa7552f5c0fd772eb8b4a7b9954de2 -size 11234 +oid sha256:14bd470786ea2a00673adec49d674fd2e575512187056a7c3fd56bf7b4140841 +size 11204 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en.png index 46a429e910..02e8c70de9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e22060e45416b2af1d660c4185e98087c915fa0a48a488797a3db0d5661744c -size 30285 +oid sha256:8fd4bb89758d8253aabbcfcee28d2b7962c97394fd2c9bd37bfda15dd8b59abe +size 30255 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en.png index b02e2c7895..f9c581a64e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6321ec6e70dd488fedf85f44659cc1a29f5fba74e7232ce3bf8b2ec376f9423 -size 29457 +oid sha256:6e6b5ee7f8475aa22f47ca7643f6cf93a2f170710859c22e1c71026fdc1721a5 +size 29445 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en.png index 272335c611..d9f727f261 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9a29a07c7336014004203fef64b0eb429fb29f4a03b9ea3df7386dfae384345 -size 80140 +oid sha256:dc29aa14fd015ea0a4a2783ff553c086b41c86757aae760b64451f78deca9d45 +size 80122 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en.png index a23340aa4b..d622d385b0 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0764beebe1397ea7382768f676f330c1ccbdf2f326199053c5165b41816d8650 -size 80523 +oid sha256:9adab952ced4b0c51b0e3a5eb8c6cc7c8c717d79dc77ae1eeef328119ebcd597 +size 80489 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en.png index 3647b9f040..3cd2696467 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fc8a84a49d10311e340f42271618c8046d30430d7e246453d646488120e687b -size 25554 +oid sha256:7d46bea9b9177b9f9ce4e08d8a33fa0161662c1611d5ffcd2aeb8aae1d1ad441 +size 25553 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en.png index 2840251a80..197785a217 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1999b03d5fb6d2bee51438dd7f4cb3f088ac4f9bc4f6378931ad5cd7166d0d4 -size 25020 +oid sha256:dc61b0fcd136cc1087dd0449d80930b86b2f8c8440b55b756450ec26829bd9ea +size 25015 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en.png index 5bfa10ac6d..f0dd1f8797 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1950690f0b07d0cdea54f486832e2c89ec7c633d745bc2849185d3e7f14bcf86 -size 29316 +oid sha256:2b1d4cecb06128bfc3de98a1a927515d2770220927bb11bb4be040c8ac365274 +size 29310 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en.png index afa0ff2a9d..3520dbd9ca 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73e30666a17ecb9fd9a1d9673e9fa96f3fc5457b6e8b13c27faae34ca7e507be -size 25111 +oid sha256:e56a10732744eca3e422be7c7509d2e107b0b93b657fba610812d3c351bfabbe +size 25088 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en.png index 77964f5f22..9b134ca9cc 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fd66c7ffe0afbdb4d2f89ebf9340ddccfed0418bc6d6cb4d98a9925e2d1a762 -size 24558 +oid sha256:55b22ee6757a6cafb99aa4f35601638dc3dac31c09b0369187379c78583b8e1d +size 24533 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en.png index 72a7a31c3e..a8795e631c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6eeff40cf2e0457b210a784bd5f73303efaa3356ff650004c6de3b9dc9138c67 -size 29911 +oid sha256:25dfa78e2b466c3bb708c3a08dc02ec4428a9cb82fb4339b348eb1009cc46ffb +size 29875 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png index 71b8c10e02..04b0ea1b72 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa52e189e42c448cd680f45fd8a20893be726feb2fa61f8ad8c2199712ecf431 -size 151398 +oid sha256:52839c50b1ebdf1a46e105045ae03b4ef2cf6a5e1e8cc842c2329e9da395eb81 +size 151404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png index f7eed329d5..e4f63cc390 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aa1a281092b29a8b0b4be292e11a46ae520e09783fcafb1a75c7912ec71878c -size 156896 +oid sha256:b420c048cb72dee9387bb1f8b32e0cda7d703569f67416d35c4f6e0cdfeef0c4 +size 156859 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png index 9635d7b007..66cb2b3a6c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c72e79524b0f37dca8cca7863c5a5d5e3d77f7845bd65b5f7bdf8ef47121cc94 -size 151435 +oid sha256:226ee1a4d618dbfaa6a9fa693af46daaf25dc17fc7fad5190ea2b36473a3963d +size 151407 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png index 1965d77e3e..624b83fc6b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad1f8892740d499197491a7826f1bcbb0d762624519b5cb693487a458c86dbb8 -size 156552 +oid sha256:e9149ea40285c1633b59dc1e1c62a1ffb560bd3f4ce97361044e0be98d59e1e6 +size 156521 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png index fe302aa676..54d8ed81b6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24d90b69aa2b3732a0fb775de48fc5e65de6bfe2070742d7e2bba12b527745c0 -size 142636 +oid sha256:93d324f26910b61eb576ad8c37fe4bfad79e93879d59f9f3017e7a8c0cef1cfd +size 142620 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png index a48730c8a2..5f923d3618 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:941f51630b9f3340ac375b8c187325b22c346378573fbc65144859025193eda2 -size 151650 +oid sha256:338261e41a91279114bf8ee6c5a3ff0c94320706ff3f0d405b17a1ef87eec088 +size 151649 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png index 24a2bcc0e8..f5f7cbf47a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66ab5535bb3d797997da1882e1969638e6d2accfdce2061f09d083e35fb2bbea -size 141958 +oid sha256:8c9c9e85f84e73951975825beeb355883da4a12f9cdbac0d247736e995857640 +size 141955 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png index 5145845083..f3ecb9df4c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e821cfffe6245425acd88c490c3a35ae8a9ef9f2751ad3e1e387d31c6702ac8f -size 150760 +oid sha256:62a3c3031385ac731b500fc7c8f6d16b58c7f2ca6e4b758b4edb635cc9e6933e +size 150720 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png index 87095f8b42..6689499dd2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:473aad560929e4c863a58a564574132b1cb60aa3e657a2f143d37db97360fc22 -size 156265 +oid sha256:d8c58a09e139e2a2fe9edc1b2bdea0cb399b1a5c1978a19aff2da86f2fd84eac +size 156247 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png index 2103bacdcf..4e7207012f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77b8e7f4397137570ccb8050d0041fe528eb3a34133ce1c7457f3a028b34e6b3 -size 142215 +oid sha256:b7a791e9c8788632f03e30aba24af768953411dffabde644bea176cd4811bee5 +size 142208 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png index e2d97b2d50..9293dc99b1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:413a50188e94cf2d804c7a90cf0c794c9e7723fd0018249a790a6fa931a7a28a -size 154292 +oid sha256:a5571483499c4acee1bf08dca534148159d6b521bceb51c306773593539639bf +size 154286 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png index 500e753b79..5d571bdb7a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73a339a0b4e5a552407abdd12a45bf9d3fde492c18aeefb08e77d15b04f37920 -size 163444 +oid sha256:e2f1da2796934102bf20655ebe9dd3ea95972399aa77faf13e550b80b3d320b1 +size 163435 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png index ba1c365c84..e40b3c2da8 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d452c2f1e21aa37099ef351755b770abbe2e641f9933f5b246b0918153333d4 -size 144974 +oid sha256:a4b8b6d80dd685efa4c627c6f48207040561c322dc93d355fe00793981ca88f3 +size 144964 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png index e0e04317dc..1efc0bcccc 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f5c37cb34374f3feab75e86c39a8aa476efeeb3b85348294fc41a680e70e56b -size 144192 +oid sha256:c0cf792b404583b855f7d09e1535eba11f597e3f4706e367dff00f608aae9029 +size 144176 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png index e31d919fb0..e4da1ad973 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac7adfc6578c399c6648f695c7f45b9156f8f5486be58c2c575c8eee30a749de +oid sha256:8ce2fcbc8fdc3b6799448c92919ef43b7fc749c2b3b9894bca18e16f8ccd5dd9 size 150903 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png index 45f1446d0e..805dbaff2a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d200afbce60c5502d4404d3f741b36f9f88c01e644466672eececa66bb17277c -size 142576 +oid sha256:c27a5471ba179e4dd66fdaaae1ec56ac2a86dbf01d62b16c448f5a0df2749519 +size 142566 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png index 41b04f5bc5..08354b0cae 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:035b6cb7a1878f6e8d8fd9087c69ddc2a35430e7f903aecca4d5e9e783653e30 -size 143373 +oid sha256:a8b4fce8e61026cf00536605f8898505cec9a974aaae1d5951fb79a30bbec95b +size 143360 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png index 40449b178a..ed957abb38 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4c7d7352bc253cba98e7963abca47fbbabca3840de1c5262a0abb2a54cb38f -size 145192 +oid sha256:f0c129646f3fd0a78d6c747f575d98696330c82633b119c266c2eea081cb36e8 +size 145193 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png index d859cb2a4f..ba9f50c1cf 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f346340dbdbc929ddf2ace6ed94bab6b51f8a522b2462db961ca4026805c6a8 -size 151442 +oid sha256:0063fdc45a8ceb4530591b4e02008eb9b754cefd43adb8acac0e754860a46340 +size 151440 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png index e3ace67a98..29cca5a8ad 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d03e00b1103f211d889b7b416a2fb954ccaf25577742bd8d0d2bab81dbdc567b -size 142845 +oid sha256:e53a7ba5131d2174121e72f520a3e9c2aa4b5c389690d2d47cfe419f8ff4290a +size 142838 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png index e08a2850ff..138530aaf2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2cf5970c363f1e04dcf10b13021a710f93b7c1544499fae4a458a3542dffac3 -size 156131 +oid sha256:ec6a17623cb93cdf165d78c58b06a9a643dff24ebd8cbfbb4bf33096942ad2c8 +size 156098 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png index 2368d2578e..324dc7ecbc 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f74a52283d616bccaabfe95ebfc332ebb43e227cbff405070c4b20cd09cf3a5d -size 141989 +oid sha256:f04df3f019284faa05b6286852276de5b6d7a047cbdfded1303fdd744e7130df +size 141956 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png index f37205999d..82f5d83b3f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81cbeefdd7a39e66232d244ce171320c2f31b19f23b36f4ad5b0f7fb71825c64 -size 154346 +oid sha256:8b5d2080791bab646a110e912e98e877347b5b5a601603161e93b7cb1c2f6dd3 +size 154322 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png index 4ba6dcd649..3fd5025675 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ccf01c26425a89cf3633eb3da28f255f4c5caf2d158ba370364b1800c5eab61 -size 162173 +oid sha256:cdd4ce216c2eb8e0e71509f95407fb832ab8923408ee0d2b7be0ba38abd696d1 +size 162129 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png index 3e78a54ec7..55d4ef365a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a31e9ff73e0faa07adf24f1b6f26b527d45bda1981eac7ad81bd1e5484b96a8 -size 144899 +oid sha256:37529421d63a9f7d97887145e91f9f0827574a5bdbe1eda2e085612b451774a3 +size 144878 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png index c39860160c..7359f1d853 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73b81643e2d85e81e001395c1439fef6f5813a6122bcc668a45880a0ba7d0548 -size 144181 +oid sha256:e01b74cf05faac0b4aabc7848f8780906750a83c2b3ee914bdd6e208c14bfac8 +size 144145 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png index ff10bc35cb..1f8482c849 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:287690a6799dfc57fe721f075172281bdb78bc03e315e7db8ff9720a80fd7cc5 -size 150854 +oid sha256:5ea2c5908d322bae326846ee7e3762a04affc6ff77842951515fc35379f8717f +size 150825 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png index 684475c1a7..ef5dc390c5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c94ebaca53436da29b6e40b9fef74f5dbaf9f225dcaddcfe76b4f69f76afbcf5 -size 142469 +oid sha256:5173c0ff87560f9ecb0d5ccbf52992a01a43f53a1665cf565e0c56fb702a07f6 +size 142448 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png index 0cbe47a50b..2bfc650bbe 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bea334b823553c48a781767952d98d1094ee12ded915abeb3c482b59b0b57b1 -size 143067 +oid sha256:cd65c0380b79e7adf032027b8e6aca43465e62bd349a893accbf6e1e3137d39a +size 143054 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png index a337569074..1a1168bc58 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b101259000fd210bfbf52f19884098af35508621cc3164bd253d71d58a41fd3f -size 145155 +oid sha256:cbaa6c97ca79422887365ef77267d78b04c835067873f85df6bead249dec58ee +size 145140 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png index 61cc6f7241..7d06290fac 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7020e4940f15e2d6f6640234d9fa750b2ec925eacaa056b38c1c52faed19350d -size 151365 +oid sha256:e9332fc228a959263cdc1acaa69a68092a0bd5f5cbc460c1581c775739d633f2 +size 151339 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png index 6304cfc827..dc3e4c115d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09e1993041f35d8e0aa4848107cb2d568d4ed31f9bde45bfc4174e517d06bf88 -size 142602 +oid sha256:ad635c9076f5d15c37b914fbbffad4bfa4d26a2f13c390f852bb63371730bcbc +size 142574 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en.png index 30fc48cd91..6d5033605b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ce42095c640d50f242818eb3ce5c057f972cc28240918b1cc448f414157fe9f -size 184207 +oid sha256:59ad66398a4b4f7f5f63cda8c15d98a78aff40d2a168bc8fa7cc6df6ae1d823d +size 184170 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en.png index d6c3523f51..ab62d8803a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28196c87c21d65272148af11e134ae6abf7fdfc361cf43abfc90209ec785e335 -size 183568 +oid sha256:d06575021a862195747c35219515a4949a089d65c1c6282c864f2116e3800704 +size 183547 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en.png index e765b7dcd1..b86b025e1b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:214eb8ecb72e331e79637a28df9bff3938ff8b2dcb8f55ce8ffb248db2941763 -size 52425 +oid sha256:544f7bbee71640356001f0242487e5b10e8e69b4e1cc9d336d6ceb388ff97dc4 +size 52410 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en.png index 297aaff834..630f40e5be 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21961806f7a938791b5211f5f3c93a6da64e3668ccf887956c7dc73ef6701a9b -size 37393 +oid sha256:fc56f6c3203e71a98395f194b020f5a82030947a7bda56299a89adbe8df52486 +size 37383 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en.png index ef1dbb71e5..b2eccb99fe 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40dfdc9821c98db913757e45663f5da494bce02a0882a260cf365697c804c9a4 -size 35249 +oid sha256:b56ad84259529e985235a0e42589ea91544ffce1cea64d0022cb3b3c2c056316 +size 35257 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_0_en.png index fa6c9958e9..bf71b7fb25 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d98bf24d1bbc36422ad69a74e554a9f7441961077b8a07f1d8123905345db41 -size 50167 +oid sha256:bf3f23066d6466f799677dd139d92ff26aeccdc7ce20d1ea4a1fa2eddbc28ecc +size 50157 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_12_en.png index e3e7fb615e..6a52101412 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6ca0ac33e28cff8479880eadc8a72134203ce0451de2365127b7478835115bb -size 51641 +oid sha256:c242b43c5674dfbe72ad5399ec2a1094fb5d9c8d2ff5d627e49c6f51010fbdf2 +size 51631 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_13_en.png index 0bd1535480..276b7c713e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff442a3e3571f5ce0ceaba707882841ec2d13c45da05dfa361876705bc121f36 -size 63403 +oid sha256:9aef1a43f5478f4a8e2715211695d0dffbfdd823d8642244c4411eef06794bee +size 63393 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_14_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_14_en.png index fcf1f465cf..643f3ab3b6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae4cf8e28e956d3b0707763bccf6e02d123459cd0a137f023f3e964f85bf6072 -size 48014 +oid sha256:117ffd9be0ee1c2ce742c1725f935662ddbbf060bf02a3d121f3b331f577eb03 +size 48007 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_15_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_15_en.png index b836e4bf29..d5247e1d68 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92e7ed569e467326fbc2a0e278663494a237cb3c2198fa543b48f0a8825abccd -size 64487 +oid sha256:986daeac63a5185195c9972fb6d6d64cccc1eec53855c755b88d8e4c364acceb +size 64484 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_16_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_16_en.png index 1def9b255c..c8788f93d5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b1c78fb676f2f7bff622a874626154d1b65c826ee3f071dab0a0d78a9d25bca -size 55207 +oid sha256:4b9bfd23b94519ee37e26e5a6c99e7ec2ac1513cae6cfa8465c71856d1e8afce +size 55197 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_17_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_17_en.png index 69c034bb53..ba6aee9606 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d017482df08cddd32e0dca6a8fc72b5c92c20fd6a2087c4940bf57171faa4b7e -size 64235 +oid sha256:17fa1c2967b460d47e33dbbbc1f10085941d2861a619c9c4e09d0ad62a6a0e5c +size 64217 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_1_en.png index 1d9116096d..fa42cdf361 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:290d8f2a5b9977a674fe6d4c7059f4aac7ad76b0ce4d58ee1b1fe437551c8812 -size 71131 +oid sha256:30ea27e644a6ad86506cebaef9ba6d3558b30945ed57e80ddcbf6cd22896d408 +size 71126 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_4_en.png index 1773908a26..b8116a6486 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c186f2119f530e2c977a5c3ae896bb4bfabdea8b1af21e8fc1e11b5e1535e19c -size 70282 +oid sha256:8479ff3816b6d08e1b61f9326f525f55f40b6a542baedbfa5f5acbb5c766150e +size 70264 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_5_en.png index cbfdc44964..8181ec17f1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38ae00eabc393ea0b780cd9ec69828c4c63c0b29d249ba8b748d837022c214fb -size 85007 +oid sha256:4d9a77ed75ee8797ff6e76b19d8e873fdcb55b2c361094c875874403887803c1 +size 85027 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_6_en.png index 9cc34ef87f..312b715249 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06baade2dbfbcbf2413f25a5c8041e6bfd88a957781dc0d73a9dd264a1467a5f -size 73356 +oid sha256:4767b56c7df7009caa31e905cfa473edfce505b9171214987a0c2be8f76f0558 +size 73338 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_7_en.png index 8a40214c69..60aeb3f330 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c32edfec150a76a7c55b5beb8b1da3731df42e4c4ea7b4d4391066e28a191968 -size 103515 +oid sha256:bccefb7d0464ec26c54333804962362923da1cffb834a31b35ccf0460a6420ef +size 103533 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_8_en.png index 99d4002da7..26a4061020 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e1505ac2d2b1a3a824d05016551ee544f3fde640443b8b95650b640c4a8939e -size 53111 +oid sha256:039d1a97453e7c74cc8e2c04ecad719b3dffa1cc023b611e8f6e909fc9d51090 +size 53106 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_0_en.png index 239afa198c..ac89f3d57b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea044803be4517bcfd9920bc2f7192f3346823fa1bf2757f168e0556b3fec9c6 -size 49207 +oid sha256:ec0671a85557e44cd629bc2c426601ac3cb25a0cb095ff2034d73ad0878b1344 +size 49225 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_11_en.png index 2099de329d..c0fd6ef9db 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c042981fb21b1bfd7e7f4f7857f1a84dce3e42e827f665e8d4faa7f1abec547 -size 86337 +oid sha256:09b245abbbc0164df2f2d6e88e5e02c66267ba50dfa20134241cb4df6d7c770e +size 86341 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_12_en.png index 04c6f6c009..849cc3a077 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7e9f0d885e2d56955d4054f97f0a18bd8c947677f982091f0ab75b1b64840fa -size 50849 +oid sha256:349b482857cbe0a6b2d7787e177205d1d5d3f564b2b4a9f2f2e81c57b90c0e74 +size 50875 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_13_en.png index f0752ed4c7..2bb32b2a83 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:809988a1560c381fb910657d05f2e2e710a77c9545dcbe30657c1ab8df169c22 -size 61917 +oid sha256:c8221d98791f152488a3f53dc654157b006d465a7c9770c1f72884d0d82df723 +size 61878 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_14_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_14_en.png index 5460d50f9a..3d0bea0389 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f0452fd9541274543670bbb49d97319e481710d59e6ad67125d96ad8991f172 -size 47144 +oid sha256:5b754901c4b62950ae48537523aeabf734d49425eb13211075c424c4a54f28a5 +size 47162 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_15_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_15_en.png index aa888749f6..3f5a9b1922 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f664b1d17a081ad4c71c19a1510ed62d9c0d02a2cb261fbbe631be73e5e66c8a -size 62916 +oid sha256:e57025ec733a1513ec594e217519546e363b07df1cab7547d89aecd19f8c55f9 +size 62874 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_16_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_16_en.png index 69e820ccf7..eb7205c572 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdfe8f427cec521d02992d2fe00d34eb3520103b361523deaceacd50a402b959 -size 53919 +oid sha256:871ca03e59cecb80a0f587ae1b8de0e015e094d032bc0e09dd3bae135dde1b7a +size 53936 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_17_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_17_en.png index 5a1480e478..30d3526c3b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ee39a6785005f95d9ef3236af0efd95c99a61186b115e7cfe6305b4c8051d71 -size 64293 +oid sha256:0d380a9ad7b10dadc52f7b485f999ecd0b2b121494815eef6cb7dc6090273e76 +size 64309 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_1_en.png index 19a0e1fa9d..b8373406d2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9a43633816c9dbd383bd95c7ee45b3f6feabc3b31a19b5bdd3b0bfc7e8694c5 -size 69358 +oid sha256:2039e099ff7fa3cc0fff14efaeac2049d5bf44196d95f652852030278d84d788 +size 69310 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_4_en.png index 84567cd635..3b61862e82 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82ce4919096f27d68be421b24926046b5ee740cc449a85b4ebdf8ab72dbf34a5 -size 68796 +oid sha256:41616d976daeb5528d013e20d6aff81f5ad8d505f9561fa02ac6395b6c03fb0d +size 68754 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_5_en.png index 65ae8ac61f..470080efac 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8deea1411eb0b21dc352338ce54e46206773067a9d3c46dc2ff6e78f25c6628e -size 82894 +oid sha256:2a33a913e668ca5e9cbf32cfd5ca6ce3b250e4de468763ca8cc52852f147574f +size 82873 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_6_en.png index c1770fa443..4d813dbb31 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9cec981969cfd44c916feadce445b3e7a8c642faa2895add3a9efd13048adc4b -size 72167 +oid sha256:d0959fb663ba273bb318671502767da57a1005a3123cb0383a4ff8879d6af5d3 +size 72120 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_7_en.png index 35e735cb4c..663f92bf7e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce5f4a48ca592b37844f874ff3eff6da8b966d7c11eb205c08ca5db1209b6d08 -size 101198 +oid sha256:410399234cd0a90f1371fbee9b7041607bef515643450945462b2f4e7f899a83 +size 101174 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_8_en.png index 2ecc108318..9682b0ee92 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7580ef7fcfaa3ab091e41ee949e01fb69bf8be346028dd01deab54fd234f4705 -size 52760 +oid sha256:a1255723686d5fde13b2c93f8d2a61ba8a42d92d41c50a5ff39b3fc1fd4edbb9 +size 52725 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png index ad7f676d40..33bbf7d3d5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce99545c68b2d7f7f026216c33aa0f34597c0f5e371e83c4d34e3051e810f6d4 -size 57425 +oid sha256:119a16be61f48adf4be664e2ac086006edca82c12b5955827a29b87088085e0e +size 57422 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png index e15d4f51bf..80f277717b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ee0d3c122093afc601d73758400996e4d48e0193cd59c814c0cfd2cc6906070 -size 57450 +oid sha256:a099b2a5bf006c24489835aae46724637c7d2c3b08d1f47904bc0e0ed93b187b +size 57445 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png index 705c777dea..83ec1b08f4 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8839778513429751427a29508a5c650a8d21d88abba2c3e35c204cb871bc25c -size 60524 +oid sha256:4d03bfc50eb1d3624f1dcd14b9d1cb515fde906f1f3688501239330c91fe2aac +size 60516 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png index f29a0f1e53..ef98edf77b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3387b50f6e1c50831f802140d125e633c028183a26527a67b2eeb11b17b94a0 -size 60224 +oid sha256:9ade5d45c72d66bc1ede4ddd84976981d7cfc59b90bd17c97fcfdf6ed44fdde9 +size 59753 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_13_en.png index 4403008663..59cded1222 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc7b967eb01668be02cab6ce6f77994622e94cf7d44da0e6b5996d2348f43108 -size 60118 +oid sha256:66ad9872af4087c25611715b1d369893daa948aa2e1e4b8bc6fbff6067c3295e +size 59685 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png index 38698e5432..852403d760 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02c954978c5ddde433a96f5dd8d6cef067a558c798ad253e9034db275c5d62a2 -size 56591 +oid sha256:0555ba9f6b8f9110ab81ef0e94264ac6b2c43fbe15c9e196ece63f25f6021d47 +size 56581 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png index 4a8bad0105..cb85490a19 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:410e8ca3c14ee859b82ca1b2776d60090e776a0c6fa217219932495e39cbf670 -size 59839 +oid sha256:388823a879ca9d7d7794bdb8fd03da91e4c47c03073e38e18039467832c99c5a +size 59831 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png index 32caaf5bc1..9c67fa0694 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd21bcdeeca45af5e65c5a0c57751e55ce920c10cbb30b18f04912ab7d193fda -size 55232 +oid sha256:516a010d56f5cfd205b46de49b49d730ad2012fe574e08eb1e6746adf07ca373 +size 55224 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png index 67dcaa672a..dbd685291f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdc5c945b52fc5f9797f385c1eca9a7382e539ca4444bb2b94ad899e2b84585f -size 55216 +oid sha256:62475d0e6b37cff42980135438faeccebe74bc110f517c12616351be8469c448 +size 55208 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png index b27aa9e36d..1526024ad2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f01c4391461d28e99e2bf20af3bf48a1803d5c1d2cff537dd2c046f7c129d4 -size 54260 +oid sha256:390f7f3c699ecd843eb79adcefc64a66e6bbb6740dcd3bccf0f90c2f2b70e7e2 +size 54255 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png index dbabf5540c..e70c83ec54 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d52d24702141a0592ea3bf2fb61a0db7c2cbe0355645164b22fee507c9aedc0f -size 58366 +oid sha256:c053c3df134ed9ee672bba25db0e730aad83a47454ba9e74981e8178e6cfb59e +size 58361 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png index fa8fde5523..13e75e4e89 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e54a56a22751f18b1f496fbde24872185cddde502c99c7381737fb9a215fa223 -size 59004 +oid sha256:6d5db5c87b3b1a8f22a41a64ea39262e2469d805cf7b7fcc6d73622d1f8b052a +size 58999 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png index cfc2b72c1f..fb7b1a6405 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15fe33b3c1d811213346b13aab230a9a52e0f173c8ea6b788cf3f692ceffb530 -size 48898 +oid sha256:91526175f646a959c58ea84a0479bb7ae7bf821684ae329201d8c1d5ec772e58 +size 48887 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png index ceaf23f3ea..57388f1df1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8475bfc48d39dc3e553438cca9b1b088c9713774c5c16194694ef0546afacc4c -size 57060 +oid sha256:10ae9ff40ba2df95b741d516f31994bb1160df5c2b1407daa78aaa882e0db5c5 +size 57036 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png index d2e8859001..ca4e901e9f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09ecd2bcc86a4b4571481d9414e6f467698f5f1882b05054444982bcfb0c91dc -size 57108 +oid sha256:cafaaeba38646ef92103d3ddfd7c3c26ffed1e2bf1586569d2194a3aabf4c183 +size 57088 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png index 642558be61..480716e90c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0f729da85f30d33ba4bb096ea11966448c1d3816fc2f845dbece8be758fcd92 -size 59816 +oid sha256:cec8142f1f293d3e684ff7eb2002d05dca6ea959cb95dce327e8a4f5a78cf462 +size 59797 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png index 208d04a7b6..7732b265fb 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a0a77633d8d2bfe5f29cd772525769e4dfa613518408db8dfe8a27cf680da9f -size 59715 +oid sha256:7567339acb87356f182961883bfde348cbfdf406eedefc2ecb54fba9cd26e2f2 +size 59291 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_13_en.png index 88affec893..9ee85846e4 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7b1d184e7ef2e9324bd5e4b1e6b6e27521a9d9a50c12210b55594919670ee24 -size 59657 +oid sha256:668c3e76f40df6ee3d64d595b840d81d62ec3f52474cd2fc261109aedbf46a69 +size 59291 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png index 04e3a8e1c1..e149b92acd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3023446a0e6f5c8460da144759e5e47819783f021dd88870b4335f5662077dc2 -size 55929 +oid sha256:c6c0840f6795a3b3e612c0848e2b54cbc977408fb5432074e7b5771d1bcf030e +size 55906 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png index c408525d58..fd57712fe5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06dde04630aae068e327d053cfe918d92f99a2f72ec32d744f87846070ba362e +oid sha256:88d3b3855bd73c340619cc2ca14a19cacca304a3bd442a4c681f5dbfe248aa34 size 58950 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png index 455893f4b9..29014feb9f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccc2b366606bd6bc2fb3454ea2eb10539b5687232b439ce425c8f7dce9e5d92e -size 51257 +oid sha256:4182c26a99a119fbf450bee488d92be31e291f259fb6d21f84f05d77324a1840 +size 51270 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png index b3f12adb94..012203b274 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca2d3c21d271e87a508a0ee2867550cb59c9c3a76a8ea1aa17548e364039b75d -size 54821 +oid sha256:f4209f327f3a7cb3fbb0de25230604857e9d0e39b7ca0b2bec948656f08b2e08 +size 54804 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png index 6c6aa7e7f4..1a2a97e582 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8c63c180eee4972abb5a44f04cf7ec8e5b28a31ec9d1fcd9dd5fd0ce883404b -size 53773 +oid sha256:c6465d7f2e552193d51dd1701d6b4f5726cb616e35cdcd3e219ebf31c22941b9 +size 53750 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png index 400fbd7552..2f0466a60f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5577395ee585414360fcf8f9f2757ed693f9cdc1510d66758a23f7756df6f99d -size 54179 +oid sha256:b30bd082aee7c78292c602c5320ade7738c0909038717cd5f8004b08243a2b3e +size 54172 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png index 3716d5cc03..7ace45e8e1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01622d32fa8a5478d64d0d613ebffed54b3a96192ef50b4a45506e48b7200d55 -size 58546 +oid sha256:c435641bf1e5f862499fc34247227200a28d47907e273c5f18331631d36fbd60 +size 58520 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png index d46df8f4de..1f1a7f3ba8 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6e92b9bdadcda3ed66462f32519fee772a084dd9e331fb3138e1144d3921969 -size 44767 +oid sha256:1f87ac09baa02003c48bfd4b8c946e650afbd6a42347a1a85a205bce40cf094a +size 44783 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_0_en.png index 51d29f8292..679efc4aaf 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ce6f663ca845a54b4e1aca55f1405e00d29084a95b6a61721e5136efe30f7fc -size 311755 +oid sha256:0ab623f806fc90bef41beccc5d5f444a882e6634cdadecbbc341e10369bbcfdb +size 315380 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_1_en.png index 52b4aa94c8..4275f3eac1 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7b714d78970f7bfc32c0bb8a226b2896681134a6eb329c871c0270511e8e81f -size 306543 +oid sha256:b73088b5af32e47d18d6961fe5622411f5342388d8a0d78c8f3fb5e27213146a +size 315116 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_2_en.png index a303b4946f..69d4181bbe 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:563c2a20cbf651c88e85dfba98c02fa60197b298ee621b5b4b1bdc4af51a456e -size 310172 +oid sha256:24735f133066c55c6d88b7f9930ad983cca431611c7e6833ca5a4d657eeb14a5 +size 313116 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_3_en.png index 5adfb27ab3..03edadc7be 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:171e41e96dbb6495c667a57047cf66e5059a19b8dba7d56e799113cd5a8690e4 -size 304775 +oid sha256:1d02a7ff9ff40f73d8c72b9378e10ba1f6580524cbdefcf290066b8047fdc33c +size 307633 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_4_en.png new file mode 100644 index 0000000000..5adfb27ab3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:171e41e96dbb6495c667a57047cf66e5059a19b8dba7d56e799113cd5a8690e4 +size 304775 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_0_en.png index 3e65f22a15..f087a418d1 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e41a284f2eedb0c7158079f059e05f8f505436ba91a82194faaff459bb30e4b4 -size 392983 +oid sha256:f5ac9f2f168b895e8262181f789b0fe2ad2c97b1296a50c1453879438bfe436a +size 395942 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_1_en.png index 0f4c0c9b0d..8ff0422506 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12691cbb9890570cb2081921224b13529e256feff1171d24e77cae11afc676e8 -size 380258 +oid sha256:f7c1c7d34935986d01113069c25849fc860133de582fe94ee63f2e28b181e87f +size 397378 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_2_en.png index a167708560..1597b13f0a 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f2c6932790cd4bbdff4dd07a728290aeb2eca47811c0b3d220e17aed5e8e34e -size 383588 +oid sha256:a11f8383fc481255fb3fa6f1a5475d23c13c0460c33b04bc190cf3b5931893a3 +size 395108 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_3_en.png index 89393180f4..fb78588841 100644 --- a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee7b3e5bb14da106ecedbe4827cded220d7222b68837c3c63ae3dfacd66ec2f2 -size 365133 +oid sha256:36424ccefbbbd35201800dbcbb743a2d22aa6fe7a606054fc3acf8b98aead272 +size 381588 diff --git a/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_4_en.png new file mode 100644 index 0000000000..89393180f4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.onboarding.impl_OnBoardingView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee7b3e5bb14da106ecedbe4827cded220d7222b68837c3c63ae3dfacd66ec2f2 +size 365133 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en.png index ce00de709c..6e151cc94b 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d96dff442cb398bdfbb12b597e368076d6ae1f0a45f0a581512d674d2affcf9 -size 21284 +oid sha256:6e7fb2de909096d9c88197d2c1360bf1998d7ea092b11719f3549722ed342c9d +size 21281 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en.png index 43c4d31bce..7b716ea37c 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a118fd6de3fec202a39728e2861272eabb72222f112dd4eac90733e2d265e51 -size 20983 +oid sha256:ce73d101556c4cd97b46a4ba720c03f0310bce96c5b88fc3544ad6e9efb00e10 +size 20982 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en.png index 34bfda7b5a..43a95660f5 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43a6f9ce99a7f261697b566f1d6876a01803661648d0e7493169aae851bf8af0 -size 21048 +oid sha256:ceecaeaa2193d68066bc54f19d4c33087496d377fce3bc4b38241659adf201dc +size 21046 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en.png index 0f53144064..dab60b94f1 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb84f215f779043c280cdef6af4976cc45483b2b1bb58d4da644c638dbb4cf1a -size 21258 +oid sha256:c37af705d8f682cd5278eb0e345b4c66a7d1dfd147b1f840f200707737e356bb +size 21253 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en.png index f63e5a99f6..f5cd59819b 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b74dfae753ea2d0e130ebac6dcd437db38f167a595e4d744ed6e9f5a11c0436 -size 21139 +oid sha256:b4456064b641b3accbb6779a52cfba3cb2b9281755b7102ecdf66ac137b57049 +size 21135 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en.png index 580fc18b9f..2112ce692e 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:843cae615c1bba130f6a233b1872ec4eac93bbab796679f0f4925569b2a4f0bd -size 19348 +oid sha256:224eb389d42d4f504f027d853570dcde1049c0dcdc58dfcbc42a12587de36311 +size 19352 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en.png index d9fa2df078..9b9aa5a28f 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc3ce0418bf1ca1f0ee70cd972b2bda2ee1a4d30e3b8402311776e39906d7d10 +oid sha256:8c4fa353f7b79f1e38b41aad2611a5264770cd56e986119b13112d6f5fe32421 size 47968 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en.png index 829f4dec4b..7418f3b4c4 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27dc8abf63b27ab118dd866eba21d18f7a6d81c6bfa61ceea10805f62d84e42b -size 45892 +oid sha256:ce9b952da4924907bc245c96166fe4c4e14babe91accf6678de30b6be68de8c4 +size 45889 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreator_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreator_Night_0_en.png index a3f97b45e5..dda4ae9673 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreator_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewCreator_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09d954a08c15111f23012f37e9e8c7e2978660fb21c9bb4e1bb765086203725c -size 47898 +oid sha256:aeabb30b46e100487def9cedff50627d18a73371705dec42941db1bf9bfda5ec +size 47890 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en.png index dc5707034c..78d8d677b0 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e10183cfe96bd34339c29a1709a16b9a38f72235d484415b9f1c172d4456c798 -size 45704 +oid sha256:3e778a1a3590c849db350d2d801e600555fe46286d132833ae8243aa7cbaa70e +size 45696 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewEnded_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewEnded_Night_0_en.png index 829f4dec4b..7418f3b4c4 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewEnded_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewEnded_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27dc8abf63b27ab118dd866eba21d18f7a6d81c6bfa61ceea10805f62d84e42b -size 45892 +oid sha256:ce9b952da4924907bc245c96166fe4c4e14babe91accf6678de30b6be68de8c4 +size 45889 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en.png index 63f6c4696d..6111183d26 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fa1f068afa5135a9c91abd89ce843e5467fa40fe10347432b853b97050afac7 -size 43255 +oid sha256:4fb88c95ef66f80a6646b894d2bf17bbd25136e2ee17abb91b9278c97635bb6f +size 43253 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_0_en.png index 1baad77d32..3e71995267 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a04877f742eb3d0f4b19e8eec981910252c15abfa4cb35afb3abbee416b18d8 -size 55383 +oid sha256:057910d2a37c535b0f70929d6440150e0c2ca04978e61cbcf0ae160c364bdc48 +size 55377 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_1_en.png index cbc653bb77..accd1dffcd 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.impl.history_PollHistoryView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad165e733d9fe9b74c5f3fe3d91a2162a0c7e972390b683b025460843b2d94d7 -size 59167 +oid sha256:0fb361c628d6c279bef5841befb5ce860d1be7b1776465ffac5e0151ede10e90 +size 59162 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png new file mode 100644 index 0000000000..1b6fb4bab8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 +size 3642 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png index 908e311bb1..27a01cf88b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d06a58bb3b3fe7b01740cc04ed3128d24a13717be84cb4a70ced228d2256b41 -size 9435 +oid sha256:6a3da0d5bba7824bc5a76b87bb2be17d17d7d2f0492f4332416929366efb7a61 +size 30927 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png index 31231ab57a..908e311bb1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:571207325446195a58251e7ece1ecaabc8a782cbb526315abe2a82f21e6441eb -size 9006 +oid sha256:2d06a58bb3b3fe7b01740cc04ed3128d24a13717be84cb4a70ced228d2256b41 +size 9435 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png index 1b6fb4bab8..51a2138363 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 -size 3642 +oid sha256:cce848d6c16adfcbd2ef9eef5e65a035ccd4d0a3092953e51433fc134c286518 +size 29471 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png index 6f43e3db48..31231ab57a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f83d60c7e1d963ccef24668c219bd9df36494dc8bd8f57af7aa63a3b73ee6882 -size 7545 +oid sha256:571207325446195a58251e7ece1ecaabc8a782cbb526315abe2a82f21e6441eb +size 9006 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png index 34ded1ba23..1b6fb4bab8 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16685ff4d5d52c5cb15f87fd14178cf791a85f3295b9869f997f4dac9fda1373 -size 25819 +oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 +size 3642 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png index d220520bfc..6f43e3db48 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b323796ccdf27e5c445aa02e3f7b73f65df961dd88f3ee480efcba14d8038281 -size 19426 +oid sha256:f83d60c7e1d963ccef24668c219bd9df36494dc8bd8f57af7aa63a3b73ee6882 +size 7545 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png index 1b6fb4bab8..d220520bfc 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 -size 3642 +oid sha256:b323796ccdf27e5c445aa02e3f7b73f65df961dd88f3ee480efcba14d8038281 +size 19426 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png new file mode 100644 index 0000000000..d6fd8eeb70 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd +size 3659 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png index 13b79b35d3..d3470ac368 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:273043114950e000953fbede780f3ad8adcce9d3d579edea2cad58f8e3c48881 -size 8175 +oid sha256:6723e18a9ec3f01a2909f97c5ad80b7ce682653ec16229e1129ac509c2decd0a +size 28912 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png index 3631712dc9..13b79b35d3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b0d51e2c5b255fb2f94959734293d4b7807f002acb0217c84856f0002297293 -size 7636 +oid sha256:273043114950e000953fbede780f3ad8adcce9d3d579edea2cad58f8e3c48881 +size 8175 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png index d6fd8eeb70..a5ba8dad45 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd -size 3659 +oid sha256:2f7b32aed7f97cd97719c22400cbe7a4d14e9a4cf10d42b26b853cd81cb42f1e +size 27620 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png index 08349f1edb..3631712dc9 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba213b12d181b04695b9271670cb7d96ce83dfccc3c2031ee002ba1cf180cc8d -size 6408 +oid sha256:2b0d51e2c5b255fb2f94959734293d4b7807f002acb0217c84856f0002297293 +size 7636 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png index 1e509dbffd..d6fd8eeb70 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfb80116e111dcc2fff42ea4a6c26e2857eecfbfcdb6a43d09aaaaf63a990fca -size 24019 +oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd +size 3659 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png index 51ce6077b5..08349f1edb 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9def02bcb5784681dc26327791510a9a25f0772644cdae363d7a4b90e69067c -size 17479 +oid sha256:ba213b12d181b04695b9271670cb7d96ce83dfccc3c2031ee002ba1cf180cc8d +size 6408 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png index d6fd8eeb70..51ce6077b5 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd -size 3659 +oid sha256:f9def02bcb5784681dc26327791510a9a25f0772644cdae363d7a4b90e69067c +size 17479 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png index 7c27035fe3..40b2f015f3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb68bc3937183f353a87f193a34847676977cb2a366cc03fcede0e059e6f8f4f -size 42658 +oid sha256:43dd94d40380a5a667aee02d3ee002c137a68a7d10ba9d3ffe09015b505a6d44 +size 42594 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png index 32c18698c2..9b16567953 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a863908ebec818d64c4b8511667b192e09094c09cbfa6dd372538c831520bb2 -size 41295 +oid sha256:c6b667d57c0cf7147a8b713535738487f88ace48d7deb9ba338f7e26eea0b643 +size 41233 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png index aaa4e9efe3..7159edfbf0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bb6ca06a5faef9a2c1dd6a648488da2d6acc2c102d5abe5ea6c8ec2bb235b66 -size 40172 +oid sha256:96bda111da271692bb83a6049a65ab62ee706282806026882757b63c12a5629f +size 40143 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png index 8c38559e8e..772b2f04a5 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05b48264399d0ae93be25361ff04cb01b9d9dc7168da72d266bbc58a781428cf -size 43445 +oid sha256:bcb0a066e02486d81040d7a1089ae2e4baf20e483e661fb513579494769f1461 +size 43381 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png index 7694db2dda..ffa68efd51 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21df4f150516a7113b890f3da79c96b141245384dd6b74af7376ebb5bb9054cb -size 41555 +oid sha256:4d3ea582081948929abb7229b5d2e7fd87c631963340a506072bcc7be4843e49 +size 41493 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_14_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_14_en.png index 02d3026a57..6cbc436046 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b4cb0e83e6e48d650350be4338a99eb9f82fede2829a8dc5d71d8f8cc2ecd66 -size 42366 +oid sha256:32b57de3b1128a3a17ddc1cff7c96d708f4ac4ef9efa6a6d3978b9cfda4b6a0b +size 42301 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png index 5fb86225c0..3818826788 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b0075df788b47714d60f58aa5de5db2cf5beda4777bf54cd48769b4df6f5ae2 -size 42944 +oid sha256:8201ab8637db30a33b16a1336705f1777fff946832e9f10ac45987eaa44a20b4 +size 42884 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_16_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_16_en.png index 82512cde13..fd91a368d0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f28b78dccc4eace6d8aeb897f1eef1d00fe9e73dddf4ae4c62305f7063add2d5 -size 41868 +oid sha256:92e08b157e0b3091e136160cdb399926cf96ba48bf1de40ea84e57c5f9f2f0f7 +size 41804 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png index fbfb4d1992..4e7871b133 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ba55e0d3271f81d8bcbd6f0a956f50c8e4ed082b23f565357c0c732f2bd7395 -size 38805 +oid sha256:60571dc08867db9ad2f6528e95cc65df0eb57b9840bf8e96e6e1fbd802ad3a26 +size 38778 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png index f15bd024a4..fcb6f77177 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:659e2f942ff1ba3aee9e63ec329aac2386e755c04324c3865dae892f67f5ade5 -size 38762 +oid sha256:69d9aaf46305802861df8be0341abd73068376893bfd7aef43d7b8f3251350dd +size 38735 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png index b0b1fcbc5b..943d018b45 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a51d96a3e531140c918de3bef60b437dfd9e8f7a4cdd162b87077d96babafda9 -size 32438 +oid sha256:59c09f612209f3550e3b3a7488347b139422c821a3c47723e44d18b8d2d59593 +size 32366 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png index d8bb680572..506a73e964 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c89d28aeacb8cf19e76ebdc784176152991ad2a2067df4adbc4e40afa315e821 -size 34581 +oid sha256:aa41d32745e495b9b9bb18ee9aa089674ddb5263da29feeb7362fea4e17f8617 +size 34524 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png index 05b8c466fd..2c8e4e9e81 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91265422162edaf2b2e6c8474db8a94aec7db30427d67a7adcf7e9a30966d7f1 -size 41805 +oid sha256:a334db0766538dbfbfda746b84af6e81b0cc3beee927c45d0e72368d25167ade +size 41722 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png index 6527c3f104..fd822b4361 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5029ca41d5043331cb59741433c5f9232105ee5d1f65d8f6aa2570e842ece45 -size 40644 +oid sha256:dbd0ba4fc6221bf4048bc355fbb7ec2bd4059c8baa8596ebec5f83388e1f8e8a +size 40592 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png index 726667ed5b..1130fa23f4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1a69af9c184578a355345037ec34016c4b4ab26a2719bfaffafa1027365a678 -size 38453 +oid sha256:7c823d58a58b9adbd3894c4d53754439da3533d72639f7181764ac1bad449bf5 +size 38417 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png index 18bd760421..adf133ac79 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e9ebe7fe8e19809489798a996f888642159200486b129cd34aa333685e0ef56 -size 41854 +oid sha256:3444349d7e07f7da2db5cd71b7334447a5d218445d626fdf8793ef224d07b731 +size 41821 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png index 19c68e34aa..1dfa6994cc 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b1be379fac2defa3ba11db1c9ab9d0847c8245b3820648315d41e3013c0dd9d -size 42642 +oid sha256:47431534c2a84619c74fae1b2c4acda8c7d3310ca31e002ad55b89b5c54ca7ec +size 42567 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png index 8f5791e8ec..e563f552b8 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1127cde21edf3ebb251dda7ddf54a4c83ecdd0e4f7fa17c84743cbc6b35d4000 -size 41570 +oid sha256:47f8d0b06c9aaea4ef5c45bb38614c174870ab0c2f22dc03e54c24f1f5ba2952 +size 41507 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png index 53677bc33c..cb57d31474 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2013a7c30aac9d4921bf2187b3a9d1d1329842fbee6a020d98a4cd82a9e0c5a8 -size 41619 +oid sha256:3d50b32ab9b3fd6d7145a04676fb491c8ce31af655c54b9a369c3a946036ae12 +size 41552 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png index 24ec736f13..8008eab180 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7b8b61cdbe5ec0657f4f1852d8e371a5ab4db49e30574ef701165cd52a654fa -size 43511 +oid sha256:5054000865a6eeed597c898c75cfdbf5dc1d0c8b49d301fac9dcd636e17f16b2 +size 43450 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png index 94e96eb7c5..92299d3e4d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d282617bd3bb1db22842c23036e1637ae18bf66d135caca644f045dbfd2f9c8f -size 42048 +oid sha256:e86fbb58436a583df74d60e410e625865036d908d792eb9fb1fd506c875c3067 +size 41995 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png index 042e601b4c..bfb8b3ff0b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fe7ba5dc42a45576b884be75b39698087d8634769fc5c522c4f94b318d60699 -size 41016 +oid sha256:64cd6474f7fdca9dec9d4a0c9c82089e1445c49835afaf1d33469f325174098c +size 41000 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png index dc6a9fc76c..b767349cd8 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5db679b0fdb9fe6b7ca92ba6feec63c56075f2526d61648e323a3b96671cf34a -size 43895 +oid sha256:202cd1054d5177cc8e7fa0ce00c3338cbed0c7a1eda693589bd3d537bfa6a044 +size 43838 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png index 9051449570..5d1f0c5cc5 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e12057777bc343396543ea3fca1352ddd33e65ec7baaa1c672a040b86b27a20 -size 42391 +oid sha256:0369d8fda8eea52848f520751a295fccaf9d7bf9d9e65161f5e1d10df03347d0 +size 42321 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_14_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_14_en.png index ec56f8f188..6a5be52a90 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:304074b1f15761b9a0fc2529d61ca56505e5c59d9a74ea3cea6db6dd5416783f -size 43205 +oid sha256:b67e13e498cc4b0709d74faa02f78692e717f3274c2f28ba77450819c2ddebb1 +size 43140 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png index 70b89260c6..181ad2bfa3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2dad2ff77ed440ab76deb90b2aa1287767f7078b4f0fd9b721d78661b891786e -size 43813 +oid sha256:f2925c650f266a5b39ec77489e2658a70761e8332cc759e418cf733b86572914 +size 43755 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_16_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_16_en.png index f07e6d441c..ace46aa6ee 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d19188df2e2b6a66be9b76edcada4dce831d8b0fb5b3e76ac2185723ccc6ec34 -size 42730 +oid sha256:433e2d955156019aa6ef906418fcb23ed26f7c357ca91b526f941a8fdd9c7161 +size 42664 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png index 1d721d5c87..e60ef55c42 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a67e601d851d95f73fe7f6165b91703e87f6c1f71fc6c1d9c614d46ee35623ee -size 39515 +oid sha256:cabfc7c5bf6f9b612e933cad211b8261b236cd981488cfa081e0226940626339 +size 39499 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png index 552e6b96c8..2e3298b2ff 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:533db6c347770157ef484b53218885a04f12f732775a94da8b915d700d356256 -size 39399 +oid sha256:cf96db08ac0382185c34863b38603fe55eb3aab691fa3c42b6d73e4167ce8f82 +size 39383 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png index bedda822e9..84360a0028 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c301ada1436b647871471716eb3bde5afe63b3c8375fecf065821b24b8d2bfdb -size 33118 +oid sha256:43c94f11c9275eca4386cb5771d62808e7de53d4fe8f0923f73474bd97e933c8 +size 33100 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png index f505e9178d..05eabd23a4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:568a9220be25d55e801611ff7e2be2b9cb5fb4da8018d280d4c9173526e2a6bf -size 35331 +oid sha256:fe5aade89a3afa13f2d9e0eea8a94c133e1e50f91a35d28d42a92c4e4d3baaf0 +size 35285 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png index bc995c30a9..af030ef6ef 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:122c7b535de6136219b6864bc72db43d093c10ac2991b7427a5fa1fd8a27c7ae -size 42543 +oid sha256:9eef5ef526837a00762336f1df6d900500b7956dc70d95b4619f80c4f158b0ee +size 42459 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png index 39d29992e4..ad155142c2 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01e1ffd408352b307274768528095cacb54863b2ec54f6ca912b51c030acc90a -size 41481 +oid sha256:f038763501f54386b7751684d0aa1402320e9d913bb231cb422da06a02fce300 +size 41427 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png index a51874b64b..1bdd63796d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e99e06f6281be006fb12e0a56e7523c8144256562464474cb2aa918f6576d636 -size 39091 +oid sha256:d254f940a7ef821c2a3a731e60fd23bccb61827bd00859d97139b9f93a5c8c3b +size 39075 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png index 47301b8b4f..ef1dcb04b0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89b759add6d46fed97f7acae8f8366bb247632090b9520e50b6dde9f8fd64921 -size 42681 +oid sha256:4277e9d618880a9d3be5d672ba7b7081af99f36f34f4678bb160b98cac9fefcf +size 42630 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png index 80d9ff59fb..0c8f52659d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61057d174a52244f032b823dc38dd2752e0ca07ae45acc185a49bc3f1808509f -size 43588 +oid sha256:cc543c855b813e48a2e0140555ab9d34ca3c30aa5f9afdc56bafdaae4eb2b476 +size 43529 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png index 789f9dbadf..cb41bda82b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5476f3210bbd833b5f0b836e5942cb78fec6055c1cc43b35625397cc34973306 -size 42481 +oid sha256:a1370d804e4f71194ce4a07f449fbe1c21bb6e3f4de7d42f92ff17b653d1f554 +size 42409 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png index d4a9658f5a..8418b73ae1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaa0c1addbc1df1fcbd03f961a4da66a2f1663f4aa2ff35ad65165eb3befee9a -size 42462 +oid sha256:0cab876c15147e3745112d63154d8097b685cc5d4abb76c2b6600f9334bd449f +size 42395 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_29_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_29_en.png index 96d500520c..9c35c6a4d6 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_29_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_29_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe7856a003131918100d8ce3da0e879f7c8d63de64e1b5889327738cb7a8f2f2 -size 23128 +oid sha256:f8f5508fc0509afed78f4259037c1f886b9c872d581756db947a11e688248c5a +size 20623 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png index 5ee881449b..c5f164e6cd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f64d2899886ab2e20bbd2dd0e0e2bd909f461751e60f4b6d93e506963c41ce03 -size 15832 +oid sha256:a92aa815f9c74893ea1b9b98912f0eefd86159af900ece588a3eb426264d076a +size 15473 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_29_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_29_en.png index 9287673872..8369441179 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_29_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_29_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e45e0dc5de85736bdbb3221f26c1cae3af78dd63acf0208fd12e023d91fa70e7 -size 23183 +oid sha256:480a8c2cb0c3ef6d30125cae576d9737b69b3ee55b8d0cb761562098c784ca83 +size 20476 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png index 4ec7b36e3b..c4379b0b60 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20a0dfc19f05e9727cf70cf7eaf83503c805423cc36edbefaa119c2692eaadd2 -size 15678 +oid sha256:8287ed2eda7e0d486f6122c5bd61517f7f6ccc5ea21413f2eea085683d15ba68 +size 15370 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en.png index ee1a8435cf..46adaa89d9 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de15498c2d1fd8b169867a4f51ba69a26cada9c66def933c434ab213d94c671 -size 22918 +oid sha256:5de23f1ff2742d0a29297be486b6944dd64a3906cbf0902358473ed91fe8b422 +size 22882 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en.png index 96d9bb6be6..0ae1afb0c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f83f3e237982ee599195423fd083d539f5b8a5bf608c63eac31654af6cfa6c2 -size 22836 +oid sha256:0365c048f02b698af5762c873a9e93ebe82712e7321d467c2f2bb1cbc105516b +size 22702 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Day_0_en.png index 9625abff26..0912c0fa57 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8f47f89b71826b87e2f6b1de8a325710f67da4c342a2fed10a253546f3b4658 -size 15303 +oid sha256:f4fdb9a9167e7088c7568b067ba2e02c5d5d953c5868f864c0c83c1bed485138 +size 15184 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Night_0_en.png index 64841c0408..ed871cda83 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileHeaderSection_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0bed8702030f9431a281c16cbf515821be63a49a5f25d7899eee2491f1804ca -size 15405 +oid sha256:28bb644a494d60947b4d66b859ce0cc678f31f93fee4f88aa28cb8e8d17dafd1 +size 15231 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png index 7653b90703..f8f5af0664 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e395d9ca4fc6b2eb1ea9a8fd6e2ad34e7397d9f64cf39e05eed50a1bfffabd7 -size 24186 +oid sha256:b2f56793a5859517f951d4b595023e44983a2ecb574cbe5408f72655969b5ee1 +size 24085 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png index 9f82a024d9..5d1451f662 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87cb4255c8346796a226cdfc2afb67641a3b27c2c1a15629a07c3293fcad5131 -size 22078 +oid sha256:cc74b230023d824504a5ce250e0abc4d9e93fd3d692902568b76248442fbc599 +size 21988 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png index 359cca7a84..a78a122bd4 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e9d94a7be97f1953a119a933084cadacd7dc6a049b334ec74e85a5bfe1bdc65 -size 24291 +oid sha256:204a2f11595cbd3fe467d6ced7d1999139d87f88bafe0fe80becba7deafcab3c +size 24127 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png index c07abc7b58..817ecc3560 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba80787576dd893f90fcdc83021e47abbde4ae2bcaaca2c167178ba7c2be3886 -size 36481 +oid sha256:19e7fa4a5c2f61cc878798d73ae12e6b7c47694c55698b383cba7c296b110a5d +size 36448 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png index c85a2d454c..553aec17b1 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a2278fd516dff3a5c86702f09d5621e8b3bb3de1df702b5036fa114975e4d85 -size 27844 +oid sha256:666b6910502871fd340571cb6503b0e056454849411abebd667c2189d6891758 +size 27808 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png index 9d8367f230..d078159df0 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4a1c91d475f54974a3c1dd8748522995fccaa03236b35f1bcf9f94ac24ce14 -size 22500 +oid sha256:af9da2f03f67ab130babec49617cf919c116ab6a2ec8ea9545b44bdd6a299e45 +size 22414 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png index ca9fcec377..623e88860b 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a465e6254ae595d62a335914414ad2f33b76736f529d1e188f3d0e0d57268f65 -size 23015 +oid sha256:4dcf82f9aa2ce550dff4bf0dc4b2786b52434641f61687a73dcba99c9421efa1 +size 22974 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png index 3c27d5ab57..8ee163709a 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef88b209eb93fb6b4e5aa411c4cdcb9ff1aa49bd44f11c2271a4d758482b145a -size 25181 +oid sha256:941b691799b124e724c350ff11d7cbd67be75859938fcb91c59e1f88216d8ee4 +size 25074 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png index 8543427ea2..191c4ba0bf 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2d2af5b466c79a8ab9243308dfc036c81c4c6e32a1ff4479702835a9259fcd1 -size 34961 +oid sha256:ea7b82e4b8df9e11e14a7be70368640ce04dd74319814a2ad99663901bebf9ba +size 34904 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_9_en.png index c69bac7acf..0e67124f43 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de2bee81e394ff032fac35d5391e38459ec8ecddb0036f34878b7239ad22d1af -size 31744 +oid sha256:76b688c758b4485634a5b5e8b350982aec21dac454cc9af68920d80c44cf8143 +size 31647 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png index 572f9e52e5..14cc9719ed 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6894ddb5662e7da82e5ee42fb0c9eb30afd87e15e415ba42be218ebab434d53a -size 23531 +oid sha256:86169e0e7694b00d1c6f924775377470f54b3cb343844c6bdd060ca0c499b8ae +size 23452 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png index 1acafc94c6..e09867ca42 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f393f620cc11b7aec7bf52f7efc5a5872ed07a14e7fbf2149b16171dbb2b03e -size 21463 +oid sha256:758b5c1df83fb5b241396268a73b019b28d0616e5f37bd501c42400683ffd583 +size 21384 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png index 4e6aeaef61..c4fe92d160 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63f75139c00eb5da98254c176fc8b4ed291cbb3c1b2581b869071cd6b435439d -size 23374 +oid sha256:3f0dbe3e9ded805f3cd4356abb5ce4c892a57cfdeff35a450607c493294f9080 +size 23261 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png index 4d0032fc29..3128baba4a 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce136a49edadeaadc229ec485b27bf2d4ae20601e948bca46c632a4035b90130 -size 33697 +oid sha256:dcffa0153086f13d3c306813b308021e9c737915284aa1bc6e6528d1c1d20e10 +size 33619 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png index 22aeb8dc7a..7263187524 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:580fc1fe77975e83cf83fe582c2f3f4aa7003cf302e7dadcd6bb97c04cbbf148 -size 25517 +oid sha256:61aa562bfeeb701f22520827cc57a3019744e1036128e2c7486cada54a7f403f +size 25435 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png index e5a8848f0f..d768ce89c7 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bad99fce2875f6ac00fee3d09434c501964519ae6bb2a6ae24864e496cfc25d -size 21886 +oid sha256:3fd9cc072584a210d41ae55c1c5c02d011b4c1afe94802ca4e8f807cdab069ec +size 21804 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png index 4351039557..6b47c5752c 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b48c3e2c5e06a612473e252ba52eead6efd741b7ea21fdb778ec9ed5952a2a2 -size 21181 +oid sha256:8d889f6a0aa62800b92d2b259a1661e9d599ea9df0156a8ca1b55d859fe37a6c +size 21098 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png index 2c7ce358ee..e349270718 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:201c3f2385059321f7500ca8d559bb973a51aeb56fd417e943be496ae55d0869 -size 24370 +oid sha256:b482ea511c9a60a35785ab480e6a495f5f45b0e7c96e72c95fbec08ec2824f48 +size 24289 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png index c7762a331d..8e0f1c8094 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2938cbc17fb7fbd45f5b455a76e18a5ed812e779d622add70bdf84c46f17c93 -size 33487 +oid sha256:76825478ae3c24522f197c2040467338a1fafb596b46596f8a6bc299808d72b6 +size 33450 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_9_en.png index e4598d092f..19e7038d2e 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecba6c23a84b90af5fac2b5146d93c16a31c0f2237032bfd77744c62b50b2856 -size 30677 +oid sha256:cd81d819a37f2f5d4217ffeecb98bc7ca4c2a07ff043e8fc12a9f17682358044 +size 30601 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en.png index 616e4d902c..66519c4bf9 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b75d304c696b92209bfe9c3564165833dd74b13ad78a33ab5d875aee94b1ed0f -size 7044 +oid sha256:19afe8458f9146aa4256e3ede12fc673e8605782db5522760ede31f1f81ddd9f +size 6969 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en.png index faac7f9106..876f53004f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85aabf48fd622093cf07a3810b8267ddc373a6f002a225d1f7a0e785566ece51 -size 7054 +oid sha256:38a0beda65338d6486ab5e6c8afeea1b8871bce396d900a184769d9a2da495cd +size 7026 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Day_0_en.png index 768278c042..fa5bc467ae 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a022f780c01ca2c2f7ce2b9990c7347c336c9f5cd40bfe1e8afb597c571f6948 -size 6081 +oid sha256:6df8ee3c6c8303c0b4626af59578ad9c3e432162c8adee963b15deffd2d8223e +size 6035 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Night_0_en.png index 6e7f0f12f4..331f7b3066 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_MatrixBadgeAtomPositive_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4c2a60758bb05c4c152adc7a88027bc636aef0dfbd1731b7643b1aa81cf2d8b -size 5885 +oid sha256:a2e9aca91645e401a475e5eddc64f958cd24964cb2d87dd820c299646d5457c4 +size 5847 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en.png index c920765764..031080449d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24d042c0c4e9f9d01c5937d42cf16d05ad24e2d8cfd8026038126531b8304cd4 -size 30195 +oid sha256:4a35c5690e291b30e85491485dca6203d3541bae8aa6e680f53d5480b312b4fa +size 30859 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Day_0_en.png index bce67de223..08268d09c4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:390153008f59d4915895498d3d6fa3354502f3f3a902b3ae5e53045aec27432d -size 18116 +oid sha256:6bf99a794b62bcac4356d40c621e541cc8b4819851be94ee4fb5f990821dad43 +size 18696 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Night_0_en.png index 7de5764954..f0edea1109 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_ListDialog_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ad10053328a529721e5f6564e493e4d0ea1d9e4e098867f1a4daa938396d712 -size 16273 +oid sha256:9ac05090a322d3b3eb9bc6a8975a3f832e15099553186b9054036807b6d16124 +size 16719 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en.png new file mode 100644 index 0000000000..c937b35d4f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:869c9e0b2dd0ea776b99ab4e223c2e8c31f62a2ea9f7c2aa7c06423eb9bfa531 +size 18638 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en.png new file mode 100644 index 0000000000..3865c87243 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ca9146ee4758956ef5487e198352e836ed354a0c427f854780db2d3977621d9 +size 16738 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en.png new file mode 100644 index 0000000000..8c14f92fb0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7874f4a2365bed4de566c8db579d8a456d298da8c913bbdabfd7b66ba2d99d9 +size 13342 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en.png new file mode 100644 index 0000000000..4712e3e1ea --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:271f172f1c5cc924426afa2ae51f295c10cf51602600b46cac812a2973b6e68d +size 11694 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en.png index f232126cfb..fac015d66d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73978f15c708b9bb39f20aaaf2e1510caa796b8875bd1418d0ce1e6a99f60ad6 -size 8907 +oid sha256:798d2a617d7d5cdad84a3e39a81e83c9de1d5b84bfd35f54fd3a1e2271bdc411 +size 9277 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en.png index c619f9a53a..dd5b34fcd5 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b02a6c58c1a54f4e86b7b5db36c925ccc9a2012dc30372736216d489d2a8e33e -size 9819 +oid sha256:ddbf6a82c11d7dd55140c65677a5c96d53e09ad08c6a65313575a1d9b73744fa +size 10207 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en.png index bc29b0cfa4..7b92100afd 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f25810294b831c73b678d93a0352c4d2e84e946ee197680be40074145a4ba5ab -size 6104 +oid sha256:3427ed512414c9e6bdfb90db3caf29dacb8373878e56992eff260506d128c4bf +size 6564 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Day_0_en.png index 768278c042..fa5bc467ae 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a022f780c01ca2c2f7ce2b9990c7347c336c9f5cd40bfe1e8afb597c571f6948 -size 6081 +oid sha256:6df8ee3c6c8303c0b4626af59578ad9c3e432162c8adee963b15deffd2d8223e +size 6035 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Night_0_en.png index 6e7f0f12f4..331f7b3066 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_Badge_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4c2a60758bb05c4c152adc7a88027bc636aef0dfbd1731b7643b1aa81cf2d8b -size 5885 +oid sha256:a2e9aca91645e401a475e5eddc64f958cd24964cb2d87dd820c299646d5457c4 +size 5847 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme_ColorAliases_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme_ColorAliases_Night_0_en.png index e85bdfdf43..73f665c052 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme_ColorAliases_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme_ColorAliases_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8b117d33e87b6f1dfb545429f6949f5362c42be957bbae7e987db5202e1b2f2 -size 66324 +oid sha256:eaf36257945e77a17ce3412e5d52a45eff114fb0b53e8158186ba2192aef5f71 +size 66325 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png index 5f6cb5caad..78aa5cff8d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f541f0a05b49272ef3e379a9a629c71458b37435928338a3a58ac852b38eb803 -size 48802 +oid sha256:73a225eeb9c217ada941b5add96dd2f832fb33b702f86eae5e1f5ecbee5a4bb2 +size 51625 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png index 81f4997ffc..20eb03d5d4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a542b3be557dddc3aba16bda8bb7db1bf0078e6bb3abcc42a11c723e8dba3504 -size 46683 +oid sha256:ab365d585b76ab0723c9998cc13f755640b7f086b99b55804a38e103b8d42416 +size 49440 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en.png index 540f1b6ec4..a3bdbf9b77 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d966380d44d4fdfa87c17135cb540aec7b5e5d9dd433ddb9945d088b46eb76a -size 13485 +oid sha256:12f0ca8fdeddd40c744cb0ece103a98bfa2f38450d46458fcc6fc06751360ed5 +size 13606 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en.png index 74852be96b..42bb0815dc 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:808978903883f938d08c594c9a87f97774f9a0c11847088a623e199809882adb -size 11916 +oid sha256:e7c5e43207805ef6241140c17405258d2d6615ca80180cc6880457326b6b8446 +size 12011 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en.png index ae450922c0..ebc1200cfe 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce993fc311dd889c5a709941cec6224af603f3e9db62c02eda36adcb43d9c8d2 -size 14697 +oid sha256:2928dcfc8005ecf9b4666ab6e4c53f79641eb6946271db8a994a40edb40c963d +size 15180 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en.png index d4209f72ff..ea6127c771 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e256f644dd8cbb96d0d97c0f41e036f41f137cd09e322da9807ad7271943a6a -size 12999 +oid sha256:69d4d152932d0ff43f179f739810a63afad8124dd5ea706d73ab399cc00871cb +size 13472 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en.png index bd2bfdf6b6..6973fd68c7 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0602f269f3d3879db0a6b18ff56de7dd3fcecf79ccee1c25e2aa763dab9e1e3d -size 16958 +oid sha256:ac58ade49e587fe1be47055a6c0147dba40ee4e3ded1a3ae67ae9ccecf4c54b0 +size 17424 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en.png index e244a6fd2a..6f592e0827 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ef3f08243ed2dde9dbe4eb7422d68c33a4ae1443b7b75dd3661d245f58b539a -size 15088 +oid sha256:e9d00a99b3bfad8572a86562f1e40b4ac06f241efcd778398d1f7593ff3f5c43 +size 15620 diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index cd27b0be16..71187feced 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -227,7 +227,7 @@ Compose: - LocalMediaItemPresenterFactories - LocalTimelineItemPresenterFactories - LocalRoomMemberProfilesCache - - LocalMentionSpanTheme + - LocalMentionSpanUpdater - LocalAnalyticsService - LocalBuildMeta CompositionLocalNaming: