diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f89f5972d7..63ac00e289 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: name: elementx-debug path: | app/build/outputs/apk/debug/*.apk - - uses: rnkdsh/action-upload-diawi@v1.5.3 + - uses: rnkdsh/action-upload-diawi@v1.5.4 id: diawi # Do not fail the whole build if Diawi upload fails continue-on-error: true diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index a51cad74a4..ad13d5ecc7 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -40,7 +40,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} - - uses: mobile-dev-inc/action-maestro-cloud@v1.6.0 + - uses: mobile-dev-inc/action-maestro-cloud@v1.8.0 with: api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} # Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android): diff --git a/.maestro/tests/settings/settings.yaml b/.maestro/tests/settings/settings.yaml index 53fcf0f017..d5f1e110e5 100644 --- a/.maestro/tests/settings/settings.yaml +++ b/.maestro/tests/settings/settings.yaml @@ -16,7 +16,7 @@ appId: ${APP_ID} - tapOn: text: "Report a problem" -- assertVisible: "Report a bug" +- assertVisible: "Report a problem" - back - tapOn: diff --git a/CHANGES.md b/CHANGES.md index 665bca158f..3b4b5ca350 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,28 @@ +Changes in Element X v0.3.2 (2023-11-22) +======================================== + +Features ✨ +---------- + - Add ongoing call indicator to rooms lists items. ([#1158](https://github.com/vector-im/element-x-android/issues/1158)) + - Add support for typing mentions in the message composer. ([#1453](https://github.com/vector-im/element-x-android/issues/1453)) + - Add intentional mentions to messages. This needs to be enabled in developer options since it's disabled by default. ([#1591](https://github.com/vector-im/element-x-android/issues/1591)) + - Update voice message recording behaviour. Instead of holding the record button, users can now tap the record button to start recording and tap again to stop recording. ([#1784](https://github.com/vector-im/element-x-android/issues/1784)) + +Bugfixes 🐛 +---------- + - Always ensure media temp dir exists ([#1790](https://github.com/vector-im/element-x-android/issues/1790)) + +Other changes +------------- + - Update icons and move away from `PreferenceText` components. ([#1718](https://github.com/vector-im/element-x-android/issues/1718)) + - Add item "This is the beginning of..." at the beginning of the timeline. ([#1801](https://github.com/vector-im/element-x-android/issues/1801)) + - LockScreen : rework LoggedInFlowNode and back management when locked. ([#1806](https://github.com/vector-im/element-x-android/issues/1806)) + - Suppress usage of removeTimeline method. ([#1824](https://github.com/vector-im/element-x-android/issues/1824)) + - Remove Element Call feature flag, it's now always enabled. + - Reverted the EC base URL to `https://call.element.io`. + - Moved the option to override this URL to developer settings from advanced settings. + + Changes in Element X v0.3.1 (2023-11-09) ======================================== diff --git a/anvilcodegen/build.gradle.kts b/anvilcodegen/build.gradle.kts index 57758f8909..62a8b9d878 100644 --- a/anvilcodegen/build.gradle.kts +++ b/anvilcodegen/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { implementation(projects.anvilannotations) api(libs.anvil.compiler.api) implementation(libs.anvil.compiler.utils) - implementation("com.squareup:kotlinpoet:1.14.2") + implementation(libs.kotlinpoet) implementation(libs.dagger) compileOnly(libs.google.autoservice.annotations) kapt(libs.google.autoservice) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c8e11927d1..69a1877455 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,8 +27,8 @@ plugins { alias(libs.plugins.anvil) alias(libs.plugins.ksp) alias(libs.plugins.kapt) - id("com.google.firebase.appdistribution") version "4.0.1" - id("org.jetbrains.kotlinx.knit") version "0.4.0" + alias(libs.plugins.firebaseAppDistribution) + alias(libs.plugins.knit) id("kotlin-parcelize") // To be able to update the firebase.xml files, uncomment and build the project // id("com.google.gms.google-services") 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 542ae7090d..03f0b20429 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -18,6 +18,7 @@ package io.element.android.x import android.app.Application import androidx.startup.AppInitializer +import io.element.android.features.cachecleaner.api.CacheCleanerInitializer import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.x.di.AppComponent import io.element.android.x.di.DaggerAppComponent @@ -27,17 +28,14 @@ import io.element.android.x.initializer.TracingInitializer class ElementXApplication : Application(), DaggerComponentOwner { - private lateinit var appComponent: AppComponent - - override val daggerComponent: Any - get() = appComponent + override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this) override fun onCreate() { super.onCreate() - appComponent = DaggerAppComponent.factory().create(applicationContext) AppInitializer.getInstance(this).apply { initializeComponent(CrashInitializer::class.java) initializeComponent(TracingInitializer::class.java) + initializeComponent(CacheCleanerInitializer::class.java) } logApplicationInfo() } diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index 7fcae7040e..c8e16f708e 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -25,6 +25,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -33,10 +36,14 @@ import com.bumble.appyx.core.integration.NodeHost import com.bumble.appyx.core.integrationpoint.NodeActivity import com.bumble.appyx.core.plugin.NodeReadyObserver import io.element.android.features.lockscreen.api.handleSecureFlag +import io.element.android.features.lockscreen.api.isLocked import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.theme.theme.Theme +import io.element.android.libraries.theme.theme.isDark +import io.element.android.libraries.theme.theme.mapToTheme import io.element.android.x.di.AppBindings import io.element.android.x.intent.SafeUriHandler import timber.log.Timber @@ -46,7 +53,6 @@ private val loggerTag = LoggerTag("MainActivity") class MainActivity : NodeActivity() { private lateinit var mainNode: MainNode - private lateinit var appBindings: AppBindings override fun onCreate(savedInstanceState: Bundle?) { @@ -61,9 +67,29 @@ class MainActivity : NodeActivity() { } } + @Deprecated("") + override fun onBackPressed() { + // If the app is locked, we need to intercept onBackPressed before it goes to OnBackPressedDispatcher. + // Indeed, otherwise we would need to trick Appyx backstack management everywhere. + // Without this trick, we would get pop operations on the hidden backstack. + if (appBindings.lockScreenService().isLocked) { + // Do not kill the app in this case, just go to background. + moveTaskToBack(false) + } else { + @Suppress("DEPRECATION") + super.onBackPressed() + } + } + @Composable private fun MainContent(appBindings: AppBindings) { - ElementTheme { + val theme by remember { + appBindings.preferencesStore().getThemeFlow().mapToTheme() + } + .collectAsState(initial = Theme.System) + ElementTheme( + darkTheme = theme.isDark() + ) { CompositionLocalProvider( LocalSnackbarDispatcher provides appBindings.snackbarDispatcher(), LocalUriHandler provides SafeUriHandler(this), diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt index 512b5093d0..96439ff973 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt @@ -18,6 +18,7 @@ package io.element.android.x.di import com.squareup.anvil.annotations.ContributesTo import io.element.android.features.lockscreen.api.LockScreenService +import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.di.AppScope @@ -29,4 +30,5 @@ interface AppBindings { fun tracingService(): TracingService fun bugReporter(): BugReporter fun lockScreenService(): LockScreenService + fun preferencesStore(): PreferencesStore } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt index 4182c32a42..bbd9f62689 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt @@ -17,5 +17,5 @@ package io.element.android.appconfig object ElementCallConfig { - const val DEFAULT_BASE_URL = "https://call.element.dev" + const val DEFAULT_BASE_URL = "https://call.element.io" } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/config/PushConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt similarity index 93% rename from libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/config/PushConfig.kt rename to appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt index 823dd7f693..1836a5acb6 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/config/PushConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.impl.config +package io.element.android.appconfig object PushConfig { /** diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt new file mode 100644 index 0000000000..625282f1e8 --- /dev/null +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appconfig + +object TimelineConfig { + const val maxReadReceiptToDisplay = 3 +} diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt new file mode 100644 index 0000000000..a840079a5e --- /dev/null +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appconfig + +import kotlin.time.Duration.Companion.minutes + +object VoiceMessageConfig { + val maxVoiceMessageDuration = 30.minutes +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index ed286c71b9..23c35b119a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -362,23 +362,18 @@ class LoggedInFlowNode @AssistedInject constructor( override fun View(modifier: Modifier) { Box(modifier = modifier) { val lockScreenState by lockScreenStateService.lockState.collectAsState() - when (lockScreenState) { - LockScreenLockState.Unlocked -> { - Children( - navModel = backstack, - modifier = Modifier, - // Animate navigation to settings and to a room - transitionHandler = rememberDefaultTransitionHandler(), - ) - val isFtueDisplayed by ftueState.shouldDisplayFlow.collectAsState() - if (!isFtueDisplayed) { - PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) - } - } - LockScreenLockState.Locked -> { - MoveActivityToBackgroundBackHandler() - PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent) - } + Children( + navModel = backstack, + modifier = Modifier, + // Animate navigation to settings and to a room + transitionHandler = rememberDefaultTransitionHandler(), + ) + val isFtueDisplayed by ftueState.shouldDisplayFlow.collectAsState() + if (!isFtueDisplayed) { + PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) + } + if (lockScreenState == LockScreenLockState.Locked) { + PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent) } } } diff --git a/build.gradle.kts b/build.gradle.kts index b2e4ac3c0f..a2dbec823c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient buildscript { dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20") - classpath("com.google.gms:google-services:4.4.0") + classpath(libs.kotlin.gradle.plugin) + classpath(libs.gms.google.services) } } diff --git a/fastlane/metadata/android/en-US/changelogs/40003020.txt b/fastlane/metadata/android/en-US/changelogs/40003020.txt new file mode 100644 index 0000000000..415e29b2bf --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40003020.txt @@ -0,0 +1,7 @@ +Main changes in this version: +- Element Call is now enabled by default. +- There is an 'ongoing call' indicator in the room list. +- Adding mentions to a message is now possible, but it's disabled by default as it's a work in progress. They can be enabled in the developer options. +- Voice messages behavior changed: there is no need to keep pressing to record, to start recording a message just tap on the mic button, then tap again to stop recording. +- Added a marker in the timeline to indicate the starting point of the room messages. +Full changelog: https://github.com/vector-im/element-x-android/releases 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 5052f0927f..9678068a3c 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 @@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.R import io.element.android.libraries.designsystem.components.LINK_TAG import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -30,7 +31,6 @@ import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithSt import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListSupportingText import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.ui.strings.CommonStrings @Composable fun AnalyticsPreferencesView( @@ -42,18 +42,18 @@ fun AnalyticsPreferencesView( } val supportingText = stringResource( - id = CommonStrings.screen_analytics_settings_help_us_improve, + id = R.string.screen_analytics_settings_help_us_improve, state.applicationName ) val linkText = buildAnnotatedStringWithStyledPart( - CommonStrings.screen_analytics_settings_read_terms, - CommonStrings.screen_analytics_settings_read_terms_content_link, + 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 = { - Text(stringResource(id = CommonStrings.screen_analytics_settings_share_data)) + Text(stringResource(id = R.string.screen_analytics_settings_share_data)) }, supportingContent = { Text(supportingText) diff --git a/features/analytics/api/src/main/res/values-cs/translations.xml b/features/analytics/api/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..a60d94b797 --- /dev/null +++ b/features/analytics/api/src/main/res/values-cs/translations.xml @@ -0,0 +1,7 @@ + + + "Sdílet analytická data" + "Sdílejte anonymní údaje o používání, které nám pomohou identifikovat problémy." + "Můžete si přečíst všechny naše podmínky %1$s." + "zde" + diff --git a/features/analytics/api/src/main/res/values-de/translations.xml b/features/analytics/api/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..135eac3d21 --- /dev/null +++ b/features/analytics/api/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ + + + "Analysedaten teilen" + "Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen." + "Du kannst alle unsere Bedingungen lesen %1$s." + "hier" + diff --git a/features/analytics/api/src/main/res/values-fr/translations.xml b/features/analytics/api/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..aa0fe9a14f --- /dev/null +++ b/features/analytics/api/src/main/res/values-fr/translations.xml @@ -0,0 +1,7 @@ + + + "Partagez des données de statistiques d’utilisation" + "Partagez des données d’utilisation anonymes pour nous aider à identifier les problèmes." + "Vous pouvez lire toutes nos conditions %1$s." + "ici" + diff --git a/features/analytics/api/src/main/res/values-ro/translations.xml b/features/analytics/api/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..e03a6cfb36 --- /dev/null +++ b/features/analytics/api/src/main/res/values-ro/translations.xml @@ -0,0 +1,7 @@ + + + "Partajați datele analitice" + "Distribuiți date anonime de utilizare pentru a ne ajuta să identificăm probleme." + "Puteți citi toate condițiile noastre %1$s." + "aici" + diff --git a/features/analytics/api/src/main/res/values-ru/translations.xml b/features/analytics/api/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..0617f21bbf --- /dev/null +++ b/features/analytics/api/src/main/res/values-ru/translations.xml @@ -0,0 +1,7 @@ + + + "Делитесь данными аналитики" + "Предоставлять анонимные данные об использовании, чтобы помочь нам выявить проблемы." + "Вы можете ознакомиться со всеми нашими условиями %1$s." + "здесь" + diff --git a/features/analytics/api/src/main/res/values-sk/translations.xml b/features/analytics/api/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..a930b315ee --- /dev/null +++ b/features/analytics/api/src/main/res/values-sk/translations.xml @@ -0,0 +1,7 @@ + + + "Zdieľať analytické údaje" + "Zdieľajte anonymné údaje o používaní, aby sme mohli identifikovať problémy." + "Môžete si prečítať všetky naše podmienky %1$s." + "tu" + diff --git a/features/analytics/api/src/main/res/values-zh-rTW/translations.xml b/features/analytics/api/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..db8f91ceea --- /dev/null +++ b/features/analytics/api/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,7 @@ + + + "分享分析數據" + "分享匿名的使用數據以協助我們釐清問題。" + "您可以到%1$s閱讀我們的條款。" + "這裡" + diff --git a/features/analytics/api/src/main/res/values/localazy.xml b/features/analytics/api/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..8ae2a2d3d8 --- /dev/null +++ b/features/analytics/api/src/main/res/values/localazy.xml @@ -0,0 +1,7 @@ + + + "Share analytics data" + "Share anonymous usage data to help us identify issues." + "You can read all our terms %1$s." + "here" + diff --git a/features/cachecleaner/api/build.gradle.kts b/features/cachecleaner/api/build.gradle.kts new file mode 100644 index 0000000000..38788af301 --- /dev/null +++ b/features/cachecleaner/api/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.features.cachecleaner.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(libs.androidx.startup) +} diff --git a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt new file mode 100644 index 0000000000..cd26d87bf9 --- /dev/null +++ b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.cachecleaner.api + +interface CacheCleaner { + /** + * Clear the cache subdirs holding temporarily decrypted content (such as media and voice messages). + * + * Will fail silently in case of errors while deleting the files. + */ + fun clearCache() +} diff --git a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerBindings.kt b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerBindings.kt new file mode 100644 index 0000000000..6492f9e62d --- /dev/null +++ b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerBindings.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.cachecleaner.api + +import com.squareup.anvil.annotations.ContributesTo +import io.element.android.libraries.di.AppScope + +@ContributesTo(AppScope::class) +interface CacheCleanerBindings { + fun cacheCleaner(): CacheCleaner +} diff --git a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerInitializer.kt b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerInitializer.kt new file mode 100644 index 0000000000..5cd17c8715 --- /dev/null +++ b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerInitializer.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.cachecleaner.api + +import android.content.Context +import androidx.startup.Initializer +import io.element.android.libraries.architecture.bindings + +class CacheCleanerInitializer : Initializer { + override fun create(context: Context) { + context.bindings().cacheCleaner().clearCache() + } + + override fun dependencies(): List>> = emptyList() +} diff --git a/features/cachecleaner/impl/build.gradle.kts b/features/cachecleaner/impl/build.gradle.kts new file mode 100644 index 0000000000..c95619419b --- /dev/null +++ b/features/cachecleaner/impl/build.gradle.kts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.features.cachecleaner.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + api(projects.features.cachecleaner.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.test.truth) + testImplementation(projects.tests.testutils) +} diff --git a/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt new file mode 100644 index 0000000000..fc77a5934a --- /dev/null +++ b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.cachecleaner.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.cachecleaner.api.CacheCleaner +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.CacheDirectory +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber +import java.io.File +import javax.inject.Inject + +/** + * Default implementation of [CacheCleaner]. + */ +@ContributesBinding(AppScope::class) +class DefaultCacheCleaner @Inject constructor( + private val scope: CoroutineScope, + private val dispatchers: CoroutineDispatchers, + @CacheDirectory private val cacheDir: File, +) : CacheCleaner { + companion object { + val SUBDIRS_TO_CLEANUP = listOf("temp/media", "temp/voice") + } + + override fun clearCache() { + scope.launch(dispatchers.io) { + runCatching { + SUBDIRS_TO_CLEANUP.forEach { + File(cacheDir.path, it).apply { + if (exists()) { + if (!deleteRecursively()) error("Failed to delete recursively cache directory $this") + } + if (!mkdirs()) error("Failed to create cache directory $this") + } + } + }.onFailure { + Timber.e(it, "Failed to clear cache") + } + } + } +} diff --git a/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt b/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt new file mode 100644 index 0000000000..7fe70028ac --- /dev/null +++ b/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.cachecleaner.impl + +import com.google.common.truth.Truth +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.File + +class DefaultCacheCleanerTest { + @get:Rule + val temporaryFolder = TemporaryFolder() + + @Test + fun `calling clearCache actually removes file in the SUBDIRS_TO_CLEANUP list`() = runTest { + // Create temp subdirs and fill with 2 files each + DefaultCacheCleaner.SUBDIRS_TO_CLEANUP.forEach { + File(temporaryFolder.root, it).apply { + mkdirs() + File(this, "temp1").createNewFile() + File(this, "temp2").createNewFile() + } + } + + // Clear cache + aCacheCleaner().clearCache() + + // Check the files are gone but the sub dirs are not. + DefaultCacheCleaner.SUBDIRS_TO_CLEANUP.forEach { + File(temporaryFolder.root, it).apply { + Truth.assertThat(exists()).isTrue() + Truth.assertThat(isDirectory).isTrue() + Truth.assertThat(listFiles()).isEmpty() + } + } + } + + @Test + fun `clear cache fails silently`() = runTest { + // Set cache dir as unreadable, unwritable and unexecutable so that the deletion fails. + check(temporaryFolder.root.setReadable(false)) + check(temporaryFolder.root.setWritable(false)) + check(temporaryFolder.root.setExecutable(false)) + + aCacheCleaner().clearCache() + } + + private fun TestScope.aCacheCleaner() = DefaultCacheCleaner( + scope = this, + dispatchers = this.testCoroutineDispatchers(true), + cacheDir = temporaryFolder.root, + ) +} diff --git a/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt b/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt index 651a2176f3..54a41ea31d 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/ui/ElementCallActivity.kt @@ -31,15 +31,22 @@ import android.webkit.PermissionRequest import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.core.content.IntentCompat import com.bumble.appyx.core.integrationpoint.NodeComponentActivity import io.element.android.features.call.CallForegroundService import io.element.android.features.call.CallType import io.element.android.features.call.di.CallBindings import io.element.android.features.call.utils.CallIntentDataParser +import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.libraries.architecture.bindings import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.theme.theme.Theme +import io.element.android.libraries.theme.theme.isDark +import io.element.android.libraries.theme.theme.mapToTheme import javax.inject.Inject class ElementCallActivity : NodeComponentActivity(), CallScreenNavigator { @@ -60,6 +67,7 @@ class ElementCallActivity : NodeComponentActivity(), CallScreenNavigator { @Inject lateinit var callIntentDataParser: CallIntentDataParser @Inject lateinit var presenterFactory: CallScreenPresenter.Factory + @Inject lateinit var preferencesStore: PreferencesStore private lateinit var presenter: CallScreenPresenter @@ -92,8 +100,14 @@ class ElementCallActivity : NodeComponentActivity(), CallScreenNavigator { requestAudioFocus() setContent { + val theme by remember { + preferencesStore.getThemeFlow().mapToTheme() + } + .collectAsState(initial = Theme.System) val state = presenter.present() - ElementTheme { + ElementTheme( + darkTheme = theme.isDark() + ) { CallScreenView( state = state, requestPermissions = { permissions, callback -> diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt index 6902a45378..6bc6e33339 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt @@ -140,7 +140,7 @@ private fun CreateRoomActionButtonsList( ) { Column(modifier = modifier) { CreateRoomActionButton( - iconRes = CommonDrawables.ic_groups, + iconRes = CommonDrawables.ic_compound_plus, text = stringResource(id = R.string.screen_create_room_action_create_room), onClick = onNewRoomClicked, ) diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt index 82bae809e8..22b5fcfd03 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt @@ -48,19 +48,19 @@ private fun LeaveRoomConfirmationDialog( when (state.confirmation) { is LeaveRoomState.Confirmation.Hidden -> {} is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog( - text = CommonStrings.leave_room_alert_private_subtitle, + text = R.string.leave_room_alert_private_subtitle, roomId = state.confirmation.roomId, eventSink = state.eventSink, ) is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog( - text = CommonStrings.leave_room_alert_empty_subtitle, + text = R.string.leave_room_alert_empty_subtitle, roomId = state.confirmation.roomId, eventSink = state.eventSink, ) is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog( - text = CommonStrings.leave_room_alert_subtitle, + text = R.string.leave_room_alert_subtitle, roomId = state.confirmation.roomId, eventSink = state.eventSink, ) diff --git a/features/leaveroom/api/src/main/res/values-cs/translations.xml b/features/leaveroom/api/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..64195325c4 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-cs/translations.xml @@ -0,0 +1,6 @@ + + + "Opravdu chcete opustit tuto místnost? Jste tu jediná osoba. Pokud odejdete, nikdo se v budoucnu nebude moci připojit, včetně vás." + "Opravdu chcete opustit tuto místnost? Tato místnost není veřejná a bez pozvánky se nebudete moci znovu připojit." + "Opravdu chcete opustit místnost?" + diff --git a/features/leaveroom/api/src/main/res/values-de/translations.xml b/features/leaveroom/api/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..c691f57471 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-de/translations.xml @@ -0,0 +1,6 @@ + + + "Bist du sicher, dass du diesen Raum verlassen möchtest? Du bist die einzige Person hier. Wenn du austritst, kann in Zukunft niemand mehr eintreten, auch du nicht." + "Bist du sicher, dass du diesen Raum verlassen möchtest? Dieser Raum ist nicht öffentlich und du kannst ihm ohne Einladung nicht erneut beitreten." + "Bist du sicher, dass du den Raum verlassen willst?" + diff --git a/features/leaveroom/api/src/main/res/values-es/translations.xml b/features/leaveroom/api/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..b996ec51f2 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-es/translations.xml @@ -0,0 +1,6 @@ + + + "¿Estás seguro de que quieres salir de esta sala? Eres la única persona aquí. Si te vas, nadie podrá unirse en el futuro, ni siquiera tú." + "¿Estás seguro de que quieres abandonar esta sala? Esta sala no es pública y no podrás volver a entrar sin una invitación." + "¿Seguro que quieres salir de la habitación?" + diff --git a/features/leaveroom/api/src/main/res/values-fr/translations.xml b/features/leaveroom/api/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..43ca62a678 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-fr/translations.xml @@ -0,0 +1,6 @@ + + + "Êtes-vous sûr de vouloir quitter ce salon ? Vous êtes la seule personne ici. Si vous partez, personne ne pourra rejoindre le salon à l’avenir, y compris vous." + "Êtes-vous sûr de vouloir quitter ce salon ? Ce salon n’est pas public et vous ne pourrez pas le rejoindre sans invitation." + "Êtes-vous sûr de vouloir quitter le salon ?" + diff --git a/features/leaveroom/api/src/main/res/values-it/translations.xml b/features/leaveroom/api/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..60d481824a --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-it/translations.xml @@ -0,0 +1,6 @@ + + + "Sei sicuro di voler lasciare questa stanza? Sei l\'unica persona presente. Se esci, nessuno potrà unirsi in futuro, te compreso." + "Sei sicuro di voler lasciare questa stanza? Questa stanza non è pubblica e non potrai rientrare senza un invito." + "Sei sicuro di voler lasciare la stanza?" + diff --git a/features/leaveroom/api/src/main/res/values-ro/translations.xml b/features/leaveroom/api/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..d736c701a6 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-ro/translations.xml @@ -0,0 +1,6 @@ + + + "Sunteți sigur că vreți să părăsiți această cameră? Sunteți singura persoană de aici. Dacă o părasiți, nimeni nu se va mai putea alătura în viitor, inclusiv dumneavoastra." + "Sunteți sigur că vrei să părăsiți această cameră? Această cameră nu este publică și nu va veti putea alătura din nou fără o invitație." + "Sunteți sigur că vreți să părăsiți camera?" + diff --git a/features/leaveroom/api/src/main/res/values-ru/translations.xml b/features/leaveroom/api/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..5915518c80 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-ru/translations.xml @@ -0,0 +1,6 @@ + + + "Вы уверены, что хотите покинуть эту комнату? Вы здесь единственный человек. Если вы уйдете, никто не сможет присоединиться в будущем, включая вас." + "Вы уверены, что хотите покинуть эту комнату? Эта комната не является публичной, и Вы не сможете присоединиться к ней без приглашения." + "Вы уверены, что хотите покинуть комнату?" + diff --git a/features/leaveroom/api/src/main/res/values-sk/translations.xml b/features/leaveroom/api/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..c67cb131e7 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-sk/translations.xml @@ -0,0 +1,6 @@ + + + "Ste si istí, že chcete opustiť túto miestnosť? Ste tu jediná osoba. Ak odídete, nikto sa do nej nebude môcť v budúcnosti pripojiť, vrátane vás." + "Ste si istí, že chcete opustiť túto miestnosť? Táto miestnosť nie je verejná a bez pozvania sa do nej nebudete môcť vrátiť." + "Ste si istí, že chcete opustiť miestnosť?" + diff --git a/features/leaveroom/api/src/main/res/values-zh-rTW/translations.xml b/features/leaveroom/api/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..7b1b55acf6 --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,6 @@ + + + "您確定要離開聊天室嗎?這裡只有您一個人。如果您離開了,包含您在內的所有人都無法再進入此聊天室。" + "您確定要離開聊天室嗎?此聊天室不是公開的,如果沒有收到邀請,您無法重新加入。" + "您確定要離開聊天室嗎?" + diff --git a/features/leaveroom/api/src/main/res/values/localazy.xml b/features/leaveroom/api/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..262369c9be --- /dev/null +++ b/features/leaveroom/api/src/main/res/values/localazy.xml @@ -0,0 +1,6 @@ + + + "Are you sure that you want to leave this room? You\'re the only person here. If you leave, no one will be able to join in the future, including you." + "Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite." + "Are you sure that you want to leave the room?" + diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt index 124f30e9f4..ccafa7e9e7 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt @@ -18,7 +18,6 @@ package io.element.android.features.location.impl.send import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues @@ -27,9 +26,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.LocationSearching -import androidx.compose.material.icons.filled.MyLocation import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ListItem import androidx.compose.material3.SheetValue @@ -68,7 +64,7 @@ import io.element.android.libraries.maplibre.compose.rememberCameraPositionState import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SendLocationView( state: SendLocationState, @@ -215,8 +211,8 @@ fun SendLocationView( .padding(end = 16.dp, bottom = 72.dp + navBarPadding), ) { when (state.mode) { - SendLocationState.Mode.PinLocation -> Icon(imageVector = Icons.Default.LocationSearching, contentDescription = null) - SendLocationState.Mode.SenderLocation -> Icon(imageVector = Icons.Default.MyLocation, contentDescription = null) + SendLocationState.Mode.PinLocation -> Icon(resourceId = CommonDrawables.ic_location_navigator, contentDescription = null) + SendLocationState.Mode.SenderLocation -> Icon(resourceId = CommonDrawables.ic_location_navigator_centered, contentDescription = null) } } } diff --git a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt index 2a628585a1..4e7a03c476 100644 --- a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt +++ b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt @@ -44,6 +44,12 @@ interface LockScreenService { fun isPinSetup(): Flow } +/** + * Check if the app is currently locked. + */ +val LockScreenService.isLocked: Boolean + get() = lockState.value == LockScreenLockState.Locked + /** * Makes sure the secure flag is set on the activity if the pin is setup. * @param activity the activity to set the flag on. diff --git a/features/lockscreen/impl/src/main/res/values-ru/translations.xml b/features/lockscreen/impl/src/main/res/values-ru/translations.xml index 1391e5f1a1..7b2c61176e 100644 --- a/features/lockscreen/impl/src/main/res/values-ru/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-ru/translations.xml @@ -24,14 +24,14 @@ "Сэкономьте время и используйте %1$s для разблокировки приложения" "Выберите PIN-код" "Подтвердите PIN-код" - "Из соображений безопасности вы не можешь выбрать это в качестве PIN-кода." + "Из соображений безопасности вы не можешь выбрать это в качестве PIN-кода" "Выберите другой PIN-код" "Заблокируйте %1$s, чтобы повысить безопасность ваших чатов. Выберите что-нибудь незабываемое. Если вы забудете этот PIN-код, вы выйдете из приложения." "Повторите PIN-код" "PIN-коды не совпадают" - "Чтобы продолжить, вам необходимо повторно войти в систему и создать новый PIN-код." + "Чтобы продолжить, вам необходимо повторно войти в систему и создать новый PIN-код" "Вы выходите из системы" "Использовать биометрию" "Использовать PIN-код" diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index b071e6509f..227c512043 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -19,7 +19,7 @@ plugins { alias(libs.plugins.anvil) alias(libs.plugins.ksp) id("kotlin-parcelize") - kotlin("plugin.serialization") version "1.9.20" + alias(libs.plugins.kotlin.serialization) } android { diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml index 1751364597..0917813950 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -35,8 +35,8 @@ "В настоящее время существует высокий спрос на %1$s на %2$s. Вернитесь в приложение через несколько дней и попробуйте снова. Спасибо за терпение!" - "Почти готово!" - "Вы зарегистрированы!" + "Почти готово." + "Вы зарегистрированы." "Matrix — это открытая сеть для безопасной децентрализованной связи." "Добро пожаловать в %1$s!" diff --git a/features/logout/impl/src/main/res/values-cs/translations.xml b/features/logout/impl/src/main/res/values-cs/translations.xml index ac228a305f..f772ee92a4 100644 --- a/features/logout/impl/src/main/res/values-cs/translations.xml +++ b/features/logout/impl/src/main/res/values-cs/translations.xml @@ -1,7 +1,6 @@ "Opravdu se chcete odhlásit?" - "Odhlásit se" "Odhlašování…" "Chystáte se odhlásit z poslední relace. Pokud se nyní odhlásíte, ztratíte přístup ke svým šifrovaným zprávám." "Vypnuli jste zálohování" @@ -14,5 +13,6 @@ "Chystáte se odhlásit z poslední relace. Pokud se nyní odhlásíte, můžete ztratit přístup k šifrovaným zprávám." "Uložili jste si klíč pro obnovení?" "Odhlásit se" + "Odhlásit se" "Odhlásit se" diff --git a/features/logout/impl/src/main/res/values-de/translations.xml b/features/logout/impl/src/main/res/values-de/translations.xml index f1eb199d95..2b86bd3a3d 100644 --- a/features/logout/impl/src/main/res/values-de/translations.xml +++ b/features/logout/impl/src/main/res/values-de/translations.xml @@ -1,8 +1,5 @@ "Bist du sicher, dass du dich abmelden willst?" - "Abmelden" "Abmelden…" - "Abmelden" - "Abmelden" diff --git a/features/logout/impl/src/main/res/values-es/translations.xml b/features/logout/impl/src/main/res/values-es/translations.xml index 5ac0656935..84f08fac3c 100644 --- a/features/logout/impl/src/main/res/values-es/translations.xml +++ b/features/logout/impl/src/main/res/values-es/translations.xml @@ -1,8 +1,5 @@ "¿Estás seguro de que quieres cerrar sesión?" - "Cerrar sesión" "Cerrando sesión…" - "Cerrar sesión" - "Cerrar sesión" diff --git a/features/logout/impl/src/main/res/values-fr/translations.xml b/features/logout/impl/src/main/res/values-fr/translations.xml index c309c3166f..0282e3199c 100644 --- a/features/logout/impl/src/main/res/values-fr/translations.xml +++ b/features/logout/impl/src/main/res/values-fr/translations.xml @@ -1,7 +1,6 @@ "Êtes-vous sûr de vouloir vous déconnecter ?" - "Se déconnecter" "Déconnexion…" "Vous êtes en train de vous déconnecter de votre dernière session. Si vous vous déconnectez maintenant, vous perdrez l’accès à l’historique de vos discussions chiffrées." "Vous avez désactivé la sauvegarde" @@ -14,5 +13,6 @@ "Vous êtes sur le point de vous déconnecter de votre dernière session. Si vous le faites maintenant, vous perdrez l’accès à l’historique de vos discussions chiffrées." "Avez-vous sauvegardé votre clé de récupération?" "Se déconnecter" + "Se déconnecter" "Se déconnecter" diff --git a/features/logout/impl/src/main/res/values-it/translations.xml b/features/logout/impl/src/main/res/values-it/translations.xml index 4e8217a7f2..73c1fbd3a7 100644 --- a/features/logout/impl/src/main/res/values-it/translations.xml +++ b/features/logout/impl/src/main/res/values-it/translations.xml @@ -1,8 +1,5 @@ "Sei sicuro di voler uscire?" - "Esci" "Uscita in corso…" - "Esci" - "Esci" diff --git a/features/logout/impl/src/main/res/values-ro/translations.xml b/features/logout/impl/src/main/res/values-ro/translations.xml index 4b2c7fbe7b..f6e47327c1 100644 --- a/features/logout/impl/src/main/res/values-ro/translations.xml +++ b/features/logout/impl/src/main/res/values-ro/translations.xml @@ -1,8 +1,5 @@ "Sunteți sigur că vreți să vă deconectați?" - "Deconectați-vă" "Deconectare în curs…" - "Deconectați-vă" - "Deconectați-vă" diff --git a/features/logout/impl/src/main/res/values-ru/translations.xml b/features/logout/impl/src/main/res/values-ru/translations.xml index ab2e3ba617..14f195fef6 100644 --- a/features/logout/impl/src/main/res/values-ru/translations.xml +++ b/features/logout/impl/src/main/res/values-ru/translations.xml @@ -1,7 +1,6 @@ "Вы уверены, что вы хотите выйти?" - "Выйти" "Выполняется выход…" "Вы собираетесь выйти из последнего сеанса. Если вы выйдете из системы сейчас, вы потеряете доступ к зашифрованным сообщениям." "Вы отключили резервное копирование" @@ -14,5 +13,6 @@ "Вы собираетесь выйти из последнего сеанса. Если вы выйдете из системы сейчас, вы можете потерять доступ к зашифрованным сообщениям." "Вы сохранили свой ключ восстановления?" "Выйти" + "Выйти" "Выйти" diff --git a/features/logout/impl/src/main/res/values-sk/translations.xml b/features/logout/impl/src/main/res/values-sk/translations.xml index fcd78f2022..a66630b7ff 100644 --- a/features/logout/impl/src/main/res/values-sk/translations.xml +++ b/features/logout/impl/src/main/res/values-sk/translations.xml @@ -1,7 +1,6 @@ "Ste si istí, že sa chcete odhlásiť?" - "Odhlásiť sa" "Prebieha odhlasovanie…" "Chystáte sa odhlásiť z vašej poslednej relácie. Ak sa teraz odhlásite, stratíte prístup k svojim šifrovaným správam." "Vypli ste zálohovanie" @@ -14,5 +13,6 @@ "Chystáte sa odhlásiť z vašej poslednej relácie. Ak sa teraz odhlásite, môžete stratiť prístup k svojim šifrovaným správam." "Uložili ste si kľúč na obnovenie?" "Odhlásiť sa" + "Odhlásiť sa" "Odhlásiť sa" diff --git a/features/logout/impl/src/main/res/values-zh-rTW/translations.xml b/features/logout/impl/src/main/res/values-zh-rTW/translations.xml index df722a9467..a4ca1b030d 100644 --- a/features/logout/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/logout/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,8 +1,5 @@ "您確定要登出嗎?" - "登出" "正在登出…" - "登出" - "登出" diff --git a/features/logout/impl/src/main/res/values/localazy.xml b/features/logout/impl/src/main/res/values/localazy.xml index 9296381c87..55a7131cb9 100644 --- a/features/logout/impl/src/main/res/values/localazy.xml +++ b/features/logout/impl/src/main/res/values/localazy.xml @@ -1,7 +1,6 @@ "Are you sure you want to sign out?" - "Sign out" "Signing out…" "You are about to sign out of your last session. If you sign out now, you will lose access to your encrypted messages." "You have turned off backup" @@ -14,5 +13,6 @@ "You are about to sign out of your last session. If you sign out now, you might lose access to your encrypted messages." "Have you saved your recovery key?" "Sign out" + "Sign out" "Sign out" diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 809cc9441e..6faf07f916 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) api(projects.features.messages.api) + implementation(projects.appconfig) implementation(projects.features.call) implementation(projects.features.location.api) implementation(projects.features.poll.api) 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 175cd34c97..2bb6e85df4 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 @@ -42,6 +42,7 @@ import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent @@ -97,6 +98,7 @@ class MessagesPresenter @AssistedInject constructor( private val customReactionPresenter: CustomReactionPresenter, private val reactionSummaryPresenter: ReactionSummaryPresenter, private val retrySendMenuPresenter: RetrySendMenuPresenter, + private val readReceiptBottomSheetPresenter: ReadReceiptBottomSheetPresenter, private val networkMonitor: NetworkMonitor, private val snackbarDispatcher: SnackbarDispatcher, private val messageSummaryFormatter: MessageSummaryFormatter, @@ -124,6 +126,7 @@ class MessagesPresenter @AssistedInject constructor( val customReactionState = customReactionPresenter.present() val reactionSummaryState = reactionSummaryPresenter.present() val retryState = retrySendMenuPresenter.present() + val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) @@ -157,10 +160,10 @@ class MessagesPresenter @AssistedInject constructor( val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true) var enableVoiceMessages by remember { mutableStateOf(false) } - var enableInRoomCalls by remember { mutableStateOf(false) } + // TODO add min power level to use this feature in the future? + val enableInRoomCalls = true LaunchedEffect(featureFlagsService) { enableVoiceMessages = featureFlagsService.isFeatureEnabled(FeatureFlags.VoiceMessages) - enableInRoomCalls = featureFlagsService.isFeatureEnabled(FeatureFlags.InRoomCalls) } fun handleEvents(event: MessagesEvents) { @@ -201,6 +204,7 @@ class MessagesPresenter @AssistedInject constructor( customReactionState = customReactionState, reactionSummaryState = reactionSummaryState, retrySendMenuState = retryState, + readReceiptBottomSheetState = readReceiptBottomSheetState, hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, snackbarMessage = snackbarMessage, showReinvitePrompt = showReinvitePrompt, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 5bce4f19a1..325c695988 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.libraries.architecture.Async @@ -43,6 +44,7 @@ data class MessagesState( val customReactionState: CustomReactionState, val reactionSummaryState: ReactionSummaryState, val retrySendMenuState: RetrySendMenuState, + val readReceiptBottomSheetState: ReadReceiptBottomSheetState, val hasNetworkConnection: Boolean, val snackbarMessage: SnackbarMessage?, val inviteProgress: Async, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 275b324862..720c385f89 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -24,9 +24,11 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuState import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState +import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -65,7 +67,14 @@ open class MessagesStateProvider : PreviewParameterProvider { ), aMessagesState().copy( isCallOngoing = true, - ) + ), + aMessagesState().copy( + enableVoiceMessages = true, + voiceMessageComposerState = aVoiceMessageComposerState( + voiceMessageState = aVoiceMessagePreviewState(), + showSendFailureDialog = true + ), + ), ) } @@ -88,6 +97,10 @@ fun aMessagesState() = MessagesState( selectedEvent = null, eventSink = {}, ), + readReceiptBottomSheetState = ReadReceiptBottomSheetState( + selectedEvent = null, + eventSink = {}, + ), actionListState = anActionListState(), customReactionState = CustomReactionState( target = CustomReactionState.Target.None, 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 4d7ef3ef3e..c00fbe09aa 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -41,6 +41,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -70,11 +71,14 @@ import io.element.android.features.messages.impl.timeline.components.customreact import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuEvents import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMessageMenu import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerEvents import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessagePermissionRationaleDialog +import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageSendingFailedDialog import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView import io.element.android.libraries.androidutils.ui.hideKeyboard import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule @@ -211,6 +215,9 @@ fun MessagesView( onReactionClicked = ::onEmojiReactionClicked, onReactionLongClicked = ::onEmojiReactionLongClicked, onMoreReactionsClicked = ::onMoreReactionsClicked, + onReadReceiptClick = { event -> + state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(event)) + }, onSendLocationClicked = onSendLocationClicked, onCreatePollClicked = onCreatePollClicked, onSwipeToReply = { targetEvent -> @@ -245,13 +252,12 @@ fun MessagesView( ) ReactionSummaryView(state = state.reactionSummaryState) - RetrySendMessageMenu( - state = state.retrySendMenuState - ) - - ReinviteDialog( - state = state + RetrySendMessageMenu(state = state.retrySendMenuState) + ReadReceiptBottomSheet( + state = state.readReceiptBottomSheetState, + onUserDataClicked = onUserDataClicked, ) + ReinviteDialog(state = state) // Since the textfield is now based on an Android view, this is no longer done automatically. // We need to hide the keyboard automatically when navigating out of this screen. @@ -309,6 +315,7 @@ private fun MessagesViewContent( onReactionClicked: (key: String, TimelineItem.Event) -> Unit, onReactionLongClicked: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClicked: (TimelineItem.Event) -> Unit, + onReadReceiptClick: (TimelineItem.Event) -> Unit, onMessageLongClicked: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSendLocationClicked: () -> Unit, @@ -340,15 +347,20 @@ private fun MessagesViewContent( appName = state.appName ) } + if (state.enableVoiceMessages && state.voiceMessageComposerState.showSendFailureDialog) { + VoiceMessageSendingFailedDialog( + onDismiss = { state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.DismissSendFailureDialog) }, + ) + } // This key is used to force the sheet to be remeasured when the content changes. // Any state change that should trigger a height size should be added to the list of remembered values here. - val sheetResizeContentKey = remember( - state.composerState.mode.relatedEventId, + val sheetResizeContentKey = remember { mutableIntStateOf(0) } + LaunchedEffect( state.composerState.richTextEditorState.lineCount, - state.composerState.memberSuggestions.size + state.composerState.showTextFormatting, ) { - Random.nextInt() + sheetResizeContentKey.intValue = Random.nextInt() } ExpandableBottomSheetScaffold( @@ -367,6 +379,7 @@ private fun MessagesViewContent( TimelineView( modifier = Modifier.padding(paddingValues), state = state.timelineState, + roomName = state.roomName.dataOrNull(), onMessageClicked = onMessageClicked, onMessageLongClicked = onMessageLongClicked, onUserDataClicked = onUserDataClicked, @@ -374,6 +387,7 @@ private fun MessagesViewContent( onReactionClicked = onReactionClicked, onReactionLongClicked = onReactionLongClicked, onMoreReactionsClicked = onMoreReactionsClicked, + onReadReceiptClick = onReadReceiptClick, onSwipeToReply = onSwipeToReply, ) }, @@ -383,7 +397,7 @@ private fun MessagesViewContent( state = state, ) }, - sheetContentKey = sheetResizeContentKey, + sheetContentKey = sheetResizeContentKey.intValue, sheetTonalElevation = 0.dp, sheetShadowElevation = if (state.composerState.memberSuggestions.isNotEmpty()) 16.dp else 0.dp, ) @@ -399,7 +413,8 @@ private fun MessagesViewComposerBottomSheetContents( if (state.userHasPermissionToSendMessage) { Column(modifier = modifier.fillMaxWidth()) { MentionSuggestionsPickerView( - modifier = Modifier.heightIn(max = 230.dp) + modifier = Modifier + .heightIn(max = 230.dp) // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions .nestedScroll(object : NestedScrollConnection { override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { @@ -411,7 +426,7 @@ private fun MessagesViewComposerBottomSheetContents( roomAvatarData = state.roomAvatar.dataOrNull(), memberSuggestions = state.composerState.memberSuggestions, onSuggestionSelected = { - // TODO pass the selected suggestion to the RTE so it can be inserted as a pill + state.composerState.eventSink(MessageComposerEvents.InsertMention(it)) } ) MessageComposerView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index bd20a3704b..1e57de2466 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -26,6 +26,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -33,9 +35,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ListItem -import androidx.compose.material.Text import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -70,12 +69,17 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatterImpl import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.hide import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail @@ -140,15 +144,13 @@ fun ActionListView( onEmojiReactionClicked = ::onEmojiReactionClicked, onCustomReactionClicked = ::onCustomReactionClicked, modifier = Modifier - .padding(bottom = 32.dp) -// .navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044 -// .imePadding() + .navigationBarsPadding() + .imePadding() ) } } } -@OptIn(ExperimentalMaterialApi::class) @Composable private fun SheetContent( state: ActionListState, @@ -198,18 +200,13 @@ private fun SheetContent( modifier = Modifier.clickable { onActionClicked(action) }, - text = { - Text( - text = stringResource(id = action.titleRes), - color = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, - ) + headlineContent = { + Text(text = stringResource(id = action.titleRes)) }, - icon = { - Icon( - resourceId = action.icon, - contentDescription = "", - tint = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, - ) + leadingContent = ListItemContent.Icon(IconSource.Resource(action.icon)), + style = when { + action.destructive -> ListItemStyle.Destructive + else -> ListItemStyle.Primary } ) } @@ -373,7 +370,7 @@ private fun EmojiReactionsRow( contentAlignment = Alignment.Center ) { Icon( - resourceId = CommonDrawables.ic_september_add_reaction, + resourceId = CommonDrawables.ic_add_reaction, contentDescription = "Emojis", tint = MaterialTheme.colorScheme.secondary, modifier = Modifier @@ -410,8 +407,7 @@ private fun EmojiButton( ) { Text( emoji, - fontSize = 24.dp.toSp(), - color = Color.White, + style = ElementTheme.typography.fontBodyLgRegular.copy(fontSize = 24.dp.toSp(), color = Color.White), modifier = Modifier .clickable( enabled = true, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index 9a71d7c179..d79b95ca70 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -28,13 +28,13 @@ sealed class TimelineItemAction( @DrawableRes val icon: Int, val destructive: Boolean = false ) { - data object Forward : TimelineItemAction(CommonStrings.action_forward, CommonDrawables.ic_september_forward) - data object Copy : TimelineItemAction(CommonStrings.action_copy, CommonDrawables.ic_september_copy) + data object Forward : TimelineItemAction(CommonStrings.action_forward, CommonDrawables.ic_forward) + data object Copy : TimelineItemAction(CommonStrings.action_copy, CommonDrawables.ic_copy) data object Redact : TimelineItemAction(CommonStrings.action_remove, CommonDrawables.ic_compound_delete, destructive = true) - data object Reply : TimelineItemAction(CommonStrings.action_reply, CommonDrawables.ic_september_reply) - data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CommonDrawables.ic_september_reply) - data object Edit : TimelineItemAction(CommonStrings.action_edit, CommonDrawables.ic_september_edit_outline) - data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_september_view_source) + data object Reply : TimelineItemAction(CommonStrings.action_reply, CommonDrawables.ic_reply) + data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CommonDrawables.ic_reply) + data object Edit : TimelineItemAction(CommonStrings.action_edit, CommonDrawables.ic_edit_outline) + data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options) data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CommonDrawables.ic_compound_chat_problem, destructive = true) data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CommonDrawables.ic_poll_end) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt index 4348b83a95..96e721e815 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt @@ -236,7 +236,7 @@ private fun MediaFileView( ) { Icon( imageVector = if (isAudio) Icons.Outlined.GraphicEq else null, - resourceId = if (isAudio) null else CommonDrawables.ic_september_attachment, + resourceId = if (isAudio) null else CommonDrawables.ic_attachment, contentDescription = null, tint = MaterialTheme.colorScheme.background, modifier = Modifier diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestion.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestion.kt new file mode 100644 index 0000000000..b2977bd508 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestion.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.mentions + +import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.room.RoomMember + +@Immutable +sealed interface MentionSuggestion { + data object Room : MentionSuggestion + data class Member(val roomMember: RoomMember) : MentionSuggestion +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt index 6b937a872e..779398b020 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.R -import io.element.android.features.messages.impl.messagecomposer.RoomMemberSuggestion 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 @@ -52,8 +51,8 @@ fun MentionSuggestionsPickerView( roomId: RoomId, roomName: String?, roomAvatarData: AvatarData?, - memberSuggestions: ImmutableList, - onSuggestionSelected: (RoomMemberSuggestion) -> Unit, + memberSuggestions: ImmutableList, + onSuggestionSelected: (MentionSuggestion) -> Unit, modifier: Modifier = Modifier, ) { LazyColumn( @@ -63,8 +62,8 @@ fun MentionSuggestionsPickerView( memberSuggestions, key = { suggestion -> when (suggestion) { - is RoomMemberSuggestion.Room -> "@room" - is RoomMemberSuggestion.Member -> suggestion.roomMember.userId.value + is MentionSuggestion.Room -> "@room" + is MentionSuggestion.Member -> suggestion.roomMember.userId.value } } ) { @@ -85,18 +84,18 @@ fun MentionSuggestionsPickerView( @Composable private fun RoomMemberSuggestionItemView( - memberSuggestion: RoomMemberSuggestion, + memberSuggestion: MentionSuggestion, roomId: String, roomName: String?, roomAvatar: AvatarData?, - onSuggestionSelected: (RoomMemberSuggestion) -> Unit, + onSuggestionSelected: (MentionSuggestion) -> Unit, modifier: Modifier = Modifier, ) { Row(modifier = modifier.clickable { onSuggestionSelected(memberSuggestion) }, horizontalArrangement = Arrangement.spacedBy(16.dp)) { val avatarSize = AvatarSize.TimelineRoom val avatarData = when (memberSuggestion) { - is RoomMemberSuggestion.Room -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize) - is RoomMemberSuggestion.Member -> AvatarData( + is MentionSuggestion.Room -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize) + is MentionSuggestion.Member -> AvatarData( memberSuggestion.roomMember.userId.value, memberSuggestion.roomMember.displayName, memberSuggestion.roomMember.avatarUrl, @@ -104,13 +103,13 @@ private fun RoomMemberSuggestionItemView( ) } val title = when (memberSuggestion) { - is RoomMemberSuggestion.Room -> stringResource(R.string.screen_room_mentions_at_room_title) - is RoomMemberSuggestion.Member -> memberSuggestion.roomMember.displayName + is MentionSuggestion.Room -> stringResource(R.string.screen_room_mentions_at_room_title) + is MentionSuggestion.Member -> memberSuggestion.roomMember.displayName } val subtitle = when (memberSuggestion) { - is RoomMemberSuggestion.Room -> "@room" - is RoomMemberSuggestion.Member -> memberSuggestion.roomMember.userId.value + is MentionSuggestion.Room -> "@room" + is MentionSuggestion.Member -> memberSuggestion.roomMember.userId.value } Avatar(avatarData = avatarData, modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)) @@ -159,9 +158,9 @@ internal fun MentionSuggestionsPickerView_Preview() { roomName = "Room", roomAvatarData = null, memberSuggestions = persistentListOf( - RoomMemberSuggestion.Room, - RoomMemberSuggestion.Member(roomMember), - RoomMemberSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")), + MentionSuggestion.Room, + MentionSuggestion.Member(roomMember), + MentionSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")), ), onSuggestionSelected = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt index 1f4e2b5376..d2c3e6e518 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt @@ -16,7 +16,7 @@ package io.element.android.features.messages.impl.mentions -import io.element.android.features.messages.impl.messagecomposer.RoomMemberSuggestion +import io.element.android.libraries.core.data.filterUpTo 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 @@ -46,10 +46,8 @@ object MentionSuggestionsProcessor { roomMembersState: MatrixRoomMembersState, currentUserId: UserId, canSendRoomMention: suspend () -> Boolean, - ): List { + ): List { val members = roomMembersState.roomMembers() - // Take the first MAX_BATCH_ITEMS only - ?.take(MAX_BATCH_ITEMS) return when { members.isNullOrEmpty() || suggestion == null -> { // Clear suggestions @@ -61,7 +59,7 @@ object MentionSuggestionsProcessor { // Replace suggestions val matchingMembers = getMemberSuggestions( query = suggestion.text, - roomMembers = roomMembersState.roomMembers(), + roomMembers = members, currentUserId = currentUserId, canSendRoomMention = canSendRoomMention() ) @@ -81,7 +79,7 @@ object MentionSuggestionsProcessor { roomMembers: List?, currentUserId: UserId, canSendRoomMention: Boolean, - ): List { + ): List { return if (roomMembers.isNullOrEmpty()) { emptyList() } else { @@ -95,14 +93,14 @@ object MentionSuggestionsProcessor { } val matchingMembers = roomMembers - // Search only in joined members, exclude the current user - .filter { member -> + // Search only in joined members, up to MAX_BATCH_ITEMS, exclude the current user + .filterUpTo(MAX_BATCH_ITEMS) { member -> isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query) } - .map(RoomMemberSuggestion::Member) + .map(MentionSuggestion::Member) if ("room".contains(query) && canSendRoomMention) { - listOf(RoomMemberSuggestion.Room) + matchingMembers + listOf(MentionSuggestion.Room) + matchingMembers } else { matchingMembers } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt index b920e5967d..e29e64fae8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt @@ -19,9 +19,8 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ListItem +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -33,12 +32,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.R import io.element.android.libraries.androidutils.ui.hideKeyboard -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables @@ -93,7 +94,6 @@ internal fun AttachmentsBottomSheet( } } -@OptIn(ExperimentalMaterialApi::class) @Composable private fun AttachmentSourcePickerMenu( state: MessageComposerState, @@ -103,28 +103,33 @@ private fun AttachmentSourcePickerMenu( modifier: Modifier = Modifier, ) { Column( - modifier.padding(bottom = 32.dp) -// .navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044 + modifier = modifier + .navigationBarsPadding() + .imePadding() ) { ListItem( modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) }, - icon = { Icon(CommonDrawables.ic_september_photo_video_library, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_image)), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) }, + style = ListItemStyle.Primary, ) ListItem( modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) }, - icon = { Icon(CommonDrawables.ic_september_attachment, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_files)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_attachment)), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_files)) }, + style = ListItemStyle.Primary, ) ListItem( modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) }, - icon = { Icon(CommonDrawables.ic_september_take_photo_camera, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_take_photo_camera, )), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) }, + style = ListItemStyle.Primary, ) ListItem( modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) }, - icon = { Icon(CommonDrawables.ic_september_video_call, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_video_call)), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) }, + style = ListItemStyle.Primary, ) if (state.canShareLocation) { ListItem( @@ -132,8 +137,9 @@ private fun AttachmentSourcePickerMenu( state.eventSink(MessageComposerEvents.PickAttachmentSource.Location) onSendLocationClicked() }, - icon = { Icon(CommonDrawables.ic_september_location, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_location)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_location_pin) ), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_location)) }, + style = ListItemStyle.Primary, ) } if (state.canCreatePoll) { @@ -142,15 +148,17 @@ private fun AttachmentSourcePickerMenu( state.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) onCreatePollClicked() }, - icon = { Icon(CommonDrawables.ic_compound_polls, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_compound_polls)), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_poll)) }, + style = ListItemStyle.Primary, ) } if (enableTextFormatting) { ListItem( modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) }, - icon = { Icon(CommonDrawables.ic_september_text_formatting, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_text_formatting, null)), + headlineContent = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) }, + style = ListItemStyle.Primary, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt index 97c2e7015d..c8ce20b6b9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt @@ -17,6 +17,7 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Immutable +import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.libraries.textcomposer.model.Message import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion @@ -41,4 +42,5 @@ sealed interface MessageComposerEvents { data object CancelSendAttachment : MessageComposerEvents data class Error(val error: Throwable) : MessageComposerEvents data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents + data class InsertMention(val mention: MentionSuggestion) : MessageComposerEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index c4b631e4df..dd2537ff18 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -20,7 +20,6 @@ import android.Manifest import android.annotation.SuppressLint import android.net.Uri import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -36,6 +35,7 @@ import im.vector.app.features.analytics.plan.Composer import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError import io.element.android.features.messages.impl.media.local.LocalMediaFactory +import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -45,8 +45,9 @@ import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.ProgressCallback +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.RoomMember +import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaSender @@ -67,6 +68,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import javax.inject.Inject @@ -87,7 +90,7 @@ class MessageComposerPresenter @Inject constructor( private val messageComposerContext: MessageComposerContextImpl, private val richTextEditorStateFactory: RichTextEditorStateFactory, private val currentSessionIdHolder: CurrentSessionIdHolder, - permissionsPresenterFactory: PermissionsPresenter.Factory + permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) @@ -173,7 +176,7 @@ class MessageComposerPresenter @Inject constructor( } } - val memberSuggestions = remember { mutableStateListOf() } + val memberSuggestions = remember { mutableStateListOf() } LaunchedEffect(isMentionsEnabled) { if (!isMentionsEnabled) return@LaunchedEffect val currentUserId = currentSessionIdHolder.current @@ -184,8 +187,11 @@ class MessageComposerPresenter @Inject constructor( return !roomIsDm && userCanSendAtRoom } - suggestionSearchTrigger - .debounce(0.5.seconds) + // This will trigger a search immediately when `@` is typed + val mentionStartTrigger = suggestionSearchTrigger.filter { it?.text.isNullOrEmpty() } + // This will start a search when the user changes the text after the `@` with a debounce to prevent too much wasted work + val mentionCompletionTrigger = suggestionSearchTrigger.filter { !it?.text.isNullOrEmpty() }.debounce(0.3.seconds) + merge(mentionStartTrigger, mentionCompletionTrigger) .combine(room.membersStateFlow) { suggestion, roomMembersState -> memberSuggestions.clear() val result = MentionSuggestionsProcessor.process( @@ -284,6 +290,20 @@ class MessageComposerPresenter @Inject constructor( is MessageComposerEvents.SuggestionReceived -> { suggestionSearchTrigger.value = event.suggestion } + is MessageComposerEvents.InsertMention -> { + localCoroutineScope.launch { + when (val mention = event.mention) { + is MentionSuggestion.Room -> { + richTextEditorState.insertAtRoomMentionAtSuggestion() + } + is MentionSuggestion.Member -> { + val text = mention.roomMember.displayName?.prependIndent("@") ?: mention.roomMember.userId.value + val link = PermalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch + richTextEditorState.insertMentionAtSuggestion(text = text, link = link) + } + } + } + } } } @@ -297,6 +317,7 @@ class MessageComposerPresenter @Inject constructor( canCreatePoll = canCreatePoll.value, attachmentsState = attachmentsState.value, memberSuggestions = memberSuggestions.toPersistentList(), + currentUserId = currentSessionIdHolder.current, eventSink = { handleEvents(it) } ) } @@ -307,15 +328,25 @@ class MessageComposerPresenter @Inject constructor( richTextEditorState: RichTextEditorState, ) = launch { val capturedMode = messageComposerContext.composerMode + val mentions = richTextEditorState.mentionsState?.let { state -> + buildList { + if (state.hasAtRoomMention) { + add(Mention.AtRoom) + } + for (userId in state.userIds) { + add(Mention.User(userId)) + } + } + }.orEmpty() // Reset composer right away richTextEditorState.setHtml("") updateComposerMode(MessageComposerMode.Normal) when (capturedMode) { - is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html) + is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html, mentions = mentions) is MessageComposerMode.Edit -> { val eventId = capturedMode.eventId val transactionId = capturedMode.transactionId - room.editMessage(eventId, transactionId, message.markdown, message.html) + room.editMessage(eventId, transactionId, message.markdown, message.html, mentions) } is MessageComposerMode.Quote -> TODO() @@ -323,6 +354,7 @@ class MessageComposerPresenter @Inject constructor( capturedMode.eventId, message.markdown, message.html, + mentions ) } analyticsService.capture( @@ -410,8 +442,3 @@ class MessageComposerPresenter @Inject constructor( } } -@Immutable -sealed interface RoomMemberSuggestion { - data object Room : RoomMemberSuggestion - data class Member(val roomMember: RoomMember) : RoomMemberSuggestion -} 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 6a9d963d18..09eb51477f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -19,6 +19,8 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import io.element.android.features.messages.impl.attachments.Attachment +import io.element.android.features.messages.impl.mentions.MentionSuggestion +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.ImmutableList @@ -33,7 +35,8 @@ data class MessageComposerState( val canShareLocation: Boolean, val canCreatePoll: Boolean, val attachmentsState: AttachmentsState, - val memberSuggestions: ImmutableList, + val memberSuggestions: ImmutableList, + val currentUserId: UserId, val eventSink: (MessageComposerEvents) -> Unit, ) { val hasFocus: Boolean = richTextEditorState.hasFocus 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 ac936b2118..a542cb772d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -17,6 +17,8 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.mentions.MentionSuggestion +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.ImmutableList @@ -38,7 +40,7 @@ fun aMessageComposerState( canShareLocation: Boolean = true, canCreatePoll: Boolean = true, attachmentsState: AttachmentsState = AttachmentsState.None, - memberSuggestions: ImmutableList = persistentListOf(), + memberSuggestions: ImmutableList = persistentListOf(), ) = MessageComposerState( richTextEditorState = composerState, isFullScreen = isFullScreen, @@ -49,5 +51,6 @@ fun aMessageComposerState( canCreatePoll = canCreatePoll, attachmentsState = attachmentsState, memberSuggestions = memberSuggestions, + currentUserId = UserId("@alice:localhost"), 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 e34a22eedc..28e40a910f 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 @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerEvents @@ -32,9 +33,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.textcomposer.TextComposer import io.element.android.libraries.textcomposer.model.Message -import io.element.android.libraries.textcomposer.model.PressEvent import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import kotlinx.coroutines.launch @Composable @@ -46,6 +47,7 @@ internal fun MessageComposerView( enableVoiceMessages: Boolean, modifier: Modifier = Modifier, ) { + val view = LocalView.current fun sendMessage(message: Message) { state.eventSink(MessageComposerEvents.SendMessage(message)) } @@ -59,6 +61,7 @@ internal fun MessageComposerView( } fun onDismissTextFormatting() { + view.clearFocus() state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = false)) } @@ -77,8 +80,8 @@ internal fun MessageComposerView( } } - val onVoiceRecordButtonEvent = { press: PressEvent -> - voiceMessageState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(press)) + val onVoiceRecorderEvent = { press: VoiceMessageRecorderEvent -> + voiceMessageState.eventSink(VoiceMessageComposerEvents.RecorderEvent(press)) } val onSendVoiceMessage = { @@ -107,12 +110,13 @@ internal fun MessageComposerView( onDismissTextFormatting = ::onDismissTextFormatting, enableTextFormatting = enableTextFormatting, enableVoiceMessages = enableVoiceMessages, - onVoiceRecordButtonEvent = onVoiceRecordButtonEvent, + onVoiceRecorderEvent = onVoiceRecorderEvent, onVoicePlayerEvent = onVoicePlayerEvent, onSendVoiceMessage = onSendVoiceMessage, onDeleteVoiceMessage = onDeleteVoiceMessage, onSuggestionReceived = ::onSuggestionReceived, onError = ::onError, + currentUserId = state.currentUserId, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt index d75c8f45c6..f4b152ecab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.features.messages.impl.R import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.components.async.AsyncView import io.element.android.libraries.designsystem.components.button.BackButton @@ -101,14 +102,14 @@ fun ReportMessageView( OutlinedTextField( value = state.reason, onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) }, - placeholder = { Text(stringResource(CommonStrings.report_content_hint)) }, + placeholder = { Text(stringResource(R.string.report_content_hint)) }, enabled = !isSending, modifier = Modifier .fillMaxWidth() .heightIn(min = 90.dp) ) Text( - text = stringResource(CommonStrings.report_content_explanation), + text = stringResource(R.string.report_content_explanation), style = ElementTheme.typography.fontBodySmRegular, color = MaterialTheme.colorScheme.secondary, textAlign = TextAlign.Start, @@ -122,11 +123,11 @@ fun ReportMessageView( ) { Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { Text( - text = stringResource(CommonStrings.screen_report_content_block_user), + text = stringResource(R.string.screen_report_content_block_user), style = ElementTheme.typography.fontBodyLgRegular, ) Text( - text = stringResource(CommonStrings.screen_report_content_block_user_hint), + text = stringResource(R.string.screen_report_content_block_user_hint), style = ElementTheme.typography.fontBodyMdRegular, color = MaterialTheme.colorScheme.secondary, ) 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 0a0feedf65..0bad8f8830 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 @@ -32,13 +32,17 @@ import im.vector.app.features.analytics.plan.PollVote import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.session.SessionState +import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus @@ -63,6 +67,8 @@ class TimelinePresenter @Inject constructor( private val analyticsService: AnalyticsService, private val verificationService: SessionVerificationService, private val encryptionService: EncryptionService, + private val featureFlagService: FeatureFlagService, + private val redactedVoiceMessageManager: RedactedVoiceMessageManager, ) : Presenter { private val timeline = room.timeline @@ -97,6 +103,9 @@ class TimelinePresenter @Inject constructor( } } + val readReceiptsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.ReadReceipts).collectAsState(initial = false) + val membersState by room.membersStateFlow.collectAsState() + fun handleEvents(event: TimelineEvents) { when (event) { TimelineEvents.LoadMore -> localScope.paginateBackwards() @@ -136,12 +145,23 @@ class TimelinePresenter @Inject constructor( LaunchedEffect(Unit) { timeline .timelineItems - .onEach(timelineItemsFactory::replaceWith) + .onEach { + timelineItemsFactory.replaceWith( + timelineItems = it, + roomMembers = if (readReceiptsEnabled) { + membersState.roomMembers().orEmpty() + } else { + // Give an empty list to not affect performance + emptyList() + } + ) + } .onEach { timelineItems -> if (timelineItems.isEmpty()) { paginateBackwards() } } + .onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem) .launchIn(this) } @@ -150,6 +170,7 @@ class TimelinePresenter @Inject constructor( userHasPermissionToSendMessage = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, + showReadReceipts = readReceiptsEnabled, hasNewItems = hasNewItems.value, sessionState = sessionState, eventSink = ::handleEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 173e33b9c9..62836db130 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList @Immutable data class TimelineState( val timelineItems: ImmutableList, + val showReadReceipts: Boolean, val highlightedEventId: EventId?, val userHasPermissionToSendMessage: Boolean, val paginationState: MatrixTimeline.PaginationState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 0e1795117f..3defab97f5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -16,9 +16,11 @@ package io.element.android.features.messages.impl.timeline +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent @@ -43,7 +45,12 @@ import kotlin.random.Random fun aTimelineState(timelineItems: ImmutableList = persistentListOf()) = TimelineState( timelineItems = timelineItems, - paginationState = MatrixTimeline.PaginationState(isBackPaginating = false, hasMoreToLoadBackwards = true), + showReadReceipts = false, + paginationState = MatrixTimeline.PaginationState( + isBackPaginating = false, + hasMoreToLoadBackwards = true, + beginningOfRoomReached = false, + ), highlightedEventId = null, userHasPermissionToSendMessage = true, hasNewItems = false, @@ -114,11 +121,12 @@ internal fun aTimelineItemEvent( senderDisplayName: String = "Sender", content: TimelineItemEventContent = aTimelineItemTextContent(), groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, - sendState: LocalEventSendState = LocalEventSendState.Sent(eventId), + sendState: LocalEventSendState? = null, inReplyTo: InReplyTo? = null, isThreaded: Boolean = false, debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(), + readReceiptState: TimelineItemReadReceipts = aTimelineItemReadReceipts(), ): TimelineItem.Event { return TimelineItem.Event( id = UUID.randomUUID().toString(), @@ -128,6 +136,7 @@ internal fun aTimelineItemEvent( senderAvatar = AvatarData("@senderId:domain", "sender", size = AvatarSize.TimelineSender), content = content, reactionsState = timelineItemReactions, + readReceiptState = readReceiptState, sentTime = "12:34", isMine = isMine, senderDisplayName = senderDisplayName, @@ -169,6 +178,12 @@ internal fun aTimelineItemDebugInfo( model, originalJson, latestEditedJson ) +internal fun aTimelineItemReadReceipts(): TimelineItemReadReceipts { + return TimelineItemReadReceipts( + receipts = emptyList().toImmutableList(), + ) +} + fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents { val event = aTimelineItemEvent( isMine = true, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 6d1929f825..1d831e5e21 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -59,6 +59,7 @@ import io.element.android.features.messages.impl.timeline.components.TimelineIte import io.element.android.features.messages.impl.timeline.components.TimelineItemStateEventRow import io.element.android.features.messages.impl.timeline.components.TimelineItemVirtualRow import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView +import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemRoomBeginningView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineLoadingMoreIndicator import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.aFakeTimelineItemPresenterFactories @@ -82,6 +83,7 @@ import kotlinx.coroutines.launch @Composable fun TimelineView( state: TimelineState, + roomName: String?, onUserDataClicked: (UserId) -> Unit, onMessageClicked: (TimelineItem.Event) -> Unit, onMessageLongClicked: (TimelineItem.Event) -> Unit, @@ -90,6 +92,7 @@ fun TimelineView( onReactionClicked: (emoji: String, TimelineItem.Event) -> Unit, onReactionLongClicked: (emoji: String, TimelineItem.Event) -> Unit, onMoreReactionsClicked: (TimelineItem.Event) -> Unit, + onReadReceiptClick: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, ) { fun onReachedLoadMore() { @@ -124,6 +127,9 @@ fun TimelineView( ) { timelineItem -> TimelineItemRow( timelineItem = timelineItem, + showReadReceipts = state.showReadReceipts, + isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true + && state.timelineItems.first().identifier() == timelineItem.identifier(), highlightedItem = state.highlightedEventId?.value, userHasPermissionToSendMessage = state.userHasPermissionToSendMessage, onClick = onMessageClicked, @@ -133,6 +139,7 @@ fun TimelineView( onReactionClick = onReactionClicked, onReactionLongClick = onReactionLongClicked, onMoreReactionsClick = onMoreReactionsClicked, + onReadReceiptClick = onReadReceiptClick, onTimestampClicked = onTimestampClicked, sessionState = state.sessionState, eventSink = state.eventSink, @@ -148,6 +155,11 @@ fun TimelineView( } } } + if (state.paginationState.beginningOfRoomReached) { + item(contentType = "BeginningOfRoomReached") { + TimelineItemRoomBeginningView(roomName = roomName) + } + } } TimelineScrollHelper( @@ -162,6 +174,8 @@ fun TimelineView( @Composable private fun TimelineItemRow( timelineItem: TimelineItem, + showReadReceipts: Boolean, + isLastOutgoingMessage: Boolean, highlightedItem: String?, userHasPermissionToSendMessage: Boolean, sessionState: SessionState, @@ -172,6 +186,7 @@ private fun TimelineItemRow( onReactionClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit, + onReadReceiptClick: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, eventSink: (TimelineEvents) -> Unit, @@ -198,6 +213,8 @@ private fun TimelineItemRow( } else { TimelineItemEventRow( event = timelineItem, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(), onClick = { onClick(timelineItem) }, @@ -207,6 +224,7 @@ private fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, + onReadReceiptClick = onReadReceiptClick, onTimestampClicked = onTimestampClicked, onSwipeToReply = { onSwipeToReply(timelineItem) }, eventSink = eventSink, @@ -237,6 +255,8 @@ private fun TimelineItemRow( timelineItem.events.forEach { subGroupEvent -> TimelineItemRow( timelineItem = subGroupEvent, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, userHasPermissionToSendMessage = false, @@ -248,6 +268,7 @@ private fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, + onReadReceiptClick = onReadReceiptClick, eventSink = eventSink, onSwipeToReply = {}, ) @@ -346,6 +367,7 @@ internal fun TimelineViewPreview( ) { TimelineView( state = aTimelineState(timelineItems), + roomName = null, onMessageClicked = {}, onTimestampClicked = {}, onUserDataClicked = {}, @@ -354,6 +376,7 @@ internal fun TimelineViewPreview( onReactionLongClicked = { _, _ -> }, onMoreReactionsClicked = {}, onSwipeToReply = {}, + onReadReceiptClick = {}, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt index 357c9b3c20..a5220e1d90 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt @@ -181,7 +181,7 @@ internal fun MessagesReactionButtonPreview(@PreviewParameter(AggregatedReactionP @Composable internal fun MessagesAddReactionButtonPreview() = ElementPreview { MessagesReactionButton( - content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_september_add_reaction), + content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), onClick = {}, onLongClick = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt index 50ba43f27c..17d1d11f83 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt @@ -49,7 +49,7 @@ fun RowScope.ReplySwipeIndicator( alpha = swipeProgress() }, contentDescription = null, - resourceId = CommonDrawables.ic_september_reply, + resourceId = CommonDrawables.ic_reply, ) } 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 cfb81f71a7..5f7fff64ae 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 @@ -66,6 +66,8 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding +import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState +import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState @@ -114,6 +116,8 @@ import kotlin.math.roundToInt @Composable fun TimelineItemEventRow( event: TimelineItem.Event, + showReadReceipts: Boolean, + isLastOutgoingMessage: Boolean, isHighlighted: Boolean, canReply: Boolean, onClick: () -> Unit, @@ -124,6 +128,7 @@ fun TimelineItemEventRow( onReactionClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onReactionLongClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, + onReadReceiptClick: (event: TimelineItem.Event) -> Unit, onSwipeToReply: () -> Unit, eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier @@ -173,6 +178,8 @@ fun TimelineItemEventRow( state = state.draggableState, ), event = event, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, interactionSource = interactionSource, onClick = onClick, @@ -183,6 +190,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onReadReceiptsClicked = { onReadReceiptClick(event) }, eventSink = eventSink, ) } @@ -190,6 +198,8 @@ fun TimelineItemEventRow( } else { TimelineItemEventRowContent( event = event, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, interactionSource = interactionSource, onClick = onClick, @@ -200,6 +210,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onReadReceiptsClicked = { onReadReceiptClick(event) }, eventSink = eventSink, ) } @@ -232,6 +243,8 @@ private fun SwipeSensitivity( @Composable private fun TimelineItemEventRowContent( event: TimelineItem.Event, + showReadReceipts: Boolean, + isLastOutgoingMessage: Boolean, isHighlighted: Boolean, interactionSource: MutableInteractionSource, onClick: () -> Unit, @@ -240,6 +253,7 @@ private fun TimelineItemEventRowContent( inReplyToClicked: () -> Unit, onUserDataClicked: () -> Unit, onReactionClicked: (emoji: String) -> Unit, + onReadReceiptsClicked: () -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, eventSink: (TimelineEvents) -> Unit, @@ -256,7 +270,12 @@ private fun TimelineItemEventRowContent( .wrapContentHeight() .fillMaxWidth(), ) { - val (sender, message, reactions) = createRefs() + val ( + sender, + message, + reactions, + readReceipts, + ) = createRefs() // Sender val avatarStrokeSize = 3.dp @@ -322,6 +341,25 @@ private fun TimelineItemEventRowContent( .padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp) ) } + + // Read receipts / Send state + TimelineItemReadReceiptView( + state = ReadReceiptViewState( + sendState = event.localSendState, + isLastOutgoingMessage = isLastOutgoingMessage, + receipts = event.readReceiptState.receipts, + ), + showReadReceipts = showReadReceipts, + onReadReceiptsClicked = onReadReceiptsClicked, + modifier = Modifier + .constrainAs(readReceipts) { + if (event.reactionsState.reactions.isNotEmpty()) { + top.linkTo(reactions.bottom, margin = 4.dp) + } else { + top.linkTo(message.bottom, margin = 4.dp) + } + } + ) } } @@ -383,22 +421,6 @@ private fun MessageEventBubbleContent( // to its `combinedClickable` parent so we do it manually fun onTimestampLongClick() = onMessageLongClick() - @Composable - fun ContentView( - modifier: Modifier = Modifier - ) { - TimelineItemEventContentView( - content = event.content, - isMine = event.isMine, - interactionSource = interactionSource, - onClick = onMessageClick, - onLongClick = onMessageLongClick, - extraPadding = event.toExtraPadding(), - eventSink = eventSink, - modifier = modifier, - ) - } - @Composable fun ThreadDecoration( modifier: Modifier = Modifier @@ -422,21 +444,20 @@ private fun MessageEventBubbleContent( } @Composable - fun ContentAndTimestampView( + fun WithTimestampLayout( timestampPosition: TimestampPosition, modifier: Modifier = Modifier, - contentModifier: Modifier = Modifier, - timestampModifier: Modifier = Modifier, + content: @Composable () -> Unit, ) { when (timestampPosition) { TimestampPosition.Overlay -> Box(modifier) { - ContentView(modifier = contentModifier) + content() TimelineEventTimestampView( event = event, onClick = onTimestampClicked, onLongClick = ::onTimestampLongClick, - modifier = timestampModifier + modifier = Modifier .padding(horizontal = 4.dp, vertical = 4.dp) // Outer padding .background(ElementTheme.colors.bgSubtleSecondary, RoundedCornerShape(10.0.dp)) .align(Alignment.BottomEnd) @@ -445,24 +466,24 @@ private fun MessageEventBubbleContent( } TimestampPosition.Aligned -> Box(modifier) { - ContentView(modifier = contentModifier) + content() TimelineEventTimestampView( event = event, onClick = onTimestampClicked, onLongClick = ::onTimestampLongClick, - modifier = timestampModifier + modifier = Modifier .align(Alignment.BottomEnd) .padding(horizontal = 8.dp, vertical = 4.dp) ) } TimestampPosition.Below -> Column(modifier) { - ContentView(modifier = contentModifier) + content() TimelineEventTimestampView( event = event, onClick = onTimestampClicked, onLongClick = ::onTimestampLongClick, - modifier = timestampModifier + modifier = Modifier .align(Alignment.End) .padding(horizontal = 8.dp, vertical = 4.dp) ) @@ -478,52 +499,77 @@ private fun MessageEventBubbleContent( inReplyToDetails: InReplyTo.Ready?, modifier: Modifier = Modifier ) { - val modifierWithPadding: Modifier + val timestampLayoutModifier: Modifier val contentModifier: Modifier when { inReplyToDetails != null -> { if (timestampPosition == TimestampPosition.Overlay) { - modifierWithPadding = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) + timestampLayoutModifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) contentModifier = Modifier.clip(RoundedCornerShape(12.dp)) } else { contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 0.dp, bottom = 8.dp) - modifierWithPadding = Modifier + timestampLayoutModifier = Modifier } } timestampPosition != TimestampPosition.Overlay -> { - modifierWithPadding = Modifier + timestampLayoutModifier = Modifier contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp) } else -> { - modifierWithPadding = Modifier + timestampLayoutModifier = Modifier contentModifier = Modifier } } - - EqualWidthColumn(modifier = modifier, spacing = 8.dp) { + val threadDecoration = @Composable { if (showThreadDecoration) { ThreadDecoration(modifier = Modifier.padding(top = 8.dp, start = 12.dp, end = 12.dp)) } - if (inReplyToDetails != null) { - val senderName = inReplyToDetails.senderDisplayName ?: inReplyToDetails.senderId.value - val attachmentThumbnailInfo = attachmentThumbnailInfoForInReplyTo(inReplyToDetails) - val text = textForInReplyTo(inReplyToDetails) - val topPadding = if (showThreadDecoration) 0.dp else 8.dp - ReplyToContent( - senderName = senderName, - text = text, - attachmentThumbnailInfo = attachmentThumbnailInfo, - modifier = Modifier - .padding(top = topPadding, start = 8.dp, end = 8.dp) - .clip(RoundedCornerShape(6.dp)) - .clickable(enabled = true, onClick = inReplyToClick), + } + val contentWithTimestamp = @Composable { + WithTimestampLayout( + timestampPosition = timestampPosition, + modifier = timestampLayoutModifier, + ) { + TimelineItemEventContentView( + content = event.content, + isMine = event.isMine, + interactionSource = interactionSource, + onClick = onMessageClick, + onLongClick = onMessageLongClick, + extraPadding = event.toExtraPadding(), + eventSink = eventSink, + modifier = contentModifier, ) } - ContentAndTimestampView( - timestampPosition = timestampPosition, - modifier = modifierWithPadding, - contentModifier = contentModifier, + } + val inReplyTo = @Composable { inReplyToReady: InReplyTo.Ready -> + val senderName = inReplyToReady.senderDisplayName ?: inReplyToReady.senderId.value + val attachmentThumbnailInfo = attachmentThumbnailInfoForInReplyTo(inReplyToReady) + val text = textForInReplyTo(inReplyToReady) + val topPadding = if (showThreadDecoration) 0.dp else 8.dp + ReplyToContent( + senderName = senderName, + text = text, + attachmentThumbnailInfo = attachmentThumbnailInfo, + modifier = Modifier + .padding(top = topPadding, start = 8.dp, end = 8.dp) + .clip(RoundedCornerShape(6.dp)) + .clickable(enabled = true, onClick = inReplyToClick), ) + + } + if (inReplyToDetails != null) { + // Use SubComposeLayout only if necessary as it can have consequences on the performance. + EqualWidthColumn(modifier = modifier, spacing = 8.dp) { + threadDecoration() + inReplyTo(inReplyToDetails) + contentWithTimestamp() + } + } else { + Column(modifier = modifier, verticalArrangement = spacedBy(8.dp)) { + threadDecoration() + contentWithTimestamp() + } } } @@ -650,6 +696,8 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { ), groupPosition = TimelineItemGroupPosition.First, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -659,6 +707,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -671,6 +720,8 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { ), groupPosition = TimelineItemGroupPosition.Last, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -680,6 +731,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -710,6 +762,8 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { inReplyTo = aInReplyToReady(replyContent), groupPosition = TimelineItemGroupPosition.First, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -719,6 +773,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -733,6 +788,8 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { isThreaded = true, groupPosition = TimelineItemGroupPosition.Last, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -742,6 +799,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -784,6 +842,8 @@ internal fun TimelineItemEventRowTimestampPreview( reactionsState = aTimelineItemReactions(count = 0), senderDisplayName = if (useDocument) "Document case" else "Text case", ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -793,6 +853,7 @@ internal fun TimelineItemEventRowTimestampPreview( onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -816,6 +877,8 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { ), timelineItemReactions = aTimelineItemReactions(count = 20), ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -825,6 +888,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, eventSink = {}, @@ -841,6 +905,8 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { event = aTimelineItemEvent( senderDisplayName = "a long sender display name to test single line and ellipsis at the end of the line", ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -850,6 +916,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, eventSink = {}, @@ -862,6 +929,8 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { TimelineItemEventRow( event = aTimelineItemEvent(content = aTimelineItemPollContent()), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -871,6 +940,7 @@ internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, eventSink = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt index f0d6871bf6..d30a60c7a4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt @@ -196,7 +196,7 @@ internal fun TimelineItemReactionsLayoutPreview() = ElementPreview { }, addMoreButton = { MessagesReactionButton( - content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_september_add_reaction), + content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), onClick = {}, onLongClick = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt index 3f625fd6e4..764b4cdea4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt @@ -69,7 +69,7 @@ private fun TimelineItemReactionsView( onToggleExpandClick: () -> Unit, modifier: Modifier = Modifier ) { - // In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL. + // In LTR languages we want an incoming message's reactions to be LTR and outgoing to be RTL. // For RTL languages it should be the opposite. val currentLayout = LocalLayoutDirection.current val reactionsLayoutDirection = if (!isOutgoing) currentLayout @@ -95,7 +95,7 @@ private fun TimelineItemReactionsView( }, addMoreButton = { MessagesReactionButton( - content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_september_add_reaction), + content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), onClick = onMoreReactionsClick, onLongClick = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt index 7a476cb8a6..4bf8e5b847 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.features.messages.impl.timeline.components.virtual.TimelineEncryptedHistoryBannerView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView +import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemEncryptedHistoryBannerVirtualModel @@ -34,7 +35,7 @@ fun TimelineItemVirtualRow( ) { when (virtual.model) { is TimelineItemDaySeparatorModel -> TimelineItemDaySeparatorView(virtual.model, modifier) - TimelineItemReadMarkerModel -> return + TimelineItemReadMarkerModel -> TimelineItemReadMarkerView() is TimelineItemEncryptedHistoryBannerVirtualModel -> TimelineEncryptedHistoryBannerView(sessionState, modifier) } } 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 29d6b14e59..02ddfec026 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 @@ -27,6 +27,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SecondaryTabRow import androidx.compose.material3.Tab import androidx.compose.runtime.Composable @@ -47,7 +48,7 @@ import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.coroutines.launch -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun EmojiPicker( onEmojiSelected: (Emoji) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt index fb111cce97..bf1c69e612 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt @@ -28,31 +28,29 @@ import androidx.compose.material.icons.outlined.EmojiSymbols import androidx.compose.material.icons.outlined.EmojiTransportation import androidx.compose.ui.graphics.vector.ImageVector import io.element.android.emojibasebindings.EmojibaseCategory -import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.features.messages.impl.R @get:StringRes -val EmojibaseCategory.title: Int get() = - when(this){ - EmojibaseCategory.People -> CommonStrings.emoji_picker_category_people - EmojibaseCategory.Nature -> CommonStrings.emoji_picker_category_nature - EmojibaseCategory.Foods -> CommonStrings.emoji_picker_category_foods - EmojibaseCategory.Activity -> CommonStrings.emoji_picker_category_activity - EmojibaseCategory.Places -> CommonStrings.emoji_picker_category_places - EmojibaseCategory.Objects -> CommonStrings.emoji_picker_category_objects - EmojibaseCategory.Symbols -> CommonStrings.emoji_picker_category_symbols - EmojibaseCategory.Flags -> CommonStrings.emoji_picker_category_flags +val EmojibaseCategory.title: Int + get() = when (this) { + EmojibaseCategory.People -> R.string.emoji_picker_category_people + EmojibaseCategory.Nature -> R.string.emoji_picker_category_nature + EmojibaseCategory.Foods -> R.string.emoji_picker_category_foods + EmojibaseCategory.Activity -> R.string.emoji_picker_category_activity + EmojibaseCategory.Places -> R.string.emoji_picker_category_places + EmojibaseCategory.Objects -> R.string.emoji_picker_category_objects + EmojibaseCategory.Symbols -> R.string.emoji_picker_category_symbols + EmojibaseCategory.Flags -> R.string.emoji_picker_category_flags } val EmojibaseCategory.icon: ImageVector - get() = - when(this){ - EmojibaseCategory.People -> Icons.Outlined.EmojiPeople - EmojibaseCategory.Nature -> Icons.Outlined.EmojiNature - EmojibaseCategory.Foods -> Icons.Outlined.EmojiFoodBeverage - EmojibaseCategory.Activity -> Icons.Outlined.EmojiEvents - EmojibaseCategory.Places -> Icons.Outlined.EmojiTransportation - EmojibaseCategory.Objects -> Icons.Outlined.EmojiObjects - EmojibaseCategory.Symbols -> Icons.Outlined.EmojiSymbols - EmojibaseCategory.Flags -> Icons.Outlined.EmojiFlags - } - + get() = when (this) { + EmojibaseCategory.People -> Icons.Outlined.EmojiPeople + EmojibaseCategory.Nature -> Icons.Outlined.EmojiNature + EmojibaseCategory.Foods -> Icons.Outlined.EmojiFoodBeverage + EmojibaseCategory.Activity -> Icons.Outlined.EmojiEvents + EmojibaseCategory.Places -> Icons.Outlined.EmojiTransportation + EmojibaseCategory.Objects -> Icons.Outlined.EmojiObjects + EmojibaseCategory.Symbols -> Icons.Outlined.EmojiSymbols + EmojibaseCategory.Flags -> Icons.Outlined.EmojiFlags + } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt index 23c698cfc6..d488d01ef4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt @@ -35,7 +35,7 @@ fun TimelineItemEncryptedView( TimelineItemInformativeView( text = stringResource(id = CommonStrings.common_waiting_for_decryption_key), iconDescription = stringResource(id = CommonStrings.dialog_title_warning), - iconResourceId = CommonDrawables.ic_september_decryption_error, + iconResourceId = CommonDrawables.ic_waiting_to_decrypt, extraPadding = extraPadding, modifier = modifier ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt index f0f08fa2f5..a8fb3e1fcc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt @@ -59,7 +59,7 @@ fun TimelineItemFileView( contentAlignment = Alignment.Center, ) { Icon( - resourceId = CommonDrawables.ic_september_attachment, + resourceId = CommonDrawables.ic_attachment, contentDescription = "OpenFile", tint = ElementTheme.materialColors.primary, modifier = Modifier diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt index e7762fe278..6d3dd57994 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt @@ -27,6 +27,11 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -53,6 +58,7 @@ import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.coroutines.delay @Composable fun TimelineItemVoiceView( @@ -90,7 +96,7 @@ fun TimelineItemVoiceView( Spacer(Modifier.width(8.dp)) val context = LocalContext.current WaveformPlaybackView( - showCursor = state.button == VoiceMessageState.Button.Pause, + showCursor = state.showCursor, playbackProgress = state.progress, waveform = content.waveform, modifier = Modifier @@ -147,19 +153,40 @@ private fun RetryButton( } } +/** + * Progress button is shown when the voice message is being downloaded. + * + * The progress indicator is optimistic and displays a pause button (which + * indicates the audio is playing) for 2 seconds before revealing the + * actual progress indicator. + */ @Composable -private fun ProgressButton() { +private fun ProgressButton( + displayImmediately: Boolean = false, +) { + var canDisplay by remember { mutableStateOf(displayImmediately) } + LaunchedEffect(Unit) { + delay(2000L) + canDisplay = true + } CustomIconButton( onClick = {}, enabled = false, ) { - CircularProgressIndicator( - modifier = Modifier - .padding(2.dp) - .size(16.dp), - color = ElementTheme.colors.iconSecondary, - strokeWidth = 2.dp, - ) + if (canDisplay) { + CircularProgressIndicator( + modifier = Modifier + .padding(2.dp) + .size(16.dp), + color = ElementTheme.colors.iconSecondary, + strokeWidth = 2.dp, + ) + } else { + Icon( + resourceId = R.drawable.pause, + contentDescription = stringResource(id = CommonStrings.a11y_pause), + ) + } } } @@ -228,3 +255,12 @@ internal fun TimelineItemVoiceViewUnifiedPreview() = ElementPreview { } } } + +@PreviewsDayNight +@Composable +internal fun ProgressButtonPreview() = ElementPreview { + Row { + ProgressButton(displayImmediately = true) + ProgressButton(displayImmediately = false) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt new file mode 100644 index 0000000000..a15ecc781b --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt + +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import kotlinx.collections.immutable.ImmutableList + +data class ReadReceiptViewState( + val sendState: LocalEventSendState?, + val isLastOutgoingMessage: Boolean, + val receipts: ImmutableList, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt new file mode 100644 index 0000000000..e0f72b010d --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.anAvatarData +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import kotlinx.collections.immutable.toImmutableList + +class ReadReceiptViewStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aReadReceiptViewState(), + aReadReceiptViewState(sendState = LocalEventSendState.NotSentYet), + aReadReceiptViewState(sendState = LocalEventSendState.Sent(EventId("\$eventId"))), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(1) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(2) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(3) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(4) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(5) { add(aReadReceiptData(it)) } }, + ), + ) +} + +private fun aReadReceiptViewState( + sendState: LocalEventSendState? = null, + isLastOutgoingMessage: Boolean = true, + receipts: List = emptyList(), +) = ReadReceiptViewState( + sendState = sendState, + isLastOutgoingMessage = isLastOutgoingMessage, + receipts = receipts.toImmutableList(), +) + +private fun aReadReceiptData( + index: Int, + avatarData: AvatarData = anAvatarData( + id = "$index", + size = AvatarSize.TimelineReadReceipt + ), + formattedDate: String = "12:34", +) = ReadReceiptData( + avatarData = avatarData, + formattedDate = formattedDate, +) 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 new file mode 100644 index 0000000000..8725b365d0 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import io.element.android.appconfig.TimelineConfig +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.getBestName +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.CommonDrawables +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonPlurals +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun TimelineItemReadReceiptView( + state: ReadReceiptViewState, + showReadReceipts: Boolean, + onReadReceiptsClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + if (state.receipts.isNotEmpty()) { + if (showReadReceipts) { + ReadReceiptsRow(modifier = modifier) { + ReadReceiptsAvatars( + receipts = state.receipts, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .clickable { onReadReceiptsClicked() } + .padding(2.dp) + ) + } + } + } else when (state.sendState) { + LocalEventSendState.NotSentYet -> { + ReadReceiptsRow(modifier) { + Icon( + modifier = Modifier.padding(2.dp), + resourceId = CommonDrawables.ic_sending, + contentDescription = null, + tint = ElementTheme.colors.iconSecondary + ) + } + } + LocalEventSendState.Canceled -> Unit + is LocalEventSendState.SendingFailed -> { + // Error? The timestamp is already displayed in red + } + null, + is LocalEventSendState.Sent -> { + if (state.isLastOutgoingMessage) { + ReadReceiptsRow(modifier = modifier) { + Icon( + modifier = Modifier.padding(2.dp), + resourceId = CommonDrawables.ic_sent, + contentDescription = null, + tint = ElementTheme.colors.iconSecondary + ) + } + } + } + } +} + +@Composable +private fun ReadReceiptsRow( + modifier: Modifier = Modifier, + content: @Composable () -> Unit = {}, +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(AvatarSize.TimelineReadReceipt.dp + 8.dp) + .padding(horizontal = 18.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + ) { + content() + } + } +} + +@Composable +private fun ReadReceiptsAvatars( + receipts: ImmutableList, + modifier: Modifier = Modifier +) { + val avatarSize = AvatarSize.TimelineReadReceipt.dp + val avatarStrokeSize = 1.dp + val avatarStrokeColor = MaterialTheme.colorScheme.background + val receiptDescription = computeReceiptDescription(receipts) + Row( + modifier = modifier + .clearAndSetSemantics { + stateDescription = receiptDescription + }, + horizontalArrangement = Arrangement.spacedBy(4.dp - avatarStrokeSize), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + contentAlignment = Alignment.CenterEnd, + ) { + receipts + .take(TimelineConfig.maxReadReceiptToDisplay) + .reversed() + .forEachIndexed { index, readReceiptData -> + Box( + modifier = Modifier + .padding(end = (12.dp + avatarStrokeSize * 2) * index) + .size(size = avatarSize + avatarStrokeSize * 2) + .clip(CircleShape) + .background(avatarStrokeColor) + .zIndex(index.toFloat()), + contentAlignment = Alignment.Center, + ) { + Avatar( + avatarData = readReceiptData.avatarData, + ) + } + } + } + if (receipts.size > TimelineConfig.maxReadReceiptToDisplay) { + Text( + text = "+" + (receipts.size - TimelineConfig.maxReadReceiptToDisplay), + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } +} + +@Composable +private fun computeReceiptDescription(receipts: ImmutableList): String { + return when (receipts.size) { + 0 -> "" // Cannot happen + 1 -> stringResource( + id = CommonStrings.a11y_read_receipts_single, + receipts[0].avatarData.getBestName() + ) + 2 -> stringResource( + id = CommonStrings.a11y_read_receipts_multiple, + receipts[0].avatarData.getBestName(), + receipts[1].avatarData.getBestName(), + ) + else -> pluralStringResource( + id = CommonPlurals.a11y_read_receipts_multiple_with_others, + count = receipts.size - 1, + receipts[0].avatarData.getBestName(), + receipts.size - 1 + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TimelineItemReactionsViewPreview( + @PreviewParameter(ReadReceiptViewStateProvider::class) state: ReadReceiptViewState, +) = ElementPreview { + TimelineItemReadReceiptView( + state = state, + showReadReceipts = true, + onReadReceiptsClicked = {}, + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt new file mode 100644 index 0000000000..391ec3de7f --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.components.MatrixUserRow +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun ReadReceiptBottomSheet( + state: ReadReceiptBottomSheetState, + onUserDataClicked: (UserId) -> Unit, + modifier: Modifier = Modifier, +) { + val isVisible = state.selectedEvent != null + + val sheetState = rememberModalBottomSheetState() + val coroutineScope = rememberCoroutineScope() + if (isVisible) { + ModalBottomSheet( + modifier = modifier, +// modifier = modifier.navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044 +// .imePadding() + sheetState = sheetState, + onDismissRequest = { + coroutineScope.launch { + sheetState.hide() + state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + } + } + ) { + ReadReceiptBottomSheetContent( + state = state, + onUserDataClicked = { + coroutineScope.launch { + sheetState.hide() + state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + onUserDataClicked.invoke(it) + } + }, + ) + // FIXME remove after https://issuetracker.google.com/issues/275849044 + Spacer(modifier = Modifier.height(32.dp)) + } + } +} + +@Composable +private fun ColumnScope.ReadReceiptBottomSheetContent( + state: ReadReceiptBottomSheetState, + onUserDataClicked: (UserId) -> Unit, +) { + ListItem( + headlineContent = { + Text(text = stringResource(id = CommonStrings.common_seen_by)) + } + ) + val receipts = state.selectedEvent?.readReceiptState?.receipts.orEmpty() + receipts.forEach { + val userId = UserId(it.avatarData.id) + MatrixUserRow( + modifier = Modifier.clickable { onUserDataClicked(userId) }, + matrixUser = MatrixUser( + userId = userId, + displayName = it.avatarData.name, + avatarUrl = it.avatarData.url, + ), + avatarSize = AvatarSize.ReadReceiptList, + trailingContent = { + Text( + text = it.formattedDate, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + ) + } +} + +@PreviewsDayNight +@Composable +internal fun ReadReceiptBottomSheetPreview(@PreviewParameter(ReadReceiptBottomSheetStateProvider::class) state: ReadReceiptBottomSheetState) = ElementPreview { + // TODO restore RetrySendMessageMenuBottomSheet once the issue with bottom sheet not being previewable is fixed + Column { + ReadReceiptBottomSheetContent( + state = state, + onUserDataClicked = {}, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt new file mode 100644 index 0000000000..213a43277d --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import io.element.android.features.messages.impl.timeline.model.TimelineItem + +sealed interface ReadReceiptBottomSheetEvents { + data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvents + data object Dismiss : ReadReceiptBottomSheetEvents +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt new file mode 100644 index 0000000000..a4a55cbc9e --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class ReadReceiptBottomSheetPresenter @Inject constructor( +) : Presenter { + + @Composable + override fun present(): ReadReceiptBottomSheetState { + var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) } + + fun handleEvent(event: ReadReceiptBottomSheetEvents) { + @Suppress("LiftReturnOrAssignment") + when (event) { + is ReadReceiptBottomSheetEvents.EventSelected -> { + selectedEvent = event.event + } + ReadReceiptBottomSheetEvents.Dismiss -> { + selectedEvent = null + } + } + } + + return ReadReceiptBottomSheetState( + selectedEvent = selectedEvent, + eventSink = { handleEvent(it) }, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt new file mode 100644 index 0000000000..34db5488fa --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.runtime.Immutable +import io.element.android.features.messages.impl.timeline.model.TimelineItem + +@Immutable +data class ReadReceiptBottomSheetState( + val selectedEvent: TimelineItem.Event?, + val eventSink: (ReadReceiptBottomSheetEvents) -> Unit, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt new file mode 100644 index 0000000000..3a3bf1dbc4 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateProvider +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import kotlinx.collections.immutable.toImmutableList + +class ReadReceiptBottomSheetStateProvider : PreviewParameterProvider { + // Reuse the provider ReadReceiptViewStateProvider + private val readReceiptViewStateProvider = ReadReceiptViewStateProvider() + override val values: Sequence = readReceiptViewStateProvider.values + .filter { it.sendState is LocalEventSendState.Sent } + .map { readReceiptViewState -> + ReadReceiptBottomSheetState( + selectedEvent = aTimelineItemEvent( + readReceiptState = TimelineItemReadReceipts( + receipts = readReceiptViewState.receipts.map { readReceiptData -> + readReceiptData + .copy(avatarData = readReceiptData.avatarData.copy(id = "@${readReceiptData.avatarData.id}:localhost")) + }.toImmutableList() + ) + ), + eventSink = {}, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt new file mode 100644 index 0000000000..ac58b14e60 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.virtual + +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.features.messages.impl.R +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.theme.ElementTheme + +@Composable +internal fun TimelineItemReadMarkerView( + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 12.dp, horizontal = 18.dp), + horizontalAlignment = Alignment.End, + verticalArrangement = spacedBy(4.dp), + ) { + Text( + text = stringResource(id = R.string.room_timeline_read_marker_title).uppercase(), + style = ElementTheme.typography.fontBodySmMedium, + color = ElementTheme.colors.textSecondary, + ) + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 2.dp), + color = ElementTheme.colors.borderInteractivePrimary, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TimelineItemReadMarkerViewPreview() = ElementPreview { + TimelineItemReadMarkerView() +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt new file mode 100644 index 0000000000..f400c214a3 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.virtual + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import io.element.android.features.messages.impl.R +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.theme.ElementTheme + +@Composable +fun TimelineItemRoomBeginningView( + roomName: String?, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + contentAlignment = Alignment.Center, + ) { + val text = if (roomName == null) { + stringResource(id = R.string.room_timeline_beginning_of_room_no_name) + } else { + stringResource(id = R.string.room_timeline_beginning_of_room, roomName) + } + Text( + color = MaterialTheme.colorScheme.secondary, + style = ElementTheme.typography.fontBodyMdRegular, + text = text, + textAlign = TextAlign.Center, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TimelineItemRoomBeginningViewPreview() = ElementPreview { + Column { + TimelineItemRoomBeginningView( + roomName = null, + ) + TimelineItemRoomBeginningView( + roomName = "Room Name", + ) + } +} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt similarity index 66% rename from libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressState.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt index 50df6d591c..d93bee7817 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt @@ -14,18 +14,13 @@ * limitations under the License. */ -package io.element.android.libraries.textcomposer.utils +package io.element.android.features.messages.impl.timeline.di + +import androidx.compose.runtime.staticCompositionLocalOf /** - * State of a press gesture. + * Provides a [TimelineItemPresenterFactories] to the composition. */ -internal sealed interface PressState { - data class Idle( - val lastPress: Pressing? - ) : PressState - - sealed interface Pressing : PressState - data object Tapping : Pressing - data object LongPressing : Pressing +val LocalTimelineItemPresenterFactories = staticCompositionLocalOf { + TimelineItemPresenterFactories(emptyMap()) } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt index 0574f7e903..e8997f5a1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt @@ -18,13 +18,13 @@ package io.element.android.features.messages.impl.timeline.di import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.runtime.staticCompositionLocalOf import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.multibindings.Multibinds import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn import javax.inject.Inject /** @@ -40,38 +40,60 @@ interface TimelineItemPresenterFactoriesModule { } /** - * Wrapper around the [TimelineItemPresenterFactory] map multi binding. + * Room level caching layer for the [TimelineItemPresenterFactory] instances. * - * Its only purpose is to provide a nicer type name than: - * `@JvmSuppressWildcards Map, TimelineItemPresenterFactory<*, *>>`. - * - * A typealias would have been better but typealiases on Dagger types which use @JvmSuppressWildcards - * currently make Dagger crash. - * - * Request this type from Dagger to access the [TimelineItemPresenterFactory] map multibinding. + * It will cache the presenter instances in the room scope, so that they can be + * reused across recompositions of the timeline items that happen whenever an item + * goes out of the [LazyColumn] viewport. */ -data class TimelineItemPresenterFactories @Inject constructor( - val factories: @JvmSuppressWildcards Map, TimelineItemPresenterFactory<*, *>>, -) +@SingleIn(RoomScope::class) +class TimelineItemPresenterFactories @Inject constructor( + private val factories: @JvmSuppressWildcards Map, TimelineItemPresenterFactory<*, *>>, +) { + private val presenters: MutableMap> = mutableMapOf() -/** - * Provides a [TimelineItemPresenterFactories] to the composition. - */ -val LocalTimelineItemPresenterFactories = staticCompositionLocalOf { - TimelineItemPresenterFactories(emptyMap()) -} - -/** - * Creates and remembers a presenter for the given content. - * - * Will throw if the presenter is not found in the [TimelineItemPresenterFactory] map multi binding. - */ -@Composable -inline fun TimelineItemPresenterFactories.rememberPresenter( - content: C -): Presenter = remember(content) { - factories.getValue(C::class.java).let { - @Suppress("UNCHECKED_CAST") - (it as TimelineItemPresenterFactory).create(content) + /** + * Creates and caches a presenter for the given content. + * + * Will throw if the presenter is not found in the [TimelineItemPresenterFactory] map multi binding. + * + * @param C The [TimelineItemEventContent] subtype handled by this TimelineItem presenter. + * @param S The state type produced by this timeline item presenter. + * @param content The [TimelineItemEventContent] instance to create a presenter for. + * @param contentClass The class of [content]. + * @return An instance of a TimelineItem presenter that will be cached in the room scope. + */ + @Composable + fun rememberPresenter( + content: C, + contentClass: Class, + ): Presenter = remember(content) { + presenters[content]?.let { + @Suppress("UNCHECKED_CAST") + it as Presenter + } ?: factories.getValue(contentClass).let { + @Suppress("UNCHECKED_CAST") + (it as TimelineItemPresenterFactory).create(content).apply { + presenters[content] = this + } + } } } + +/** + * Creates and caches a presenter for the given content. + * + * Will throw if the presenter is not found in the [TimelineItemPresenterFactory] map multi binding. + * + * @param C The [TimelineItemEventContent] subtype handled by this TimelineItem presenter. + * @param S The state type produced by this timeline item presenter. + * @param content The [TimelineItemEventContent] instance to create a presenter for. + * @return An instance of a TimelineItem presenter that will be cached in the room scope. + */ +@Composable +inline fun TimelineItemPresenterFactories.rememberPresenter( + content: C +): Presenter = rememberPresenter( + content = content, + contentClass = C::class.java +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index 8c894bc99a..ffc1a1b3f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -66,19 +67,23 @@ class TimelineItemsFactory @Inject constructor( suspend fun replaceWith( timelineItems: List, + roomMembers: List, ) = withContext(dispatchers.computation) { lock.withLock { diffCacheUpdater.updateWith(timelineItems) - buildAndEmitTimelineItemStates(timelineItems) + buildAndEmitTimelineItemStates(timelineItems, roomMembers) } } - private suspend fun buildAndEmitTimelineItemStates(timelineItems: List) { + private suspend fun buildAndEmitTimelineItemStates( + timelineItems: List, + roomMembers: List, + ) { val newTimelineItemStates = ArrayList() for (index in diffCache.indices().reversed()) { val cacheItem = diffCache.get(index) if (cacheItem == null) { - buildAndCacheItem(timelineItems, index)?.also { timelineItemState -> + buildAndCacheItem(timelineItems, index, roomMembers)?.also { timelineItemState -> newTimelineItemStates.add(timelineItemState) } } else { @@ -91,11 +96,12 @@ class TimelineItemsFactory @Inject constructor( private suspend fun buildAndCacheItem( timelineItems: List, - index: Int + index: Int, + roomMembers: List, ): TimelineItem? { val timelineItemState = when (val currentTimelineItem = timelineItems[index]) { - is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems) + is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems, roomMembers) is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem) MatrixTimelineItem.Other -> null } 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 d15a07526c..bf4ba27bd2 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 @@ -61,7 +61,7 @@ class TimelineItemContentMessageFactory @Inject constructor( return when (val messageType = content.type) { is EmoteMessageType -> TimelineItemEmoteContent( body = "* $senderDisplayName ${messageType.body}", - htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* senderDisplayName"), + htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"), isEdited = content.isEdited, ) is ImageMessageType -> { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 14f8429c85..4c9217a884 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -19,13 +19,17 @@ package io.element.android.features.messages.impl.timeline.factories.event import io.element.android.features.messages.impl.timeline.groups.canBeDisplayedInBubbleBlock import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReactionSender +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.libraries.core.bool.orTrue +import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import kotlinx.collections.immutable.toImmutableList @@ -36,12 +40,14 @@ import javax.inject.Inject class TimelineItemEventFactory @Inject constructor( private val contentFactory: TimelineItemContentFactory, private val matrixClient: MatrixClient, + private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, ) { suspend fun create( currentTimelineItem: MatrixTimelineItem.Event, index: Int, timelineItems: List, + roomMembers: List, ): TimelineItem.Event { val currentSender = currentTimelineItem.event.sender val groupPosition = @@ -84,6 +90,7 @@ class TimelineItemEventFactory @Inject constructor( sentTime = sentTime, groupPosition = groupPosition, reactionsState = currentTimelineItem.computeReactionsState(), + readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers), localSendState = currentTimelineItem.event.localSendState, inReplyTo = currentTimelineItem.event.inReplyTo(), isThreaded = currentTimelineItem.event.isThreaded(), @@ -102,7 +109,7 @@ class TimelineItemEventFactory @Inject constructor( key = reaction.key, currentUserId = matrixClient.sessionId, senders = reaction.senders - .sortedByDescending{ it.timestamp } + .sortedByDescending { it.timestamp } .map { val date = Date(it.timestamp) AggregatedReactionSender( @@ -124,6 +131,27 @@ class TimelineItemEventFactory @Inject constructor( return TimelineItemReactions(aggregatedReactions.toImmutableList()) } + private fun MatrixTimelineItem.Event.computeReadReceiptState( + roomMembers: List, + ): TimelineItemReadReceipts { + return TimelineItemReadReceipts( + receipts = event.receipts + .map { receipt -> + val roomMember = roomMembers.find { it.userId == receipt.userId } + ReadReceiptData( + avatarData = AvatarData( + id = receipt.userId.value, + name = roomMember?.displayName, + url = roomMember?.avatarUrl, + size = AvatarSize.TimelineReadReceipt, + ), + formattedDate = lastMessageTimestampFormatter.format(receipt.timestamp) + ) + } + .toImmutableList() + ) + } + private fun computeGroupPosition( currentTimelineItem: MatrixTimelineItem.Event, timelineItems: List, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index bd3090e390..5ceaba5550 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -65,6 +65,7 @@ sealed interface TimelineItem { val isMine: Boolean = false, val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, val reactionsState: TimelineItemReactions, + val readReceiptState: TimelineItemReadReceipts, val localSendState: LocalEventSendState?, val inReplyTo: InReplyTo?, val isThreaded: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt new file mode 100644 index 0000000000..dc5ae8289c --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.model + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import kotlinx.collections.immutable.ImmutableList + +data class TimelineItemReadReceipts( + val receipts: ImmutableList, +) + +data class ReadReceiptData( + val avatarData: AvatarData, + val formattedDate: String, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt index f80ee15d95..0c83e834b0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt @@ -17,12 +17,12 @@ package io.element.android.features.messages.impl.voicemessages.composer import androidx.lifecycle.Lifecycle -import io.element.android.libraries.textcomposer.model.PressEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent sealed interface VoiceMessageComposerEvents { - data class RecordButtonEvent( - val pressEvent: PressEvent + data class RecorderEvent( + val recorderEvent: VoiceMessageRecorderEvent ): VoiceMessageComposerEvents data class PlayerEvent( val playerEvent: VoiceMessagePlayerEvent, @@ -32,4 +32,5 @@ sealed interface VoiceMessageComposerEvents { data object AcceptPermissionRationale: VoiceMessageComposerEvents data object DismissPermissionsRationale: VoiceMessageComposerEvents data class LifecycleEvent(val event: Lifecycle.Event): VoiceMessageComposerEvents + data object DismissSendFailureDialog: VoiceMessageComposerEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt index 545a604af2..584ec96f2a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt @@ -17,96 +17,233 @@ package io.element.android.features.messages.impl.voicemessages.composer import io.element.android.libraries.mediaplayer.api.MediaPlayer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject /** * A media player for the voice message composer. * * @param mediaPlayer The [MediaPlayer] to use. + * @param coroutineScope */ class VoiceMessageComposerPlayer @Inject constructor( private val mediaPlayer: MediaPlayer, + private val coroutineScope: CoroutineScope, ) { - private var lastPlayedMediaPath: String? = null - private val curPlayingMediaId - get() = mediaPlayer.state.value.mediaId + companion object { + const val MIME_TYPE = "audio/ogg" + } - val state: Flow = mediaPlayer.state.map { state -> - if (lastPlayedMediaPath == null || lastPlayedMediaPath != state.mediaId) { - return@map State.NotLoaded + private var mediaPath: String? = null + + private var seekJob: Job? = null + private val seekingTo = MutableStateFlow(null) + + val state: Flow = combine(mediaPlayer.state, seekingTo) { state, seekingTo -> + state to seekingTo + }.scan(InternalState.NotLoaded) { prevState, (state, seekingTo) -> + if (mediaPath == null || mediaPath != state.mediaId) { + return@scan InternalState.NotLoaded } - State( - isPlaying = state.isPlaying, + InternalState( + playState = calcPlayState(prevState.playState, seekingTo, state), currentPosition = state.currentPosition, - duration = state.duration ?: 0L, + duration = state.duration, + seekingTo = seekingTo, + ) + }.map { + State( + playState = it.playState, + currentPosition = it.currentPosition, + progress = calcProgress(it), ) }.distinctUntilChanged() + /** + * Set the voice message to be played. + */ + suspend fun setMedia(mediaPath: String) { + this.mediaPath = mediaPath + mediaPlayer.setMedia( + uri = mediaPath, + mediaId = mediaPath, + mimeType = MIME_TYPE, + ) + } + /** * Start playing from the current position. * - * @param mediaPath The path to the media to be played. - * @param mimeType The mime type of the media file. + * Call [setMedia] before calling this method. */ - suspend fun play(mediaPath: String, mimeType: String) { - if (mediaPath == curPlayingMediaId) { - mediaPlayer.play() - } else { - lastPlayedMediaPath = mediaPath - mediaPlayer.setMedia( - uri = mediaPath, - mediaId = mediaPath, - mimeType = mimeType, - ) - mediaPlayer.play() + suspend fun play() { + val mediaPath = this.mediaPath + if (mediaPath == null) { + Timber.e("Set media before playing") + return } + + mediaPlayer.ensureMediaReady(mediaPath) + + mediaPlayer.play() } /** * Pause playback. */ fun pause() { - if (lastPlayedMediaPath == curPlayingMediaId) { + if (mediaPath == mediaPlayer.state.value.mediaId) { mediaPlayer.pause() } } + /** + * Seek to a given position in the current media. + * + * Call [setMedia] before calling this method. + * + * @param position The position to seek to between 0 and 1. + */ + suspend fun seek(position: Float) { + val mediaPath = this.mediaPath + if (mediaPath == null) { + Timber.e("Set media before seeking") + return + } + + seekJob?.cancelAndJoin() + seekingTo.value = position + seekJob = coroutineScope.launch { + val mediaState = mediaPlayer.ensureMediaReady(mediaPath) + val duration = mediaState.duration ?: return@launch + val positionMs = (duration * position).toLong() + mediaPlayer.seekTo(positionMs) + }.apply { + invokeOnCompletion { + seekingTo.value = null + } + } + } + + private suspend fun MediaPlayer.ensureMediaReady(mediaPath: String): MediaPlayer.State { + val state = state.value + if (state.mediaId == mediaPath && state.isReady) { + return state + } + + return setMedia( + uri = mediaPath, + mediaId = mediaPath, + mimeType = MIME_TYPE, + ) + } + + private fun calcPlayState(prevPlayState: PlayState, seekingTo: Float?, state: MediaPlayer.State): PlayState { + if (state.mediaId == null || state.mediaId != mediaPath) { + return PlayState.Stopped + } + + // If we were stopped and the player didn't start playing or seeking, we are still stopped. + if (prevPlayState == PlayState.Stopped && !state.isPlaying && seekingTo == null) { + return PlayState.Stopped + } + + return if (state.isPlaying) { + PlayState.Playing + } else { + PlayState.Paused + } + } + + private fun calcProgress(state: InternalState): Float { + if (state.seekingTo != null) { + return state.seekingTo + } + + if (state.playState == PlayState.Stopped) { + return 0f + } + + if (state.duration == null) { + return 0f + } + + return (state.currentPosition.toFloat() / state.duration.toFloat()) + .coerceAtMost(1f) // Current position may exceed reported duration + } + + /** + * @property playState Whether this player is currently playing. See [PlayState]. + * @property currentPosition The elapsed time of this player in milliseconds. + * @property progress The progress of this player between 0 and 1. + */ data class State( + val playState: PlayState, + val currentPosition: Long, + val progress: Float, + ) { + + companion object { + val Initial = State( + playState = PlayState.Stopped, + currentPosition = 0L, + progress = 0f, + ) + } /** * Whether this player is currently playing. */ - val isPlaying: Boolean, + val isPlaying get() = this.playState == PlayState.Playing + /** - * The elapsed time of this player in milliseconds. + * Whether this player is currently stopped. */ + val isStopped get() = this.playState == PlayState.Stopped + } + + + enum class PlayState { + /** + * The player is stopped, i.e. it has just been initialised. + */ + Stopped, + + /** + * The player is playing. + */ + Playing, + + /** + * The player has been paused. The player can also enter the paused state after seeking to a position. + */ + Paused, + } + + private data class InternalState( + val playState: PlayState, val currentPosition: Long, - /** - * The duration of this player in milliseconds. - */ - val duration: Long, + val duration: Long?, + val seekingTo: Float?, ) { companion object { - val NotLoaded = State( - isPlaying = false, + val NotLoaded = InternalState( + playState = PlayState.Stopped, currentPosition = 0L, - duration = 0L, + duration = null, + seekingTo = null, ) } - - val isLoaded get() = this != NotLoaded - - /** - * The progress of this player between 0 and 1. - */ - val progress: Float = - if (duration == 0L) - 0f - else - (currentPosition.toFloat() / duration.toFloat()) - .coerceAtMost(1f) // Current position may exceed reported duration } } + diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt index bb642226a9..528711b71e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.voicemessages.composer import android.Manifest import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -36,7 +37,7 @@ import io.element.android.libraries.di.SingleIn import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsPresenter -import io.element.android.libraries.textcomposer.model.PressEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.voicerecorder.api.VoiceRecorder @@ -69,13 +70,18 @@ class VoiceMessageComposerPresenter @Inject constructor( override fun present(): VoiceMessageComposerState { val localCoroutineScope = rememberCoroutineScope() val recorderState by voiceRecorder.state.collectAsState(initial = VoiceRecorderState.Idle) + val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.Initial) val keepScreenOn by remember { derivedStateOf { recorderState is VoiceRecorderState.Recording } } val permissionState = permissionsPresenter.present() var isSending by remember { mutableStateOf(false) } - val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.NotLoaded) - val playerTime by remember(playerState, recorderState) { derivedStateOf { displayTime(playerState, recorderState) } } - val waveform by remember(recorderState) { derivedStateOf { recorderState.finishedWaveform() } } + var showSendFailureDialog by remember { mutableStateOf(false) } + + LaunchedEffect(recorderState) { + val recording = recorderState as? VoiceRecorderState.Finished + ?: return@LaunchedEffect + player.setMedia(recording.file.path) + } val onLifecycleEvent = { event: Lifecycle.Event -> when (event) { @@ -90,10 +96,10 @@ class VoiceMessageComposerPresenter @Inject constructor( } } - val onRecordButtonPress = { event: VoiceMessageComposerEvents.RecordButtonEvent -> + val onVoiceMessageRecorderEvent = { event: VoiceMessageComposerEvents.RecorderEvent -> val permissionGranted = permissionState.permissionGranted - when (event.pressEvent) { - PressEvent.PressStart -> { + when (event.recorderEvent) { + VoiceMessageRecorderEvent.Start -> { Timber.v("Voice message record button pressed") when { permissionGranted -> { @@ -105,37 +111,25 @@ class VoiceMessageComposerPresenter @Inject constructor( } } } - PressEvent.LongPressEnd -> { - Timber.v("Voice message record button released") + VoiceMessageRecorderEvent.Stop -> { + Timber.v("Voice message stop button pressed") localCoroutineScope.finishRecording() } - PressEvent.Tapped -> { - Timber.v("Voice message record button tapped") + VoiceMessageRecorderEvent.Cancel -> { + Timber.v("Voice message cancel button tapped") localCoroutineScope.cancelRecording() } } } - val onPlayerEvent = { event: VoiceMessagePlayerEvent -> - when (event) { - VoiceMessagePlayerEvent.Play -> - when (val recording = recorderState) { - is VoiceRecorderState.Finished -> - localCoroutineScope.launch { - player.play( - mediaPath = recording.file.path, - mimeType = recording.mimeType, - ) - } - else -> Timber.e("Voice message player event received but no file to play") - } - VoiceMessagePlayerEvent.Pause -> { - player.pause() - } - is VoiceMessagePlayerEvent.Seek -> { - // TODO implement seeking + val onPlayerEvent = { event: VoiceMessagePlayerEvent -> localCoroutineScope.launch { + localCoroutineScope.launch { + when (event) { + VoiceMessagePlayerEvent.Play -> player.play() + VoiceMessagePlayerEvent.Pause -> player.pause() + is VoiceMessagePlayerEvent.Seek -> player.seek(event.position) } } - } + } } val onAcceptPermissionsRationale = { permissionState.eventSink(PermissionsEvents.OpenSystemSettingAndCloseDialog) @@ -145,6 +139,10 @@ class VoiceMessageComposerPresenter @Inject constructor( permissionState.eventSink(PermissionsEvents.CloseDialog) } + val onDismissSendFailureDialog = { + showSendFailureDialog = false + } + val onSendButtonPress = lambda@{ val finishedState = recorderState as? VoiceRecorderState.Finished if (finishedState == null) { @@ -159,18 +157,23 @@ class VoiceMessageComposerPresenter @Inject constructor( isSending = true player.pause() analyticsService.captureComposerEvent() - appCoroutineScope.sendMessage( - file = finishedState.file, - mimeType = finishedState.mimeType, - waveform = finishedState.waveform, - ).invokeOnCompletion { + appCoroutineScope.launch { + val result = sendMessage( + file = finishedState.file, + mimeType = finishedState.mimeType, + waveform = finishedState.waveform, + ) + if (result.isFailure) { + showSendFailureDialog = true + } + }.invokeOnCompletion { isSending = false } } val handleEvents: (VoiceMessageComposerEvents) -> Unit = { event -> when (event) { - is VoiceMessageComposerEvents.RecordButtonEvent -> onRecordButtonPress(event) + is VoiceMessageComposerEvents.RecorderEvent -> onVoiceMessageRecorderEvent(event) is VoiceMessageComposerEvents.PlayerEvent -> onPlayerEvent(event.playerEvent) is VoiceMessageComposerEvents.SendVoiceMessage -> localCoroutineScope.launch { onSendButtonPress() @@ -182,6 +185,7 @@ class VoiceMessageComposerPresenter @Inject constructor( VoiceMessageComposerEvents.DismissPermissionsRationale -> onDismissPermissionsRationale() VoiceMessageComposerEvents.AcceptPermissionRationale -> onAcceptPermissionsRationale() is VoiceMessageComposerEvents.LifecycleEvent -> onLifecycleEvent(event.event) + VoiceMessageComposerEvents.DismissSendFailureDialog -> onDismissSendFailureDialog() } } @@ -189,24 +193,43 @@ class VoiceMessageComposerPresenter @Inject constructor( voiceMessageState = when (val state = recorderState) { is VoiceRecorderState.Recording -> VoiceMessageState.Recording( duration = state.elapsedTime, - levels = state.levels.toPersistentList() - ) - is VoiceRecorderState.Finished -> VoiceMessageState.Preview( - isSending = isSending, - isPlaying = playerState.isPlaying, - showCursor = playerState.isLoaded && !isSending, - playbackProgress = playerState.progress, - time = playerTime, - waveform = waveform, + levels = state.levels.toPersistentList(), ) + is VoiceRecorderState.Finished -> + previewState( + playerState = playerState, + recorderState = recorderState, + isSending = isSending + ) else -> VoiceMessageState.Idle }, showPermissionRationaleDialog = permissionState.showDialog, + showSendFailureDialog = showSendFailureDialog, keepScreenOn = keepScreenOn, eventSink = handleEvents, ) } + @Composable + private fun previewState( + playerState: VoiceMessageComposerPlayer.State, + recorderState: VoiceRecorderState, + isSending: Boolean, + ): VoiceMessageState { + val showCursor by remember(playerState.isStopped, isSending) { derivedStateOf { !playerState.isStopped && !isSending }} + val playerTime by remember(playerState, recorderState) { derivedStateOf { displayTime(playerState, recorderState) } } + val waveform by remember(recorderState) { derivedStateOf { recorderState.finishedWaveform() } } + + return VoiceMessageState.Preview( + isSending = isSending, + isPlaying = playerState.isPlaying, + showCursor = showCursor, + playbackProgress = playerState.progress, + time = playerTime, + waveform = waveform, + ) + } + private fun CoroutineScope.startRecording() = launch { try { voiceRecorder.startRecord() @@ -228,11 +251,11 @@ class VoiceMessageComposerPresenter @Inject constructor( voiceRecorder.deleteRecording() } - private fun CoroutineScope.sendMessage( + private suspend fun sendMessage( file: File, mimeType: String, - waveform: List - ) = launch { + waveform: List, + ): Result { val result = mediaSender.sendVoiceMessage( uri = file.toUri(), mimeType = mimeType, @@ -241,14 +264,16 @@ class VoiceMessageComposerPresenter @Inject constructor( if (result.isFailure) { Timber.e(result.exceptionOrNull(), "Voice message error") - return@launch + return result } voiceRecorder.deleteRecording() + + return result } private fun AnalyticsService.captureComposerEvent() = - analyticsService.capture( + capture( Composer( inThread = messageComposerContext.composerMode.inThread, isEditing = messageComposerContext.composerMode.isEditing, @@ -273,7 +298,7 @@ private fun displayTime( playerState: VoiceMessageComposerPlayer.State, recording: VoiceRecorderState ): Duration = when { - playerState.isLoaded -> + !playerState.isStopped -> playerState.currentPosition.milliseconds recording is VoiceRecorderState.Finished -> recording.duration diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt index 055fa28177..aaab388ec1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState data class VoiceMessageComposerState( val voiceMessageState: VoiceMessageState, val showPermissionRationaleDialog: Boolean, + val showSendFailureDialog: Boolean, val keepScreenOn: Boolean, val eventSink: (VoiceMessageComposerEvents) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt index f9856005bb..0e884f5491 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.messages.impl.voicemessages.composer import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.designsystem.components.media.createFakeWaveform import io.element.android.libraries.textcomposer.model.VoiceMessageState import kotlinx.collections.immutable.toPersistentList import kotlin.time.Duration.Companion.seconds @@ -32,13 +33,24 @@ internal fun aVoiceMessageComposerState( voiceMessageState: VoiceMessageState = VoiceMessageState.Idle, keepScreenOn: Boolean = false, showPermissionRationaleDialog: Boolean = false, + showSendFailureDialog: Boolean = false, ) = VoiceMessageComposerState( voiceMessageState = voiceMessageState, showPermissionRationaleDialog = showPermissionRationaleDialog, + showSendFailureDialog = showSendFailureDialog, keepScreenOn = keepScreenOn, eventSink = {}, ) +internal fun aVoiceMessagePreviewState() = VoiceMessageState.Preview( + isSending = false, + isPlaying = false, + showCursor = false, + playbackProgress = 0f, + time = 10.seconds, + waveform = createFakeWaveform(), +) + internal var aWaveformLevels = List(100) { it.toFloat() / 100 }.toPersistentList() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt new file mode 100644 index 0000000000..a828e19f30 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.voicemessages.composer + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +internal fun VoiceMessageSendingFailedDialog( + onDismiss: () -> Unit, +) { + ErrorDialog( + title = stringResource(CommonStrings.common_error), + content = stringResource(CommonStrings.error_failed_uploading_voice_message), + onDismiss = onDismiss, + submitText = stringResource(CommonStrings.action_ok), + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt new file mode 100644 index 0000000000..fc9e98f0ed --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.voicemessages.timeline + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent +import io.element.android.libraries.mediaplayer.api.MediaPlayer +import kotlinx.coroutines.withContext +import javax.inject.Inject + +interface RedactedVoiceMessageManager { + suspend fun onEachMatrixTimelineItem(timelineItems: List) +} + +@ContributesBinding(RoomScope::class) +class DefaultRedactedVoiceMessageManager @Inject constructor( + private val dispatchers: CoroutineDispatchers, + private val mediaPlayer: MediaPlayer, +) : RedactedVoiceMessageManager { + override suspend fun onEachMatrixTimelineItem(timelineItems: List) { + withContext(dispatchers.computation) { + mediaPlayer.state.value.let { playerState -> + if (playerState.isPlaying && playerState.mediaId != null) { + val needsToPausePlayer = timelineItems.any { + it is MatrixTimelineItem.Event && + playerState.mediaId == it.eventId?.value && + it.event.content is RedactedContent + } + if (needsToPausePlayer) { + withContext(dispatchers.main) { mediaPlayer.pause() } + } + } + } + } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt index a36eccc1d2..cc8bd1945f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt @@ -89,7 +89,6 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor( source = mediaSource, mimeType = mimeType, body = body, - useCache = false, ).mapCatching { it.use { mediaFile -> val dest = cachedFile.apply { parentFile?.mkdirs() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt index 67297fc3b3..a6f002c38a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt @@ -17,13 +17,16 @@ package io.element.android.features.messages.impl.voicemessages.timeline import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaplayer.api.MediaPlayer import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import java.io.File import javax.inject.Inject /** @@ -60,14 +63,18 @@ interface VoiceMessagePlayer { val state: Flow /** - * Starts playing from the beginning - * acquiring control of the underlying [MediaPlayer]. - * If already in control of the underlying [MediaPlayer], starts playing from the - * current position. + * Acquires control of the underlying [MediaPlayer] and prepares it + * to play the media file. * - * Will suspend whilst the media file is being downloaded. + * Will suspend whilst the media file is being downloaded and/or + * the underlying [MediaPlayer] is loading the media file. */ - suspend fun play(): Result + suspend fun prepare(): Result + + /** + * Play the media. + */ + fun play() /** * Pause playback. @@ -82,18 +89,26 @@ interface VoiceMessagePlayer { fun seekTo(positionMs: Long) data class State( + /** + * Whether the player is ready to play. + */ + val isReady: Boolean, /** * Whether this player is currently playing. */ val isPlaying: Boolean, /** - * Whether this player has control of the underlying [MediaPlayer]. + * Whether the player has reached the end of the media. */ - val isMyMedia: Boolean, + val isEnded: Boolean, /** * The elapsed time of this player in milliseconds. */ val currentPosition: Long, + /** + * The duration of the current content, if available. + */ + val duration: Long?, ) } @@ -137,49 +152,84 @@ class DefaultVoiceMessagePlayer( body = body ) - override val state: Flow = mediaPlayer.state.map { state -> + private var internalState = MutableStateFlow( VoiceMessagePlayer.State( - isPlaying = state.mediaId.isMyTrack() && state.isPlaying, - isMyMedia = state.mediaId.isMyTrack(), - currentPosition = if (state.mediaId.isMyTrack()) state.currentPosition else 0L + isReady = false, + isPlaying = false, + isEnded = false, + currentPosition = 0L, + duration = null + ) + ) + + override val state: Flow = combine(mediaPlayer.state, internalState) { mediaPlayerState, internalState -> + if (mediaPlayerState.isMyTrack) { + this.internalState.update { + it.copy( + isReady = mediaPlayerState.isReady, + isPlaying = mediaPlayerState.isPlaying, + isEnded = mediaPlayerState.isEnded, + currentPosition = mediaPlayerState.currentPosition, + duration = mediaPlayerState.duration, + ) + } + } else { + this.internalState.update { + it.copy( + isReady = false, + isPlaying = false, + ) + } + } + VoiceMessagePlayer.State( + isReady = internalState.isReady, + isPlaying = internalState.isPlaying, + isEnded = internalState.isEnded, + currentPosition = internalState.currentPosition, + duration = internalState.duration, ) }.distinctUntilChanged() - override suspend fun play(): Result = if (inControl()) { - mediaPlayer.play() - Result.success(Unit) + override suspend fun prepare(): Result = if (eventId != null) { + repo.getMediaFile().mapCatching { mediaFile -> + val state = internalState.value + mediaPlayer.setMedia( + uri = mediaFile.path, + mediaId = eventId.value, + mimeType = "audio/ogg", // Files in the voice cache have no extension so we need to set the mime type manually. + startPositionMs = if (state.isEnded) 0L else state.currentPosition, + ) + } } else { - if (eventId != null) { - repo.getMediaFile().mapCatching { mediaFile -> - mediaPlayer.setMedia( - uri = mediaFile.path, - mediaId = eventId.value, - mimeType = "audio/ogg" // Files in the voice cache have no extension so we need to set the mime type manually. - ) - mediaPlayer.play() - } - } else { - Result.failure(IllegalStateException("Cannot play a voice message with no eventId")) + Result.failure(IllegalStateException("Cannot acquireControl on a voice message with no eventId")) + } + + override fun play() { + if (inControl()) { + mediaPlayer.play() } } override fun pause() { - ifInControl { + if (inControl()) { mediaPlayer.pause() } } override fun seekTo(positionMs: Long) { - ifInControl { + if (inControl()) { mediaPlayer.seekTo(positionMs) + } else { + internalState.update { + it.copy(currentPosition = positionMs) + } } } - private fun String?.isMyTrack(): Boolean = if (eventId == null) false else this == eventId.value + private val MediaPlayer.State.isMyTrack: Boolean + get() = if (eventId == null) false else this.mediaId == eventId.value - private inline fun ifInControl(block: () -> Unit) { - if (inControl()) block() + private fun inControl(): Boolean = mediaPlayer.state.value.let { + it.isMyTrack && (it.isReady || it.isEnded) } - - private fun inControl(): Boolean = mediaPlayer.state.value.mediaId.isMyTrack() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt index 1bbdedc22d..aebed9dd57 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt @@ -22,7 +22,6 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import com.squareup.anvil.annotations.ContributesTo import dagger.Binds import dagger.Module @@ -40,6 +39,7 @@ import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.di.RoomScope import io.element.android.libraries.ui.utils.time.formatShort import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.milliseconds @@ -55,6 +55,7 @@ interface VoiceMessagePresenterModule { class VoiceMessagePresenter @AssistedInject constructor( voiceMessagePlayerFactory: VoiceMessagePlayer.Factory, private val analyticsService: AnalyticsService, + private val scope: CoroutineScope, @Assisted private val content: TimelineItemVoiceContent, ) : Presenter { @@ -70,13 +71,20 @@ class VoiceMessagePresenter @AssistedInject constructor( body = content.body, ) + private val play = mutableStateOf>(Async.Uninitialized) + @Composable override fun present(): VoiceMessageState { - val scope = rememberCoroutineScope() - - val playerState by player.state.collectAsState(VoiceMessagePlayer.State(isPlaying = false, isMyMedia = false, currentPosition = 0L)) - val play = remember { mutableStateOf>(Async.Uninitialized) } + val playerState by player.state.collectAsState( + VoiceMessagePlayer.State( + isReady = false, + isPlaying = false, + isEnded = false, + currentPosition = 0L, + duration = null + ) + ) val button by remember { derivedStateOf { @@ -89,13 +97,26 @@ class VoiceMessagePresenter @AssistedInject constructor( } } } + val duration by remember { + derivedStateOf { playerState.duration ?: content.duration.toMillis() } + } val progress by remember { - derivedStateOf { if (playerState.isMyMedia) playerState.currentPosition / content.duration.toMillis().toFloat() else 0f } + derivedStateOf { + playerState.currentPosition / duration.toFloat() + } } val time by remember { derivedStateOf { - val time = if (playerState.isMyMedia) playerState.currentPosition else content.duration.toMillis() - time.milliseconds.formatShort() + when { + playerState.isReady && !playerState.isEnded -> playerState.currentPosition + playerState.currentPosition > 0 -> playerState.currentPosition + else -> duration + }.milliseconds.formatShort() + } + } + val showCursor by remember { + derivedStateOf { + !play.value.isUninitialized() && !playerState.isEnded } } @@ -104,6 +125,8 @@ class VoiceMessagePresenter @AssistedInject constructor( is VoiceMessageEvents.PlayPause -> { if (playerState.isPlaying) { player.pause() + } else if (playerState.isReady) { + player.play() } else { scope.launch { play.runUpdatingState( @@ -114,13 +137,15 @@ class VoiceMessagePresenter @AssistedInject constructor( it }, ) { - player.play() + player.prepare().apply { + player.play() + } } } } } is VoiceMessageEvents.Seek -> { - player.seekTo((event.percentage * content.duration.toMillis()).toLong()) + player.seekTo((event.percentage * duration).toLong()) } } } @@ -129,6 +154,7 @@ class VoiceMessagePresenter @AssistedInject constructor( button = button, progress = progress, time = time, + showCursor = showCursor, eventSink = { eventSink(it) }, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt index 093d5336fd..8940374cc7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageState.kt @@ -20,6 +20,7 @@ data class VoiceMessageState( val button: Button, val progress: Float, val time: String, + val showCursor: Boolean, val eventSink: (event: VoiceMessageEvents) -> Unit, ) { enum class Button { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt index 83d2f1141f..188ff6656d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageStateProvider.kt @@ -35,11 +35,13 @@ open class VoiceMessageStateProvider : PreviewParameterProvider + "Aktivity" + "Vlajky" + "Jídlo a nápoje" + "Zvířata a příroda" + "Předměty" + "Smajlíci a lidé" + "Cestování a místa" + "Symboly" "%1$d změna místnosti" "%1$d změny místnosti" "%1$d změn místnosti" + "Tato zpráva bude nahlášena správci vašeho domovského serveru. Nebude si moci přečíst žádné šifrované zprávy." + "Důvod nahlášení tohoto obsahu" + "Toto je začátek %1$s." + "Toto je začátek této konverzace." + "Nové" "Informujte celou místnost" + "Zaškrtněte, pokud chcete skrýt všechny aktuální a budoucí zprávy od tohoto uživatele" "Fotoaparát" "Vyfotit" "Natočit video" @@ -41,6 +55,7 @@ "Zobrazit méně" "Držte pro nahrávání" "Všichni" + "Zablokovat uživatele" "Nahrání média se nezdařilo, zkuste to prosím znovu." "Pouze zmínky a klíčová slova" diff --git a/features/messages/impl/src/main/res/values-de/translations.xml b/features/messages/impl/src/main/res/values-de/translations.xml index 161868614c..16580c21c5 100644 --- a/features/messages/impl/src/main/res/values-de/translations.xml +++ b/features/messages/impl/src/main/res/values-de/translations.xml @@ -1,9 +1,23 @@ + "Aktivitäten" + "Flaggen" + "Essen & Trinken" + "Tiere & Natur" + "Objekte" + "Smileys & Menschen" + "Reisen & Orte" + "Symbole" "%1$d Raumänderung" "%1$d Raumänderungen" + "Diese Meldung wird an den Administrator deines Homeservers weitergeleitet. Dieser kann keine verschlüsselten Nachrichten lesen." + "Grund für die Meldung dieses Inhalts" + "Dies ist der Anfang von %1$s." + "Dies ist der Anfang dieses Gesprächs." + "Neu" + "Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest" "Kamera" "Foto machen" "Video aufnehmen" @@ -36,6 +50,7 @@ "Deine Nachricht konnte nicht gesendet werden" "Emoji hinzufügen" "Weniger anzeigen" + "Benutzer sperren" "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Nur Erwähnungen und Schlüsselwörter" diff --git a/features/messages/impl/src/main/res/values-es/translations.xml b/features/messages/impl/src/main/res/values-es/translations.xml index fe186df358..493fdb7ad7 100644 --- a/features/messages/impl/src/main/res/values-es/translations.xml +++ b/features/messages/impl/src/main/res/values-es/translations.xml @@ -1,7 +1,22 @@ + "Actividades" + "Banderas" + "Comida y bebida" + "Animales y naturaleza" + "Objetos" + "Emojis y personas" + "Viajes y lugares" + "Símbolos" "%1$d cambio en la sala" "%1$d cambios en la sala" + "Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado." + "Motivo para denunciar este contenido" + "Este es el principio de %1$s." + "Este es el principio de esta conversación." + "Nuevos" + "Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario" + "Bloquear usuario" diff --git a/features/messages/impl/src/main/res/values-fr/translations.xml b/features/messages/impl/src/main/res/values-fr/translations.xml index f68a29ce16..2c9ab7574c 100644 --- a/features/messages/impl/src/main/res/values-fr/translations.xml +++ b/features/messages/impl/src/main/res/values-fr/translations.xml @@ -1,10 +1,24 @@ + "Activités" + "Drapeaux" + "Nourriture et boissons" + "Animaux et nature" + "Objets" + "Émoticônes et personnes" + "Voyages & lieux" + "Symboles" "%1$d changement dans le salon" "%1$d changements dans le salon" + "Ce message sera signalé à l’administrateur de votre serveur d’accueil. Il ne pourra lire aucun message chiffré." + "Raison du signalement de ce contenu" + "Ceci est le début de %1$s." + "Ceci est le début de cette conversation." + "Nouveau" "Notifier tout le salon" + "Cochez si vous souhaitez masquer tous les messages actuels et futurs de cet utilisateur." "Appareil photo" "Prendre une photo" "Enregistrer une vidéo" @@ -40,6 +54,7 @@ "Afficher moins" "Maintenir pour enregistrer" "Tout le monde" + "Bloquer l’utilisateur" "Échec du traitement des médias à télécharger, veuillez réessayer." "Mentions et mots clés uniquement" diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml index 1b0a2c99a3..c36e8c515c 100644 --- a/features/messages/impl/src/main/res/values-it/translations.xml +++ b/features/messages/impl/src/main/res/values-it/translations.xml @@ -1,7 +1,22 @@ + "Attività" + "Bandiere" + "Cibi & Bevande" + "Animali & Natura" + "Oggetti" + "Faccine & Persone" + "Viaggi & Luoghi" + "Simboli" "%1$d modifica alla stanza" "%1$d modifiche alla stanza" + "Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati." + "Motivo della segnalazione di questo contenuto" + "Questo è l\'inizio di %1$s." + "Questo è l\'inizio della conversazione." + "Nuovo" + "Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente" + "Blocca utente" diff --git a/features/messages/impl/src/main/res/values-ro/translations.xml b/features/messages/impl/src/main/res/values-ro/translations.xml index 16e4867196..c15a465976 100644 --- a/features/messages/impl/src/main/res/values-ro/translations.xml +++ b/features/messages/impl/src/main/res/values-ro/translations.xml @@ -1,10 +1,24 @@ + "Activități" + "Steaguri" + "Mâncare & Băutură" + "Animale și Natură" + "Obiecte" + "Fețe zâmbitoare & Oameni" + "Călătorii & Locuri" + "Simboluri" "%1$d schimbare a camerii" "%1$d schimbări ale camerei" "%1$d schimbări ale camerei" + "Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat." + "Motivul raportării acestui conținut" + "Acesta este începutul conversației %1$s." + "Acesta este începutul acestei conversații." + "Nou" + "Confirmați că doriți să ascundeți toate mesajele curente și viitoare de la acest utilizator" "Cameră foto" "Faceți o fotografie" "Înregistrați un videoclip" @@ -37,6 +51,7 @@ "Mesajul dvs. nu a putut fi trimis" "Adăugați emoji" "Afișați mai puțin" + "Blocați utilizatorul" "Procesarea datelor media a eșuat, vă rugăm să încercați din nou." "Numai mențiuni și cuvinte cheie" diff --git a/features/messages/impl/src/main/res/values-ru/translations.xml b/features/messages/impl/src/main/res/values-ru/translations.xml index 0fd2ddfe4d..77f7e1d0e5 100644 --- a/features/messages/impl/src/main/res/values-ru/translations.xml +++ b/features/messages/impl/src/main/res/values-ru/translations.xml @@ -1,11 +1,25 @@ + "Деятельность" + "Флаги" + "Еда и напитки" + "Животные и природа" + "Объекты" + "Смайлы и люди" + "Путешествия и места" + "Символы" "%1$d изменение в комнате" "%1$d изменения в комнате" "%1$d изменений в комнате" + "Это сообщение будет передано администратору вашего домашнего сервера. Они не смогут прочитать зашифрованные сообщения." + "Причина, по которой вы пожаловались на этот контент" + "Это начало %1$s." + "Это начало разговора." + "Новый" "Уведомить всю комнату" + "Отметьте, хотите ли вы скрыть все текущие и будущие сообщения от этого пользователя" "Камера" "Сделать фото" "Записать видео" @@ -14,7 +28,7 @@ "Местоположение" "Опрос" "Форматирование текста" - "В настоящее время история сообщений недоступна в этой комнате" + "В настоящее время история сообщений недоступна в этой комнате." "История сообщений в этой комнате недоступна. Проверьте это устройство, чтобы увидеть историю сообщений." "Не удалось получить данные о пользователе" "Хотите пригласить их снова?" @@ -25,12 +39,13 @@ "Включение этого параметра отменяет настройки по умолчанию" "Уведомить меня в этом чате" "Вы можете изменить его в своем %1$s." - "Основные Настройки" + "основные настройки" "Настройка по умолчанию" "Удалить пользовательскую настройку" "Произошла ошибка при загрузке настроек уведомлений." "Не удалось восстановить режим по умолчанию, попробуйте еще раз." "Не удалось настроить режим, попробуйте еще раз." + "Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, вы не будете получать уведомления в этой комнате." "Все сообщения" "В этой комнате уведомить меня о" "Показать меньше" @@ -41,6 +56,7 @@ "Показать меньше" "Удерживайте для записи" "Для всех" + "Заблокировать пользователя" "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Только упоминания и ключевые слова" diff --git a/features/messages/impl/src/main/res/values-sk/translations.xml b/features/messages/impl/src/main/res/values-sk/translations.xml index 8800aa7a22..0bdf008fc2 100644 --- a/features/messages/impl/src/main/res/values-sk/translations.xml +++ b/features/messages/impl/src/main/res/values-sk/translations.xml @@ -1,11 +1,25 @@ + "Aktivity" + "Vlajky" + "Jedlo a nápoje" + "Zvieratá a príroda" + "Predmety" + "Smajlíky a ľudia" + "Cestovanie a miesta" + "Symboly" "%1$d zmena miestnosti" "%1$d zmeny miestnosti" "%1$d zmien miestnosti" + "Táto správa bude nahlásená správcovi vášho domovského servera. Nebude môcť prečítať žiadne šifrované správy." + "Dôvod nahlásenia tohto obsahu" + "Toto je začiatok %1$s." + "Toto je začiatok tejto konverzácie." + "Nové" "Informovať celú miestnosť" + "Označte, či chcete skryť všetky aktuálne a budúce správy od tohto používateľa" "Kamera" "Odfotiť" "Nahrať video" @@ -31,6 +45,7 @@ "Pri načítavaní nastavení oznámení došlo k chybe." "Nepodarilo sa obnoviť predvolený režim, skúste to prosím znova." "Nepodarilo sa nastaviť režim, skúste to prosím znova." + "Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v tejto miestnosti nedostanete upozornenie." "Všetky správy" "V tejto miestnosti ma upozorniť na" "Zobraziť menej" @@ -41,6 +56,7 @@ "Zobraziť menej" "Podržaním nahrajte" "Všetci" + "Zablokovať používateľa" "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Iba zmienky a kľúčové slová" diff --git a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml index d3e9c9bce9..4f22c5bb7a 100644 --- a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,8 +1,18 @@ + "活動" + "旗幟" + "食物與飲料" + "動物與大自然" + "物品" + "表情與人物" + "旅行與景點" + "標誌" "%1$d 個聊天室變更" + "檢舉這個內容的原因" + "新訊息" "照相機" "拍照" "錄影" @@ -25,5 +35,6 @@ "無法傳送您的訊息" "新增表情符號" "較少" + "封鎖使用者" "僅限提及與關鍵字" diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 17f791e752..d9c5fce030 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -1,10 +1,24 @@ + "Activities" + "Flags" + "Food & Drink" + "Animals & Nature" + "Objects" + "Smileys & People" + "Travel & Places" + "Symbols" "%1$d room change" "%1$d room changes" + "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages." + "Reason for reporting this content" + "This is the beginning of %1$s." + "This is the beginning of this conversation." + "New" "Notify the whole room" + "Check if you want to hide all current and future messages from this user" "Camera" "Take photo" "Record video" @@ -30,6 +44,7 @@ "An error occurred while loading notification settings." "Failed restoring the default mode, please try again." "Failed setting the mode, please try again." + "Your homeserver does not support this option in encrypted rooms, you won\'t get notified in this room." "All messages" "In this room, notify me for" "Show less" @@ -40,6 +55,7 @@ "Show less" "Hold to record" "Everyone" + "Block user" "Failed processing media to upload, please try again." "Mentions and Keywords only" diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 640be695e9..7fdb2885c4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent @@ -47,6 +48,7 @@ import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.features.messages.textcomposer.TestRichTextEditorStateFactory import io.element.android.features.messages.timeline.components.customreaction.FakeEmojibaseProvider import io.element.android.features.messages.utils.messagesummary.FakeMessageSummaryFormatter +import io.element.android.features.messages.voicemessages.timeline.FakeRedactedVoiceMessageManager import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.architecture.Async @@ -641,7 +643,7 @@ class MessagesPresenterTest { FakeVoiceRecorder(), analyticsService, mediaSender, - player = VoiceMessageComposerPlayer(FakeMediaPlayer()), + player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this), messageComposerContext = FakeMessageComposerContext(), permissionsPresenterFactory, ) @@ -653,9 +655,12 @@ class MessagesPresenterTest { analyticsService = analyticsService, encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), + featureFlagService = FakeFeatureFlagService(), + redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), ) val preferencesStore = InMemoryPreferencesStore(isRichTextEditorEnabled = true) val actionListPresenter = ActionListPresenter(preferencesStore = preferencesStore) + val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter() val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom) @@ -668,6 +673,7 @@ class MessagesPresenterTest { customReactionPresenter = customReactionPresenter, reactionSummaryPresenter = reactionSummaryPresenter, retrySendMenuPresenter = retrySendMenuPresenter, + readReceiptBottomSheetPresenter = readReceiptBottomSheetPresenter, networkMonitor = FakeNetworkMonitor(), snackbarDispatcher = SnackbarDispatcher(), messageSummaryFormatter = FakeMessageSummaryFormatter(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt index 6d944075d1..68c80d6b80 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt @@ -17,7 +17,9 @@ package io.element.android.features.messages.fixtures import io.element.android.features.messages.impl.timeline.aTimelineItemReactions +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -31,6 +33,7 @@ import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.room.aTimelineItemDebugInfo +import kotlinx.collections.immutable.toImmutableList internal fun aMessageEvent( eventId: EventId? = AN_EVENT_ID, @@ -50,6 +53,7 @@ internal fun aMessageEvent( sentTime = "", isMine = isMine, reactionsState = aTimelineItemReactions(count = 0), + readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt index 18fa0e390e..972f105a4a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.timeline.groups.TimelineItemGro import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractorWithoutValidation import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter +import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem @@ -65,6 +66,7 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(), ), matrixClient = matrixClient, + lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), ), virtualItemFactory = TimelineItemVirtualFactory( daySeparatorFactory = TimelineItemDaySeparatorFactory( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index f05c209085..a48f55d120 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -26,12 +26,12 @@ import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Composer +import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.features.messages.impl.messagecomposer.AttachmentsState import io.element.android.features.messages.impl.messagecomposer.MessageComposerContextImpl import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerState -import io.element.android.features.messages.impl.messagecomposer.RoomMemberSuggestion import io.element.android.features.messages.media.FakeLocalMediaFactory import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -44,6 +44,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.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState +import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE @@ -79,10 +80,12 @@ import io.element.android.tests.testutils.waitForPredicate import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import okhttp3.internal.immutableListOf import org.junit.Rule import org.junit.Test +import uniffi.wysiwyg_composer.MentionsState import java.io.File @Suppress("LargeClass") @@ -754,19 +757,19 @@ class MessageComposerPresenterTest { // An empty suggestion returns the room and joined members that are not the current user initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) assertThat(awaitItem().memberSuggestions) - .containsExactly(RoomMemberSuggestion.Room, RoomMemberSuggestion.Member(bob), RoomMemberSuggestion.Member(david)) + .containsExactly(MentionSuggestion.Room, MentionSuggestion.Member(bob), MentionSuggestion.Member(david)) // A suggestion containing a part of "room" will also return the room mention initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "roo"))) - assertThat(awaitItem().memberSuggestions).containsExactly(RoomMemberSuggestion.Room) + assertThat(awaitItem().memberSuggestions).containsExactly(MentionSuggestion.Room) // A non-empty suggestion will return those joined members whose user id matches it initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "bob"))) - assertThat(awaitItem().memberSuggestions).containsExactly(RoomMemberSuggestion.Member(bob)) + assertThat(awaitItem().memberSuggestions).containsExactly(MentionSuggestion.Member(bob)) // A non-empty suggestion will return those joined members whose display name matches it initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "dave"))) - assertThat(awaitItem().memberSuggestions).containsExactly(RoomMemberSuggestion.Member(david)) + assertThat(awaitItem().memberSuggestions).containsExactly(MentionSuggestion.Member(david)) // If the suggestion isn't a mention, no suggestions are returned initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Command, ""))) @@ -776,7 +779,7 @@ class MessageComposerPresenterTest { room.givenCanTriggerRoomNotification(Result.success(false)) initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) assertThat(awaitItem().memberSuggestions) - .containsExactly(RoomMemberSuggestion.Member(bob), RoomMemberSuggestion.Member(david)) + .containsExactly(MentionSuggestion.Member(bob), MentionSuggestion.Member(david)) // If room is a DM, `RoomMemberSuggestion.Room` is not returned room.givenCanTriggerRoomNotification(Result.success(true)) @@ -813,8 +816,86 @@ class MessageComposerPresenterTest { // An empty suggestion returns the joined members that are not the current user, but not the room initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + skipItems(1) assertThat(awaitItem().memberSuggestions) - .containsExactly(RoomMemberSuggestion.Member(bob), RoomMemberSuggestion.Member(david)) + .containsExactly(MentionSuggestion.Member(bob), MentionSuggestion.Member(david)) + } + } + + @Test + fun `present - insertMention`() = runTest { + val presenter = createPresenter(this) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + initialState.richTextEditorState.setHtml("Hey @bo") + initialState.eventSink(MessageComposerEvents.InsertMention(MentionSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) + + assertThat(initialState.richTextEditorState.messageHtml) + .isEqualTo("Hey ${A_USER_ID_2.value}") + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `present - send messages with intentional mentions`() = runTest { + val room = FakeMatrixRoom() + val presenter = createPresenter(room = room, coroutineScope = this) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + + // Check intentional mentions on message sent + val mentionUser1 = listOf(A_USER_ID.value) + initialState.richTextEditorState.mentionsState = MentionsState( + userIds = mentionUser1, + roomIds = emptyList(), + roomAliases = emptyList(), + hasAtRoomMention = false + ) + initialState.richTextEditorState.setHtml(A_MESSAGE) + initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage())) + + advanceUntilIdle() + + assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID.value))) + + // Check intentional mentions on reply sent + initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) + val mentionUser2 = listOf(A_USER_ID_2.value) + awaitItem().richTextEditorState.mentionsState = MentionsState( + userIds = mentionUser2, + roomIds = emptyList(), + roomAliases = emptyList(), + hasAtRoomMention = false + ) + + initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage())) + advanceUntilIdle() + + assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_2.value))) + + // Check intentional mentions on edit message + skipItems(1) + initialState.eventSink(MessageComposerEvents.SetMode(anEditMode())) + val mentionUser3 = listOf(A_USER_ID_3.value) + awaitItem().richTextEditorState.mentionsState = MentionsState( + userIds = mentionUser3, + roomIds = emptyList(), + roomAliases = emptyList(), + hasAtRoomMention = false + ) + + initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage())) + advanceUntilIdle() + + assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_3.value))) + + skipItems(1) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index eeca4026bf..15ec930d2e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -24,11 +24,15 @@ import im.vector.app.features.analytics.plan.PollEnd import im.vector.app.features.analytics.plan.PollVote import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.fixtures.aTimelineItemsFactory -import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.session.SessionState +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager +import io.element.android.features.messages.voicemessages.timeline.FakeRedactedVoiceMessageManager +import io.element.android.features.messages.voicemessages.timeline.aRedactedMatrixTimeline import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem @@ -310,9 +314,31 @@ class TimelinePresenterTest { } } + @Test + fun `present - side effect on redacted items is invoked`() = runTest { + val redactedVoiceMessageManager = FakeRedactedVoiceMessageManager() + val presenter = createTimelinePresenter( + timeline = FakeMatrixTimeline( + initialTimelineItems = aRedactedMatrixTimeline(AN_EVENT_ID), + ), + redactedVoiceMessageManager = redactedVoiceMessageManager, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) // skip initial state + assertThat(redactedVoiceMessageManager.invocations.size).isEqualTo(0) + awaitItem().let { + assertThat(it.timelineItems).isNotEmpty() + assertThat(redactedVoiceMessageManager.invocations.size).isEqualTo(1) + } + } + } + private fun TestScope.createTimelinePresenter( timeline: MatrixTimeline = FakeMatrixTimeline(), - timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory() + timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(), + redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), ): TimelinePresenter { return TimelinePresenter( timelineItemsFactory = timelineItemsFactory, @@ -322,6 +348,8 @@ class TimelinePresenterTest { analyticsService = FakeAnalyticsService(), encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), + featureFlagService = FakeFeatureFlagService(), + redactedVoiceMessageManager = redactedVoiceMessageManager, ) } @@ -337,6 +365,8 @@ class TimelinePresenterTest { analyticsService = analyticsService, encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), + featureFlagService = FakeFeatureFlagService(), + redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt new file mode 100644 index 0000000000..248fcad399 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.timeline.components.receipt.bottomsheet + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter +import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class ReadReceiptBottomSheetPresenterTests { + + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - handle event selected`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val selectedEvent = aTimelineItemEvent() + initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) + assertThat(awaitItem().selectedEvent).isSameInstanceAs(selectedEvent) + } + } + + @Test + fun `present - handle dismiss`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val selectedEvent = aTimelineItemEvent() + initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) + skipItems(1) + initialState.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + assertThat(awaitItem().selectedEvent).isNull() + } + } + + private fun createPresenter() = ReadReceiptBottomSheetPresenter() +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt index 4c43a8552f..e8a8eb30cf 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt @@ -21,7 +21,9 @@ import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper import io.element.android.features.messages.impl.timeline.groups.computeGroupIdWith +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel import io.element.android.libraries.designsystem.components.avatar.anAvatarData @@ -42,6 +44,7 @@ class TimelineItemGrouperTest { senderDisplayName = "", content = TimelineItemStateEventContent(body = "a state event"), reactionsState = aTimelineItemReactions(count = 0), + readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = LocalEventSendState.Sent(AN_EVENT_ID), inReplyTo = null, isThreaded = false, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index 598ceaf227..4ec1432377 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -35,8 +35,8 @@ import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_USER_NAME -import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.permissions.api.PermissionsPresenter @@ -44,7 +44,7 @@ import io.element.android.libraries.permissions.api.aPermissionsState import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.libraries.textcomposer.model.MessageComposerMode -import io.element.android.libraries.textcomposer.model.PressEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder @@ -99,7 +99,7 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) @@ -116,13 +116,13 @@ class VoiceMessageComposerPresenterTest { presenter.present() }.test { awaitItem().apply { - eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) assertThat(keepScreenOn).isFalse() } awaitItem().apply { assertThat(keepScreenOn).isTrue() - eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) } val finalState = awaitItem().apply { @@ -139,13 +139,11 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.Tapped)) - + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Cancel)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) - testPauseAndDestroy(finalState) } } @@ -156,8 +154,8 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState()) @@ -173,7 +171,7 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem().apply { this.eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) } @@ -192,10 +190,9 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aLoadedState()) } val finalState = awaitItem().also { assertThat(it.voiceMessageState).isEqualTo(aPlayingState()) } @@ -211,10 +208,9 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - skipItems(1) // Loaded state awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Pause)) val finalState = awaitItem().also { assertThat(it.voiceMessageState).isEqualTo(aPausedState()) @@ -225,14 +221,41 @@ class VoiceMessageComposerPresenterTest { } } + @Test + fun `present - seek recording`() = runTest { + val presenter = createVoiceMessageComposerPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.5f))) + awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.5f, time = 0.seconds, showCursor = true)) + } + awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.5f, time = 5.seconds, showCursor = true)) + eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.2f))) + } + awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.2f, time = 5.seconds, showCursor = true)) + } + val finalState = awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.2f, time = 2.seconds, showCursor = true)) + } + + testPauseAndDestroy(finalState) + } + } + @Test fun `present - delete recording`() = runTest { val presenter = createVoiceMessageComposerPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage) val finalState = awaitItem() @@ -249,10 +272,9 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - skipItems(1) // Loaded state awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage) awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPausedState()) @@ -272,8 +294,8 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) @@ -294,15 +316,15 @@ class VoiceMessageComposerPresenterTest { }.test { // Send a normal voice message messageComposerContext.composerMode = MessageComposerMode.Normal - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) skipItems(1) // Sending state // Now reply with a voice message messageComposerContext.composerMode = aReplyMode() - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) val finalState = awaitItem() // Sending state @@ -321,12 +343,12 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - skipItems(1) // Loaded state awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) assertThat(awaitItem().voiceMessageState).isEqualTo(aPlayingState().toSendingState()) + skipItems(1) // Duplicate sending state val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) @@ -343,8 +365,8 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().run { eventSink(VoiceMessageComposerEvents.SendVoiceMessage) eventSink(VoiceMessageComposerEvents.SendVoiceMessage) @@ -368,8 +390,8 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPreviewState()) eventSink(VoiceMessageComposerEvents.SendVoiceMessage) @@ -393,8 +415,8 @@ class VoiceMessageComposerPresenterTest { presenter.present() }.test { mediaPreProcessor.givenResult(Result.failure(Exception())) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) val previewState = awaitItem() previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) @@ -415,6 +437,42 @@ class VoiceMessageComposerPresenterTest { } } + @Test + fun `present - send failures are displayed as an error dialog`() = runTest { + val presenter = createVoiceMessageComposerPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + // Let sending fail due to media preprocessing error + mediaPreProcessor.givenResult(Result.failure(Exception())) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + + assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) + + awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState().toSendingState()) + assertThat(showSendFailureDialog).isTrue() + } + + awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState()) + assertThat(showSendFailureDialog).isTrue() + eventSink(VoiceMessageComposerEvents.DismissSendFailureDialog) + } + + val finalState = awaitItem().apply { + assertThat(voiceMessageState).isEqualTo(aPreviewState()) + assertThat(showSendFailureDialog).isFalse() + } + + + assertThat(matrixRoom.sendMediaCount).isEqualTo(0) + testPauseAndDestroy(finalState) + } + } + @Test fun `present - send error - missing recording is tracked`() = runTest { val presenter = createVoiceMessageComposerPresenter() @@ -443,7 +501,7 @@ class VoiceMessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) assertThat(matrixRoom.sendMediaCount).isEqualTo(0) assertThat(analyticsService.trackedErrors).containsExactly( @@ -467,15 +525,15 @@ class VoiceMessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Idle) - initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) + initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) voiceRecorder.assertCalls(stopped = 1) permissionsPresenter.setPermissionGranted() - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) voiceRecorder.assertCalls(stopped = 1, started = 1) @@ -495,7 +553,7 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) // See the dialog and accept it awaitItem().also { @@ -509,7 +567,7 @@ class VoiceMessageComposerPresenterTest { permissionsPresenter.setPermissionGranted() - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) voiceRecorder.assertCalls(started = 1) @@ -529,7 +587,7 @@ class VoiceMessageComposerPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) // See the dialog and accept it awaitItem().also { @@ -541,7 +599,7 @@ class VoiceMessageComposerPresenterTest { // Dialog is hidden, user tries to record again awaitItem().also { assertThat(it.showPermissionRationaleDialog).isFalse() - it.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) + it.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) } // Dialog is shown once again @@ -603,7 +661,7 @@ class VoiceMessageComposerPresenterTest { voiceRecorder, analyticsService, mediaSender, - player = VoiceMessageComposerPlayer(FakeMediaPlayer()), + player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this), messageComposerContext = messageComposerContext, FakePermissionsPresenterFactory(permissionsPresenter), ) @@ -639,14 +697,6 @@ class VoiceMessageComposerPresenterTest { waveform = waveform.toImmutableList(), ) - private fun aLoadedState() = - aPreviewState( - isPlaying = false, - playbackProgress = 0.0f, - showCursor = true, - time = 0.seconds, - ) - private fun aPlayingState() = aPreviewState( isPlaying = true, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt index 78c34cd60b..e989a07b15 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt @@ -16,15 +16,17 @@ package io.element.android.features.messages.voicemessages.timeline +import app.cash.turbine.TurbineTestContext import app.cash.turbine.test import com.google.common.truth.Truth -import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.features.messages.impl.voicemessages.timeline.DefaultVoiceMessagePlayer import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageMediaRepo -import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer +import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessagePlayer import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.mediaplayer.api.MediaPlayer +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import kotlinx.coroutines.test.runTest import org.junit.Test @@ -33,68 +35,176 @@ class DefaultVoiceMessagePlayerTest { @Test fun `initial state`() = runTest { createDefaultVoiceMessagePlayer().state.test { - awaitItem().let { - Truth.assertThat(it.isPlaying).isEqualTo(false) - Truth.assertThat(it.isMyMedia).isEqualTo(false) - Truth.assertThat(it.currentPosition).isEqualTo(0) - } + matchInitialState() } } @Test - fun `downloading and play works`() = runTest { + fun `prepare succeeds`() = runTest { val player = createDefaultVoiceMessagePlayer() player.state.test { - skipItems(1) // skip initial state. - Truth.assertThat(player.play().isSuccess).isTrue() - awaitItem().let { - Truth.assertThat(it.isPlaying).isEqualTo(false) - Truth.assertThat(it.isMyMedia).isEqualTo(true) - Truth.assertThat(it.currentPosition).isEqualTo(0) - } - awaitItem().let { - Truth.assertThat(it.isPlaying).isEqualTo(true) - Truth.assertThat(it.isMyMedia).isEqualTo(true) - Truth.assertThat(it.currentPosition).isEqualTo(1000) - } + matchInitialState() + Truth.assertThat(player.prepare().isSuccess).isTrue() + matchReadyState() } } @Test - fun `downloading and play fails`() = runTest { + fun `prepare fails when repo fails`() = runTest { val player = createDefaultVoiceMessagePlayer( voiceMessageMediaRepo = FakeVoiceMessageMediaRepo().apply { shouldFail = true }, ) player.state.test { - skipItems(1) // skip initial state. - Truth.assertThat(player.play().isFailure).isTrue() + matchInitialState() + Truth.assertThat(player.prepare().isFailure).isTrue() } } @Test - fun `play fails with no eventId`() = runTest { + fun `prepare fails with no eventId`() = runTest { val player = createDefaultVoiceMessagePlayer( eventId = null ) player.state.test { - skipItems(1) // skip initial state. - Truth.assertThat(player.play().isFailure).isTrue() + matchInitialState() + Truth.assertThat(player.prepare().isFailure).isTrue() } } @Test - fun `pause playing works`() = runTest { + fun `play after prepare works`() = runTest { val player = createDefaultVoiceMessagePlayer() player.state.test { - skipItems(1) // skip initial state. - Truth.assertThat(player.play().isSuccess).isTrue() - skipItems(2) // skip play states + matchInitialState() + Truth.assertThat(player.prepare().isSuccess).isTrue() + matchReadyState() + player.play() + awaitItem().let { + Truth.assertThat(it.isPlaying).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + } + } + } + + @Test + fun `play reaches end of media`() = runTest { + val player = createDefaultVoiceMessagePlayer( + mediaPlayer = FakeMediaPlayer( + fakeTotalDurationMs = 1000, + fakePlayedDurationMs = 1000 + ) + ) + player.state.test { + matchInitialState() + Truth.assertThat(player.prepare().isSuccess).isTrue() + matchReadyState(fakeTotalDurationMs = 1000) + player.play() + awaitItem().let { + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + Truth.assertThat(it.duration).isEqualTo(1000) + } + } + } + + @Test + fun `player1 plays again after both player1 and player2 are finished`() = runTest { + val mediaPlayer = FakeMediaPlayer( + fakeTotalDurationMs = 1_000L, + fakePlayedDurationMs = 1_000L, + ) + val player1 = createDefaultVoiceMessagePlayer(mediaPlayer = mediaPlayer) + val player2 = createDefaultVoiceMessagePlayer(mediaPlayer = mediaPlayer) + + // Play player1 until the end. + player1.state.test { + matchInitialState() + Truth.assertThat(player1.prepare().isSuccess).isTrue() + matchReadyState(1_000L) + player1.play() + awaitItem().let { // it plays until the end. + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + Truth.assertThat(it.duration).isEqualTo(1000) + } + } + + // Play player2 until the end. + player2.state.test { + matchInitialState() + Truth.assertThat(player2.prepare().isSuccess).isTrue() + awaitItem().let { // Additional spurious state due to MediaPlayer owner change. + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + Truth.assertThat(it.duration).isEqualTo(1000) + } + awaitItem().let {// Additional spurious state due to MediaPlayer owner change. + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) + Truth.assertThat(it.currentPosition).isEqualTo(0) + Truth.assertThat(it.duration).isEqualTo(null) + } + matchReadyState(1_000L) + player2.play() + awaitItem().let { // it plays until the end. + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + Truth.assertThat(it.duration).isEqualTo(1000) + } + } + + // Play player1 again. + player1.state.test { + awaitItem().let {// Last previous state/ + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + Truth.assertThat(it.duration).isEqualTo(1000) + } + Truth.assertThat(player1.prepare().isSuccess).isTrue() + awaitItem().let {// Additional spurious state due to MediaPlayer owner change. + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) + Truth.assertThat(it.currentPosition).isEqualTo(0) + Truth.assertThat(it.duration).isEqualTo(null) + } + matchReadyState(1_000L) + player1.play() + awaitItem().let { // it played again until the end. + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(true) + Truth.assertThat(it.currentPosition).isEqualTo(1000) + Truth.assertThat(it.duration).isEqualTo(1000) + } + } + } + + @Test + fun `pause after play pauses`() = runTest { + val player = createDefaultVoiceMessagePlayer() + player.state.test { + matchInitialState() + Truth.assertThat(player.prepare().isSuccess).isTrue() + matchReadyState() + player.play() + skipItems(1) // skip play state player.pause() awaitItem().let { Truth.assertThat(it.isPlaying).isEqualTo(false) - Truth.assertThat(it.isMyMedia).isEqualTo(true) Truth.assertThat(it.currentPosition).isEqualTo(1000) } } @@ -104,39 +214,72 @@ class DefaultVoiceMessagePlayerTest { fun `play after pause works`() = runTest { val player = createDefaultVoiceMessagePlayer() player.state.test { - skipItems(1) // skip initial state. - Truth.assertThat(player.play().isSuccess).isTrue() - skipItems(2) // skip play states + matchInitialState() + Truth.assertThat(player.prepare().isSuccess).isTrue() + matchReadyState() + player.play() + skipItems(1) // skip play state player.pause() - skipItems(1) + skipItems(1) // skip pause state player.play() awaitItem().let { Truth.assertThat(it.isPlaying).isEqualTo(true) - Truth.assertThat(it.isMyMedia).isEqualTo(true) Truth.assertThat(it.currentPosition).isEqualTo(2000) } } } @Test - fun `seek to works`() = runTest { + fun `seek before prepare works`() = runTest { val player = createDefaultVoiceMessagePlayer() player.state.test { - skipItems(1) // skip initial state. - Truth.assertThat(player.play().isSuccess).isTrue() - skipItems(2) // skip play states + matchInitialState() player.seekTo(2000) awaitItem().let { - Truth.assertThat(it.isPlaying).isEqualTo(true) - Truth.assertThat(it.isMyMedia).isEqualTo(true) + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) Truth.assertThat(it.currentPosition).isEqualTo(2000) + Truth.assertThat(it.duration).isEqualTo(null) + } + Truth.assertThat(player.prepare().isSuccess).isTrue() + awaitItem().let { + Truth.assertThat(it.isReady).isEqualTo(true) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) + Truth.assertThat(it.currentPosition).isEqualTo(2000) + Truth.assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS) + } + } + } + + @Test + fun `seek after prepare works`() = runTest { + val player = createDefaultVoiceMessagePlayer() + player.state.test { + matchInitialState() + Truth.assertThat(player.prepare().isSuccess).isTrue() + matchReadyState() + player.seekTo(2000) + awaitItem().let { + Truth.assertThat(it.isReady).isEqualTo(true) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) + Truth.assertThat(it.currentPosition).isEqualTo(2000) + Truth.assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS) } } } } +private const val FAKE_TOTAL_DURATION_MS = 10_000L +private const val FAKE_PLAYED_DURATION_MS = 1000L + private fun createDefaultVoiceMessagePlayer( - mediaPlayer: MediaPlayer = FakeMediaPlayer(), + mediaPlayer: MediaPlayer = FakeMediaPlayer( + fakeTotalDurationMs = FAKE_TOTAL_DURATION_MS, + fakePlayedDurationMs = FAKE_PLAYED_DURATION_MS + ), voiceMessageMediaRepo: VoiceMessageMediaRepo = FakeVoiceMessageMediaRepo(), eventId: EventId? = AN_EVENT_ID, ) = DefaultVoiceMessagePlayer( @@ -152,3 +295,25 @@ private fun createDefaultVoiceMessagePlayer( ) private const val MXC_URI = "mxc://matrix.org/1234567890abcdefg" + +private suspend fun TurbineTestContext.matchInitialState() { + awaitItem().let { + Truth.assertThat(it.isReady).isEqualTo(false) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) + Truth.assertThat(it.currentPosition).isEqualTo(0) + Truth.assertThat(it.duration).isEqualTo(null) + } +} + +private suspend fun TurbineTestContext.matchReadyState( + fakeTotalDurationMs: Long = FAKE_TOTAL_DURATION_MS, +) { + awaitItem().let { + Truth.assertThat(it.isReady).isEqualTo(true) + Truth.assertThat(it.isPlaying).isEqualTo(false) + Truth.assertThat(it.isEnded).isEqualTo(false) + Truth.assertThat(it.currentPosition).isEqualTo(0) + Truth.assertThat(it.duration).isEqualTo(fakeTotalDurationMs) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt new file mode 100644 index 0000000000..0ff58d7728 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.voicemessages.timeline + +import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem + +class FakeRedactedVoiceMessageManager : RedactedVoiceMessageManager { + + private val _invocations: MutableList> = mutableListOf() + val invocations: List> + get() = _invocations + + override suspend fun onEachMatrixTimelineItem(timelineItems: List) { + _invocations.add(timelineItems) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt new file mode 100644 index 0000000000..ccf3d4aa92 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.voicemessages.timeline + +import com.google.common.truth.Truth +import io.element.android.features.messages.impl.voicemessages.timeline.DefaultRedactedVoiceMessageManager +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.mediaplayer.api.MediaPlayer +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class RedactedVoiceMessageManagerTest { + @Test + fun `redacted event - no playing related media`() = runTest { + val mediaPlayer = FakeMediaPlayer().apply { + setMedia(uri = "someUri", mediaId = AN_EVENT_ID.value, mimeType = "audio/ogg") + play() + } + val manager = aDefaultRedactedVoiceMessageManager(mediaPlayer = mediaPlayer) + + Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value) + Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue() + + manager.onEachMatrixTimelineItem(aRedactedMatrixTimeline(AN_EVENT_ID_2)) + + Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value) + Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue() + } + + @Test + fun `redacted event - playing related media is paused`() = runTest { + val mediaPlayer = FakeMediaPlayer().apply { + setMedia(uri = "someUri", mediaId = AN_EVENT_ID.value, mimeType = "audio/ogg") + play() + } + val manager = aDefaultRedactedVoiceMessageManager(mediaPlayer = mediaPlayer) + + Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value) + Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue() + + manager.onEachMatrixTimelineItem(aRedactedMatrixTimeline(AN_EVENT_ID)) + + Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value) + Truth.assertThat(mediaPlayer.state.value.isPlaying).isFalse() + } +} + +fun TestScope.aDefaultRedactedVoiceMessageManager( + mediaPlayer: MediaPlayer = FakeMediaPlayer(), +) = DefaultRedactedVoiceMessageManager( + dispatchers = this.testCoroutineDispatchers(true), + mediaPlayer = mediaPlayer, +) + +fun aRedactedMatrixTimeline(eventId: EventId) = listOf( + MatrixTimelineItem.Event( + uniqueId = 0, + event = EventTimelineItem( + eventId = eventId, + transactionId = null, + isEditable = false, + isLocal = false, + isOwn = false, + isRemote = false, + localSendState = null, + reactions = listOf(), + receipts = listOf(), + sender = A_USER_ID, + senderProfile = ProfileTimelineDetails.Unavailable, + timestamp = 9442, + content = RedactedContent, + debugInfo = TimelineItemDebugInfo( + model = "enim", + originalJson = null, + latestEditedJson = null + ), + origin = null + ), + ) +) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt index 0097245d27..00c8cd1f70 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt @@ -31,6 +31,7 @@ import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMes import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -52,6 +53,7 @@ class VoiceMessagePresenterTest { @Test fun `pressing play downloads and plays`() = runTest { val presenter = createVoiceMessagePresenter( + mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), content = aTimelineItemVoiceContent(durationMs = 2_000), ) moleculeFlow(RecompositionMode.Immediate) { @@ -87,6 +89,7 @@ class VoiceMessagePresenterTest { fun `pressing play downloads and fails`() = runTest { val analyticsService = FakeAnalyticsService() val presenter = createVoiceMessagePresenter( + mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), voiceMessageMediaRepo = FakeVoiceMessageMediaRepo().apply { shouldFail = true }, analyticsService = analyticsService, content = aTimelineItemVoiceContent(durationMs = 2_000), @@ -113,7 +116,10 @@ class VoiceMessagePresenterTest { Truth.assertThat(it.time).isEqualTo("0:02") } analyticsService.trackedErrors.first().also { - Truth.assertThat(it).isInstanceOf(VoiceMessageException.PlayMessageError::class.java) + Truth.assertThat(it).apply { + isInstanceOf(VoiceMessageException.PlayMessageError::class.java) + hasMessageThat().isEqualTo("Error while trying to play voice message") + } } } } @@ -121,6 +127,7 @@ class VoiceMessagePresenterTest { @Test fun `pressing pause while playing pauses`() = runTest { val presenter = createVoiceMessagePresenter( + mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), content = aTimelineItemVoiceContent(durationMs = 2_000), ) moleculeFlow(RecompositionMode.Immediate) { @@ -167,7 +174,32 @@ class VoiceMessagePresenterTest { } @Test - fun `seeking seeks`() = runTest { + fun `seeking before play`() = runTest { + val presenter = createVoiceMessagePresenter( + mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000), + content = aTimelineItemVoiceContent(durationMs = 10_000), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem().also { + Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play) + Truth.assertThat(it.progress).isEqualTo(0f) + Truth.assertThat(it.time).isEqualTo("0:10") + } + + initialState.eventSink(VoiceMessageEvents.Seek(0.5f)) + + awaitItem().also { + Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play) + Truth.assertThat(it.progress).isEqualTo(0.5f) + Truth.assertThat(it.time).isEqualTo("0:05") + } + } + } + + @Test + fun `seeking after play`() = runTest { val presenter = createVoiceMessagePresenter( content = aTimelineItemVoiceContent(durationMs = 10_000), ) @@ -201,14 +233,15 @@ class VoiceMessagePresenterTest { } } -fun createVoiceMessagePresenter( +fun TestScope.createVoiceMessagePresenter( + mediaPlayer: FakeMediaPlayer = FakeMediaPlayer(), voiceMessageMediaRepo: VoiceMessageMediaRepo = FakeVoiceMessageMediaRepo(), analyticsService: AnalyticsService = FakeAnalyticsService(), content: TimelineItemVoiceContent = aTimelineItemVoiceContent(), ) = VoiceMessagePresenter( voiceMessagePlayerFactory = { eventId, mediaSource, mimeType, body -> DefaultVoiceMessagePlayer( - mediaPlayer = FakeMediaPlayer(), + mediaPlayer = mediaPlayer, voiceMessageMediaRepoFactory = { _, _, _ -> voiceMessageMediaRepo }, eventId = eventId, mediaSource = mediaSource, @@ -217,5 +250,6 @@ fun createVoiceMessagePresenter( ) }, analyticsService = analyticsService, + scope = this, content = content, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt index fea42baf5f..f79c53335c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt @@ -16,8 +16,12 @@ package io.element.android.features.preferences.impl.advanced +import io.element.android.libraries.theme.theme.Theme + sealed interface AdvancedSettingsEvents { data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents - data class SetCustomElementCallBaseUrl(val baseUrl: String?) : AdvancedSettingsEvents + data object ChangeTheme : AdvancedSettingsEvents + data object CancelChangeTheme : AdvancedSettingsEvents + data class SetTheme(val theme: Theme) : AdvancedSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 4bb5abfa19..bf438db4ef 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 @@ -17,25 +17,21 @@ package io.element.android.features.preferences.impl.advanced import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import io.element.android.appconfig.ElementCallConfig import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.theme.theme.Theme +import io.element.android.libraries.theme.theme.mapToTheme import kotlinx.coroutines.launch -import java.net.URL import javax.inject.Inject class AdvancedSettingsPresenter @Inject constructor( private val preferencesStore: PreferencesStore, - private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable @@ -47,15 +43,11 @@ class AdvancedSettingsPresenter @Inject constructor( val isDeveloperModeEnabled by preferencesStore .isDeveloperModeEnabledFlow() .collectAsState(initial = false) - val customElementCallBaseUrl by preferencesStore - .getCustomElementCallBaseUrlFlow() - .collectAsState(initial = null) - - var canDisplayElementCallSettings by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - canDisplayElementCallSettings = featureFlagService.isFeatureEnabled(FeatureFlags.InRoomCalls) + val theme by remember { + preferencesStore.getThemeFlow().mapToTheme() } - + .collectAsState(initial = Theme.System) + var showChangeThemeDialog by remember { mutableStateOf(false) } fun handleEvents(event: AdvancedSettingsEvents) { when (event) { is AdvancedSettingsEvents.SetRichTextEditorEnabled -> localCoroutineScope.launch { @@ -64,10 +56,11 @@ class AdvancedSettingsPresenter @Inject constructor( is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { preferencesStore.setDeveloperModeEnabled(event.enabled) } - is AdvancedSettingsEvents.SetCustomElementCallBaseUrl -> localCoroutineScope.launch { - // If the URL is either empty or the default one, we want to save 'null' to remove the custom URL - val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() && it != ElementCallConfig.DEFAULT_BASE_URL } - preferencesStore.setCustomElementCallBaseUrl(urlToSave) + AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false + AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true + is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch { + preferencesStore.setTheme(event.theme.name) + showChangeThemeDialog = false } } } @@ -75,23 +68,9 @@ class AdvancedSettingsPresenter @Inject constructor( return AdvancedSettingsState( isRichTextEditorEnabled = isRichTextEditorEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled, - customElementCallBaseUrlState = if (canDisplayElementCallSettings) { - CustomElementCallBaseUrlState( - baseUrl = customElementCallBaseUrl, - defaultUrl = ElementCallConfig.DEFAULT_BASE_URL, - validator = ::customElementCallUrlValidator, - ) - } else null, + theme = theme, + showChangeThemeDialog = showChangeThemeDialog, eventSink = { handleEvents(it) } ) } - - private fun customElementCallUrlValidator(url: String?): Boolean { - return runCatching { - if (url.isNullOrEmpty()) return@runCatching - val parsedUrl = URL(url) - if (parsedUrl.protocol !in listOf("http", "https")) error("Incorrect protocol") - if (parsedUrl.host.isNullOrBlank()) error("Missing host") - }.isSuccess - } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index cd56078b27..8258684750 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -16,15 +16,12 @@ package io.element.android.features.preferences.impl.advanced +import io.element.android.libraries.theme.theme.Theme + data class AdvancedSettingsState( val isRichTextEditorEnabled: Boolean, val isDeveloperModeEnabled: Boolean, - val customElementCallBaseUrlState: CustomElementCallBaseUrlState?, + val theme: Theme, + val showChangeThemeDialog: Boolean, val eventSink: (AdvancedSettingsEvents) -> Unit ) - -data class CustomElementCallBaseUrlState( - val baseUrl: String?, - val defaultUrl: String, - val validator: (String?) -> Boolean, -) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt index d3a2dee3f4..012a39896c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.preferences.impl.advanced import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.theme.theme.Theme open class AdvancedSettingsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -24,17 +25,18 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider Unit, modifier: Modifier = Modifier, ) { - fun isUsingDefaultUrl(value: String?): Boolean { - val defaultUrl = state.customElementCallBaseUrlState?.defaultUrl ?: return false - return value.isNullOrEmpty() || value == defaultUrl - } - PreferencePage( modifier = modifier, onBackPressed = onBackPressed, title = stringResource(id = CommonStrings.common_advanced_settings) ) { + ListItem( + headlineContent = { + Text( + text = stringResource(id = CommonStrings.common_appearance) + ) + }, + trailingContent = ListItemContent.Text( + state.theme.toHumanReadable() + ), + onClick = { + state.eventSink(AdvancedSettingsEvents.ChangeTheme) + } + ) PreferenceSwitch( title = stringResource(id = CommonStrings.common_rich_text_editor), subtitle = stringResource(id = R.string.screen_advanced_settings_rich_text_editor_description), @@ -58,24 +72,40 @@ fun AdvancedSettingsView( isChecked = state.isDeveloperModeEnabled, onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(it)) }, ) - state.customElementCallBaseUrlState?.let { callUrlState -> - val supportingText = if (isUsingDefaultUrl(callUrlState.baseUrl)) { - stringResource(R.string.screen_advanced_settings_element_call_base_url_description) - } else { - callUrlState.baseUrl - } - PreferenceTextField( - headline = stringResource(R.string.screen_advanced_settings_element_call_base_url), - value = callUrlState.baseUrl ?: callUrlState.defaultUrl, - supportingText = supportingText, - validation = callUrlState.validator, - onValidationErrorMessage = stringResource(R.string.screen_advanced_settings_element_call_base_url_validation_error), - displayValue = { value -> !isUsingDefaultUrl(value) }, - keyboardOptions = KeyboardOptions.Default.copy(autoCorrect = false, keyboardType = KeyboardType.Uri), - onChange = { state.eventSink(AdvancedSettingsEvents.SetCustomElementCallBaseUrl(it)) } - ) - } } + + if (state.showChangeThemeDialog) { + SingleSelectionDialog( + options = getOptions(), + initialSelection = themes.indexOf(state.theme), + onOptionSelected = { + state.eventSink( + AdvancedSettingsEvents.SetTheme( + themes[it] + ) + ) + }, + onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangeTheme) }, + ) + } +} + +@Composable +private fun getOptions(): ImmutableList { + return themes.map { + ListOption(title = it.toHumanReadable()) + }.toImmutableList() +} + +@Composable +private fun Theme.toHumanReadable(): String { + return stringResource( + id = when (this) { + Theme.System -> CommonStrings.common_system + Theme.Dark -> CommonStrings.common_dark + Theme.Light -> CommonStrings.common_light + } + ) } @PreviewsDayNight 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 ce67916178..376f14f7bf 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 @@ -20,5 +20,6 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents + data class SetCustomElementCallBaseUrl(val baseUrl: String?) : 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 ebcd44b4db..ae6c70188b 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 @@ -19,12 +19,16 @@ package io.element.android.features.preferences.impl.developer import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshots.SnapshotStateMap +import io.element.android.appconfig.ElementCallConfig +import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter @@ -39,6 +43,7 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import java.net.URL import javax.inject.Inject class DeveloperSettingsPresenter @Inject constructor( @@ -46,6 +51,7 @@ class DeveloperSettingsPresenter @Inject constructor( private val computeCacheSizeUseCase: ComputeCacheSizeUseCase, private val clearCacheUseCase: ClearCacheUseCase, private val rageshakePresenter: RageshakePreferencesPresenter, + private val preferencesStore: PreferencesStore, ) : Presenter { @Composable @@ -64,8 +70,12 @@ class DeveloperSettingsPresenter @Inject constructor( val clearCacheAction = remember { mutableStateOf>(Async.Uninitialized) } + val customElementCallBaseUrl by preferencesStore + .getCustomElementCallBaseUrlFlow() + .collectAsState(initial = null) + LaunchedEffect(Unit) { - FeatureFlags.values().forEach { feature -> + FeatureFlags.entries.forEach { feature -> features[feature.key] = feature enabledFeatures[feature.key] = featureFlagService.isFeatureEnabled(feature) } @@ -86,6 +96,11 @@ class DeveloperSettingsPresenter @Inject constructor( event.isEnabled, triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) } ) + is DeveloperSettingsEvents.SetCustomElementCallBaseUrl -> coroutineScope.launch { + // If the URL is either empty or the default one, we want to save 'null' to remove the custom URL + val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() && it != ElementCallConfig.DEFAULT_BASE_URL } + preferencesStore.setCustomElementCallBaseUrl(urlToSave) + } DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) } } @@ -95,6 +110,11 @@ class DeveloperSettingsPresenter @Inject constructor( cacheSize = cacheSize.value, clearCacheAction = clearCacheAction.value, rageshakeState = rageshakeState, + customElementCallBaseUrlState = CustomElementCallBaseUrlState( + baseUrl = customElementCallBaseUrl, + defaultUrl = ElementCallConfig.DEFAULT_BASE_URL, + validator = ::customElementCallUrlValidator, + ), eventSink = ::handleEvents ) } @@ -145,5 +165,14 @@ class DeveloperSettingsPresenter @Inject constructor( } } +private fun customElementCallUrlValidator(url: String?): Boolean { + return runCatching { + if (url.isNullOrEmpty()) return@runCatching + val parsedUrl = URL(url) + if (parsedUrl.protocol !in listOf("http", "https")) error("Incorrect protocol") + if (parsedUrl.host.isNullOrBlank()) error("Missing host") + }.isSuccess +} + 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 8d79c9241d..d49e94b309 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 @@ -21,10 +21,17 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import kotlinx.collections.immutable.ImmutableList -data class DeveloperSettingsState constructor( +data class DeveloperSettingsState( val features: ImmutableList, val cacheSize: Async, val rageshakeState: RageshakePreferencesState, val clearCacheAction: Async, + val customElementCallBaseUrlState: CustomElementCallBaseUrlState, val eventSink: (DeveloperSettingsEvents) -> Unit ) + +data class CustomElementCallBaseUrlState( + val baseUrl: String?, + val defaultUrl: String, + val validator: (String?) -> Boolean, +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index ee5c897987..719b736c09 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 @@ -26,6 +26,13 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider !isUsingDefaultUrl(value) }, + keyboardOptions = KeyboardOptions.Default.copy(autoCorrect = false, keyboardType = KeyboardType.Uri), + onChange = { state.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl(it)) } + ) + } +} + @Composable private fun FeatureListContent( state: DeveloperSettingsState, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index f93ecad3fd..167dc9b1c1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.lifecycle.Lifecycle +import io.element.android.features.preferences.impl.R import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule import io.element.android.libraries.designsystem.components.async.AsyncView @@ -57,7 +58,7 @@ fun NotificationSettingsView( PreferencePage( modifier = modifier, onBackPressed = onBackPressed, - title = stringResource(id = CommonStrings.screen_notification_settings_title) + title = stringResource(id = R.string.screen_notification_settings_title) ) { when (state.matrixSettings) { @@ -80,7 +81,7 @@ fun NotificationSettingsView( } AsyncView( async = state.changeNotificationSettingAction, - errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) }, + errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) }, onSuccess = {}, ) @@ -103,10 +104,10 @@ private fun NotificationSettingsContentView( if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) { PreferenceText( iconResourceId = CommonDrawables.ic_compound_notifications_solid_off, - title = stringResource(id = CommonStrings.screen_notification_settings_system_notifications_turned_off), + title = stringResource(id = R.string.screen_notification_settings_system_notifications_turned_off), subtitle = stringResource( - id = CommonStrings.screen_notification_settings_system_notifications_action_required, - stringResource(id = CommonStrings.screen_notification_settings_system_notifications_action_required_content_link) + id = R.string.screen_notification_settings_system_notifications_action_required, + stringResource(id = R.string.screen_notification_settings_system_notifications_action_required_content_link) ), onClick = { context.startNotificationSettingsIntent() @@ -116,31 +117,31 @@ private fun NotificationSettingsContentView( PreferenceSwitch( modifier = modifier, - title = stringResource(id = CommonStrings.screen_notification_settings_enable_notifications), + title = stringResource(id = R.string.screen_notification_settings_enable_notifications), isChecked = systemSettings.appNotificationsEnabled, switchAlignment = Alignment.Top, onCheckedChange = onNotificationsEnabledChanged ) if (systemSettings.appNotificationsEnabled) { - PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_notification_section_title)) { + PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_notification_section_title)) { PreferenceText( - title = stringResource(id = CommonStrings.screen_notification_settings_group_chats), + title = stringResource(id = R.string.screen_notification_settings_group_chats), subtitle = getTitleForRoomNotificationMode(mode = matrixSettings.defaultGroupNotificationMode), onClick = onGroupChatsClicked ) PreferenceText( - title = stringResource(id = CommonStrings.screen_notification_settings_direct_chats), + title = stringResource(id = R.string.screen_notification_settings_direct_chats), subtitle = getTitleForRoomNotificationMode(mode = matrixSettings.defaultOneToOneNotificationMode), onClick = onDirectChatsClicked ) } - PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_mode_mentions)) { + PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_mode_mentions)) { PreferenceSwitch( modifier = Modifier, - title = stringResource(id = CommonStrings.screen_notification_settings_room_mention_label), + title = stringResource(id = R.string.screen_notification_settings_room_mention_label), isChecked = matrixSettings.atRoomNotificationsEnabled, switchAlignment = Alignment.Top, onCheckedChange = onMentionNotificationsChanged @@ -162,8 +163,8 @@ private fun NotificationSettingsContentView( @Composable private fun getTitleForRoomNotificationMode(mode: RoomNotificationMode?) = when (mode) { - RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) - RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) + RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_notification_settings_edit_mode_all_messages) + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = R.string.screen_notification_settings_edit_mode_mentions_and_keywords) RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) null -> "" } @@ -177,8 +178,8 @@ private fun InvalidNotificationSettingsView( ) { DialogLikeBannerMolecule( modifier = modifier, - title = stringResource(CommonStrings.screen_notification_settings_configuration_mismatch), - content = stringResource(CommonStrings.screen_notification_settings_configuration_mismatch_description), + title = stringResource(R.string.screen_notification_settings_configuration_mismatch), + content = stringResource(R.string.screen_notification_settings_configuration_mismatch_description), onSubmitClicked = onContinueClicked, onDismissClicked = null, ) @@ -186,7 +187,7 @@ private fun InvalidNotificationSettingsView( if (showError) { ErrorDialog( title = stringResource(id = CommonStrings.dialog_title_error), - content = stringResource(id = CommonStrings.screen_notification_settings_failed_fixing_configuration), + content = stringResource(id = R.string.screen_notification_settings_failed_fixing_configuration), onDismiss = onDismissError ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt index c7d2ac507d..3201243e75 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt @@ -27,13 +27,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp +import io.element.android.features.preferences.impl.R import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.RadioButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.theme.ElementTheme -import io.element.android.libraries.ui.strings.CommonStrings @Composable fun DefaultNotificationSettingOption( @@ -43,8 +43,8 @@ fun DefaultNotificationSettingOption( onOptionSelected: (RoomNotificationMode) -> Unit = {}, ) { val subtitle = when(mode) { - RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) - RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) + RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_notification_settings_edit_mode_all_messages) + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = R.string.screen_notification_settings_edit_mode_mentions_and_keywords) else -> "" } Row( diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index dbbd90791b..415d5e01da 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.preferences.impl.R import io.element.android.libraries.designsystem.components.async.AsyncView import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -49,9 +50,9 @@ fun EditDefaultNotificationSettingView( modifier: Modifier = Modifier, ) { val title = if (state.isOneToOne) { - CommonStrings.screen_notification_settings_direct_chats + R.string.screen_notification_settings_direct_chats } else { - CommonStrings.screen_notification_settings_group_chats + R.string.screen_notification_settings_group_chats } PreferencePage( modifier = modifier, @@ -63,9 +64,9 @@ fun EditDefaultNotificationSettingView( val validModes = listOf(RoomNotificationMode.ALL_MESSAGES, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val categoryTitle = if (state.isOneToOne) { - CommonStrings.screen_notification_settings_edit_screen_direct_section_header + R.string.screen_notification_settings_edit_screen_direct_section_header } else { - CommonStrings.screen_notification_settings_edit_screen_group_section_header + R.string.screen_notification_settings_edit_screen_group_section_header } PreferenceCategory(title = stringResource(id = categoryTitle)) { @@ -82,12 +83,12 @@ fun EditDefaultNotificationSettingView( } } if (state.roomsWithUserDefinedMode.isNotEmpty()) { - PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_edit_custom_settings_section_title)) { + PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_edit_custom_settings_section_title)) { state.roomsWithUserDefinedMode.forEach { summary -> val subtitle = when (summary.details.notificationMode) { - RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) + RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_notification_settings_edit_mode_all_messages) RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> { - stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) + stringResource(id = R.string.screen_notification_settings_edit_mode_mentions_and_keywords) } RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) null -> "" @@ -117,7 +118,7 @@ fun EditDefaultNotificationSettingView( } AsyncView( async = state.changeNotificationSettingAction, - errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) }, + errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) }, onSuccess = {}, ) 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 801a7f6895..4fa20bde8f 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 @@ -20,7 +20,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.outlined.InsertChart import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -28,6 +27,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.user.UserPreferences import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferencePage @@ -116,7 +116,7 @@ fun PreferencesRootView( } if (state.showNotificationSettings) { ListItem( - headlineContent = { Text(stringResource(id = CommonStrings.screen_notification_settings_title)) }, + headlineContent = { Text(stringResource(id = R.string.screen_notification_settings_title)) }, leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_compound_notifications)), onClick = onOpenNotificationSettings, ) @@ -134,7 +134,7 @@ fun PreferencesRootView( if (state.showLockScreenSettings) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_screen_lock)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(Icons.Default.Lock)), + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_lock_outline)), onClick = onOpenLockScreenSettings, ) } diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml index d19ba2ac08..49ffdd14a1 100644 --- a/features/preferences/impl/src/main/res/values-cs/translations.xml +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -12,4 +12,31 @@ "Nelze aktualizovat profil" "Upravit profil" "Aktualizace profilu…" + "Další nastavení" + "Halsové a video hovory" + "Neshoda konfigurace" + "Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností. + +Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní. + +Pokud budete pokračovat, některá nastavení se mohou změnit." + "Přímé zprávy" + "Vlastní nastavení pro chat" + "Při aktualizaci nastavení oznámení došlo k chybě." + "Všechny zprávy" + "Pouze zmínky a klíčová slova" + "V přímých zprávách mě upozornit na" + "Ve skupinových chatech mě upozornit na" + "Povolit oznámení na tomto zařízení" + "Konfigurace nebyla opravena, zkuste to prosím znovu." + "Skupinové chaty" + "Zmínky" + "Vše" + "Zmínky" + "Upozornit mě na" + "Upozornit mě na @room" + "Chcete-li dostávat oznámení, změňte prosím svůj %1$s." + "systémová nastavení" + "Systémová oznámení byla vypnuta" + "Oznámení" diff --git a/features/preferences/impl/src/main/res/values-de/translations.xml b/features/preferences/impl/src/main/res/values-de/translations.xml index 3992b93ab7..4287d4d7c8 100644 --- a/features/preferences/impl/src/main/res/values-de/translations.xml +++ b/features/preferences/impl/src/main/res/values-de/translations.xml @@ -8,4 +8,27 @@ "Profil kann nicht aktualisiert werden" "Profil bearbeiten" "Profil wird aktualisiert…" + "Zusätzliche Einstellungen" + "Audio- und Videoanrufe" + "Konfiguration stimmt nicht überein" + "Wir haben die Einstellungen für Benachrichtigungen vereinfacht, damit die Optionen leichter zu finden sind. Einige benutzerdefinierte Einstellungen, die du in der Vergangenheit gewählt hast, werden hier nicht angezeigt, sind aber immer noch aktiv. Wenn du fortfährst, können sich einige deiner Einstellungen ändern." + "Direkte Chats" + "Benutzerdefinierte Einstellung pro Chat" + "Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." + "Alle Nachrichten" + "Nur Erwähnungen und Schlüsselwörter" + "Bei direkten Chats, benachrichtige mich bei" + "Bei Gruppenchats benachrichtige mich bei" + "Benachrichtigungen auf diesem Gerät aktivieren" + "Die Konfiguration wurde nicht korrigiert, bitte versuche es erneut." + "Gruppenchats" + "Erwähnungen" + "Alle" + "Erwähnungen" + "Benachrichtige mich bei" + "Benachrichtige mich bei @room" + "Um Benachrichtigungen zu erhalten, ändere bitte deine %1$s." + "Systemeinstellungen" + "Systembenachrichtigungen deaktiviert" + "Benachrichtigungen" diff --git a/features/preferences/impl/src/main/res/values-fr/translations.xml b/features/preferences/impl/src/main/res/values-fr/translations.xml index 85ac80b5d9..a831656698 100644 --- a/features/preferences/impl/src/main/res/values-fr/translations.xml +++ b/features/preferences/impl/src/main/res/values-fr/translations.xml @@ -12,4 +12,31 @@ "Impossible de mettre à jour le profil" "Modifier le profil" "Mise à jour du profil…" + "Réglages supplémentaires" + "Appels audio et vidéo" + "Incompatibilité de configuration" + "Nous avons simplifié les paramètres des notifications pour que les options soient plus faciles à trouver. + +Certains paramètres personnalisés que vous avez choisis par le passé ne sont pas affichés ici, mais ils sont toujours actifs. + +Si vous continuez, il est possible que certains de vos paramètres soient modifiés." + "Discussions directes" + "Paramétrage personnalisé par salon" + "Une erreur s’est produite lors de la mise à jour du paramètre de notification." + "Tous les messages" + "Mentions et mots clés uniquement" + "Sur les discussions directes, prévenez-moi pour" + "Lors de discussions de groupe, prévenez-moi pour" + "Activer les notifications sur cet appareil" + "La configuration n’a pas été corrigée, veuillez réessayer." + "Discussions de groupe" + "Mentions" + "Tous" + "Mentions" + "Prévenez-moi pour" + "Prévenez-moi si un message contient \"@room\"" + "Pour recevoir des notifications, veuillez modifier votre %1$s." + "paramètres du système" + "Les notifications du système sont désactivées" + "Notifications" diff --git a/features/preferences/impl/src/main/res/values-ro/translations.xml b/features/preferences/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..1b652c3a30 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,30 @@ + + + "Setări adiționale" + "Apeluri audio și video" + "Nepotrivire de configurație" + "Am simplificat Setările pentru notificări pentru a face opțiunile mai ușor de găsit. + +Unele setări personalizate pe care le-ați ales în trecut nu sunt afișate aici, dar sunt încă active. + +Dacă continuați, unele dintre setările dumneavoastră pot fi modificate." + "Discuții directe" + "Setare personalizată per chat" + "A apărut o eroare în timpul actualizării setărilor pentru notificari." + "Toate mesajele" + "Numai mențiuni și cuvinte cheie" + "În conversațiile directe, anunță-mă pentru" + "În conversațiile de grup, anunțați-mă pentru" + "Activați notificările pe acest dispozitiv" + "Configurația nu a fost corectată, vă rugăm să încercați din nou." + "Discuții de grup" + "Mențiuni" + "Toate" + "Mențiuni" + "Anunță-mă pentru" + "Anunțați-mă pentru @room" + "Pentru a primi notificări, vă rugăm să vă schimbați %1$s." + "Setări de sistem" + "Notificările de sistem sunt dezactivate" + "Notificări" + diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml index ed2d9b4d54..3432902dea 100644 --- a/features/preferences/impl/src/main/res/values-ru/translations.xml +++ b/features/preferences/impl/src/main/res/values-ru/translations.xml @@ -1,7 +1,7 @@ "Базовый URL сервера звонков Element" - "Задайте свой сервер звонков Element" + "Задайте свой сервер Element Call." "Адрес указан неверно, удостоверьтесь, что вы указали протокол (http/https) и правильный адрес." "Режим разработчика" "Предоставьте разработчикам доступ к функциям и функциональным возможностям." @@ -12,4 +12,30 @@ "Невозможно обновить профиль" "Редактировать профиль" "Обновление профиля…" + "Дополнительные параметры" + "Аудио и видео звонки" + "Несоответствие конфигурации" + "Мы упростили настройки уведомлений, чтобы упростить поиск опций. Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны. + +Если вы продолжите, некоторые настройки могут быть изменены." + "Прямые чаты" + "Индивидуальные настройки для каждого чата" + "При обновлении настроек уведомления произошла ошибка." + "Все сообщения" + "Только упоминания и ключевые слова" + "Уведомлять меня в личных чатах" + "Уведомлять меня в групповых чатах" + "Включить уведомления на данном устройстве" + "Конфигурация не была исправлена, попробуйте еще раз." + "Групповые чаты" + "Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, в некоторых комнатах вы можете не получать уведомления." + "Упоминания" + "Все" + "Упоминания" + "Уведомить меня" + "Уведомить меня в @room" + "Чтобы получать уведомления, измените свой %1$s." + "настройки системы" + "Системные уведомления выключены" + "Уведомления" 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 2e1dc22b93..2b8f866255 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -12,4 +12,32 @@ "Nepodarilo sa aktualizovať profil" "Upraviť profil" "Aktualizácia profilu…" + "Ďalšie nastavenia" + "Audio a video hovory" + "Nezhoda konfigurácie" + "Zjednodušili sme Nastavenia oznámení, aby ste ľahšie našli možnosti. + +Niektoré vlastné nastavenia, ktoré ste si nastavili v minulosti, sa tu nezobrazujú, ale sú stále aktívne. + +Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť." + "Priame konverzácie" + "Vlastné nastavenie pre konverzácie" + "Pri aktualizácii nastavenia oznámenia došlo k chybe." + "Všetky správy" + "Iba zmienky a kľúčové slová" + "Pri priamych rozhovoroch ma upozorniť na" + "Pri skupinových rozhovoroch ma upozorniť na" + "Povoliť oznámenia na tomto zariadení" + "Konfigurácia nebola opravená, skúste to prosím znova." + "Skupinové rozhovory" + "Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie." + "Zmienky" + "Všetky" + "Zmienky" + "Upozorniť ma na" + "Upozorniť ma na @miestnosť" + "Ak chcete dostávať oznámenia, zmeňte prosím svoje %1$s." + "nastavenia systému" + "Systémové oznámenia sú vypnuté" + "Oznámenia" diff --git a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml index 046e820a51..1282f45e4a 100644 --- a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml @@ -6,4 +6,16 @@ "無法更新個人檔案" "編輯個人檔案" "正在更新個人檔案…" + "其他設定" + "私訊" + "更新通知設定時發生錯誤。" + "所有訊息" + "僅限提及與關鍵字" + "在這個裝置上開啟通知" + "群組聊天" + "提及" + "提及" + "系統設定" + "已關閉系統通知" + "通知" diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 1ca7071436..9cb6aaf698 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -12,4 +12,30 @@ "Unable to update profile" "Edit profile" "Updating profile…" + "Additional settings" + "Audio and video calls" + "Configuration mismatch" + "We’ve simplified Notifications Settings to make options easier to find. Some custom settings you’ve chosen in the past are not shown here, but they’re still active. + +If you proceed, some of your settings may change." + "Direct chats" + "Custom setting per chat" + "An error occurred while updating the notification setting." + "All messages" + "Mentions and Keywords only" + "On direct chats, notify me for" + "On group chats, notify me for" + "Enable notifications on this device" + "The configuration has not been corrected, please try again." + "Group chats" + "Your homeserver does not support this option in encrypted rooms, you may not get notified in some rooms." + "Mentions" + "All" + "Mentions" + "Notify me for" + "Notify me on @room" + "To receive notifications, please change your %1$s." + "system settings" + "System notifications turned off" + "Notifications" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index c2a7fb3e20..00ea3bbf14 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -20,9 +20,8 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore +import io.element.android.libraries.theme.theme.Theme import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import kotlinx.coroutines.test.runTest @@ -37,23 +36,22 @@ class AdvancedSettingsPresenterTest { @Test fun `present - initial state`() = runTest { val store = InMemoryPreferencesStore() - val featureFlagService = FakeFeatureFlagService() - val presenter = AdvancedSettingsPresenter(store, featureFlagService) + val presenter = AdvancedSettingsPresenter(store) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitLastSequentialItem() assertThat(initialState.isDeveloperModeEnabled).isFalse() assertThat(initialState.isRichTextEditorEnabled).isFalse() - assertThat(initialState.customElementCallBaseUrlState?.baseUrl).isNull() + assertThat(initialState.showChangeThemeDialog).isFalse() + assertThat(initialState.theme).isEqualTo(Theme.System) } } @Test fun `present - developer mode on off`() = runTest { val store = InMemoryPreferencesStore() - val featureFlagService = FakeFeatureFlagService() - val presenter = AdvancedSettingsPresenter(store, featureFlagService) + val presenter = AdvancedSettingsPresenter(store) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -69,8 +67,7 @@ class AdvancedSettingsPresenterTest { @Test fun `present - rich text editor on off`() = runTest { val store = InMemoryPreferencesStore() - val featureFlagService = FakeFeatureFlagService() - val presenter = AdvancedSettingsPresenter(store, featureFlagService) + val presenter = AdvancedSettingsPresenter(store) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -84,54 +81,26 @@ class AdvancedSettingsPresenterTest { } @Test - fun `present - custom element call url state is null if the feature flag is disabled`() = runTest { + fun `present - change theme`() = runTest { val store = InMemoryPreferencesStore() - val featureFlagService = FakeFeatureFlagService().apply { - setFeatureEnabled(FeatureFlags.InRoomCalls, false) - } - val presenter = AdvancedSettingsPresenter(store, featureFlagService) + val presenter = AdvancedSettingsPresenter(store) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitLastSequentialItem() - assertThat(initialState.customElementCallBaseUrlState).isNull() - } - } - - @Test - fun `present - custom element call base url`() = runTest { - val store = InMemoryPreferencesStore() - val featureFlagService = FakeFeatureFlagService(initialState = hashMapOf(FeatureFlags.InRoomCalls.key to true)) - val presenter = AdvancedSettingsPresenter(store, featureFlagService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitLastSequentialItem() - assertThat(initialState.customElementCallBaseUrlState).isNotNull() - assertThat(initialState.customElementCallBaseUrlState?.baseUrl).isNull() - - initialState.eventSink(AdvancedSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.ahoy")) - val updatedItem = awaitItem() - assertThat(updatedItem.customElementCallBaseUrlState?.baseUrl).isEqualTo("https://call.element.ahoy") - } - } - - @Test - fun `present - custom element call base url validator needs at least an HTTP scheme and host`() = runTest { - val store = InMemoryPreferencesStore() - val featureFlagService = FakeFeatureFlagService().apply { - setFeatureEnabled(FeatureFlags.InRoomCalls, true) - } - val presenter = AdvancedSettingsPresenter(store, featureFlagService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val urlValidator = awaitLastSequentialItem().customElementCallBaseUrlState!!.validator - assertThat(urlValidator("")).isTrue() // We allow empty string to clear the value and use the default one - assertThat(urlValidator("test")).isFalse() - assertThat(urlValidator("http://")).isFalse() - assertThat(urlValidator("geo://test")).isFalse() - assertThat(urlValidator("https://call.element.io")).isTrue() + initialState.eventSink.invoke(AdvancedSettingsEvents.ChangeTheme) + val withDialog = awaitItem() + assertThat(withDialog.showChangeThemeDialog).isTrue() + // Cancel + withDialog.eventSink(AdvancedSettingsEvents.CancelChangeTheme) + val withoutDialog = awaitItem() + assertThat(withoutDialog.showChangeThemeDialog).isFalse() + withDialog.eventSink.invoke(AdvancedSettingsEvents.ChangeTheme) + assertThat(awaitItem().showChangeThemeDialog).isTrue() + withDialog.eventSink(AdvancedSettingsEvents.SetTheme(Theme.Light)) + val withNewTheme = awaitItem() + assertThat(withNewTheme.showChangeThemeDialog).isFalse() + assertThat(withNewTheme.theme).isEqualTo(Theme.Light) } } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 88778d4227..fb10ee272a 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.appconfig.ElementCallConfig import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter @@ -28,7 +29,9 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto import io.element.android.libraries.architecture.Async import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.awaitLastSequentialItem import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -40,13 +43,7 @@ class DeveloperSettingsPresenterTest { @Test fun `present - ensures initial state is correct`() = runTest { - val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) - val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService(), - FakeComputeCacheSizeUseCase(), - FakeClearCacheUseCase(), - rageshakePresenter - ) + val presenter = createDeveloperSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -54,6 +51,8 @@ class DeveloperSettingsPresenterTest { assertThat(initialState.features).isEmpty() assertThat(initialState.clearCacheAction).isEqualTo(Async.Uninitialized) assertThat(initialState.cacheSize).isEqualTo(Async.Uninitialized) + assertThat(initialState.customElementCallBaseUrlState).isNotNull() + assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull() val loadedState = awaitItem() assertThat(loadedState.rageshakeState.isEnabled).isFalse() assertThat(loadedState.rageshakeState.isSupported).isTrue() @@ -64,32 +63,20 @@ class DeveloperSettingsPresenterTest { @Test fun `present - ensures feature list is loaded`() = runTest { - val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) - val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService(), - FakeComputeCacheSizeUseCase(), - FakeClearCacheUseCase(), - rageshakePresenter, - ) + val presenter = createDeveloperSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) val state = awaitItem() - assertThat(state.features).hasSize(FeatureFlags.values().size) + assertThat(state.features).hasSize(FeatureFlags.entries.size) cancelAndIgnoreRemainingEvents() } } @Test fun `present - ensures state is updated when enabled feature event is triggered`() = runTest { - val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) - val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService(), - FakeComputeCacheSizeUseCase(), - FakeClearCacheUseCase(), - rageshakePresenter, - ) + val presenter = createDeveloperSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -109,12 +96,7 @@ class DeveloperSettingsPresenterTest { fun `present - clear cache`() = runTest { val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) val clearCacheUseCase = FakeClearCacheUseCase() - val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService(), - FakeComputeCacheSizeUseCase(), - clearCacheUseCase, - rageshakePresenter, - ) + val presenter = createDeveloperSettingsPresenter(clearCacheUseCase = clearCacheUseCase, rageshakePresenter = rageshakePresenter) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -130,4 +112,52 @@ class DeveloperSettingsPresenterTest { cancelAndIgnoreRemainingEvents() } } + + @Test + fun `present - custom element call base url`() = runTest { + val preferencesStore = InMemoryPreferencesStore() + val presenter = createDeveloperSettingsPresenter(preferencesStore = preferencesStore) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull() + initialState.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.ahoy")) + val updatedItem = awaitItem() + assertThat(updatedItem.customElementCallBaseUrlState.baseUrl).isEqualTo("https://call.element.ahoy") + assertThat(updatedItem.customElementCallBaseUrlState.defaultUrl).isEqualTo(ElementCallConfig.DEFAULT_BASE_URL) + } + } + + @Test + fun `present - custom element call base url validator needs at least an HTTP scheme and host`() = runTest { + val presenter = createDeveloperSettingsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val urlValidator = awaitLastSequentialItem().customElementCallBaseUrlState.validator + assertThat(urlValidator("")).isTrue() // We allow empty string to clear the value and use the default one + assertThat(urlValidator("test")).isFalse() + assertThat(urlValidator("http://")).isFalse() + assertThat(urlValidator("geo://test")).isFalse() + assertThat(urlValidator("https://call.element.io")).isTrue() + } + } + + private fun createDeveloperSettingsPresenter( + featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), + cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(), + clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(), + rageshakePresenter: DefaultRageshakePreferencesPresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()), + preferencesStore: InMemoryPreferencesStore = InMemoryPreferencesStore(), + ): DeveloperSettingsPresenter { + return DeveloperSettingsPresenter( + featureFlagService = featureFlagService, + computeCacheSizeUseCase = cacheSizeUseCase, + clearCacheUseCase = clearCacheUseCase, + rageshakePresenter = rageshakePresenter, + preferencesStore = preferencesStore, + ) + } } 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 b9b8566d4a..d3c830a821 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 @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.rageshake.api.R import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSlide import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch @@ -43,7 +44,7 @@ fun RageshakePreferencesView( } Column(modifier = modifier) { - PreferenceCategory(title = stringResource(id = CommonStrings.settings_rageshake)) { + PreferenceCategory(title = stringResource(id = R.string.settings_rageshake)) { if (state.isSupported) { PreferenceSwitch( title = stringResource(id = CommonStrings.preference_rageshake), @@ -51,7 +52,7 @@ fun RageshakePreferencesView( onCheckedChange = ::onEnabledChanged ) PreferenceSlide( - title = stringResource(id = CommonStrings.settings_rageshake_detection_threshold), + title = stringResource(id = R.string.settings_rageshake_detection_threshold), // summary = stringResource(id = CommonStrings.settings_rageshake_detection_threshold_summary), value = state.sensitivity, enabled = state.isEnabled, diff --git a/features/rageshake/api/src/main/res/values-cs/translations.xml b/features/rageshake/api/src/main/res/values-cs/translations.xml index 59e4448ba6..7a48cf84db 100644 --- a/features/rageshake/api/src/main/res/values-cs/translations.xml +++ b/features/rageshake/api/src/main/res/values-cs/translations.xml @@ -2,4 +2,6 @@ "%1$s havaroval při posledním použití. Chcete se s námi podělit o zprávu o selhání?" "Zdá se, že frustrovaně třesete telefonem. Chcete nahlásit chybu?" + "Rageshake" + "Práh detekce" diff --git a/features/rageshake/api/src/main/res/values-de/translations.xml b/features/rageshake/api/src/main/res/values-de/translations.xml index 6cfb4608dc..6a22b4a72c 100644 --- a/features/rageshake/api/src/main/res/values-de/translations.xml +++ b/features/rageshake/api/src/main/res/values-de/translations.xml @@ -1,4 +1,6 @@ "%1$s ist bei der letzten Nutzung abgestürzt. Möchtest du einen Absturzbericht mit uns teilen?" + "Rageshake" + "Erkennungsschwelle" diff --git a/features/rageshake/api/src/main/res/values-es/translations.xml b/features/rageshake/api/src/main/res/values-es/translations.xml index 597ec74260..cc9b1f810d 100644 --- a/features/rageshake/api/src/main/res/values-es/translations.xml +++ b/features/rageshake/api/src/main/res/values-es/translations.xml @@ -2,4 +2,6 @@ "%1$s se cerró inesperadamente la última vez que se lo usaste. ¿Quieres compartir un informe de error con nosotros?" "Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?" + "Agitar con fuerza" + "Umbral de detección" diff --git a/features/rageshake/api/src/main/res/values-fr/translations.xml b/features/rageshake/api/src/main/res/values-fr/translations.xml index 4b41fdc0cb..e21171120e 100644 --- a/features/rageshake/api/src/main/res/values-fr/translations.xml +++ b/features/rageshake/api/src/main/res/values-fr/translations.xml @@ -2,4 +2,6 @@ "%1$s s’est arrêté la dernière fois qu’il a été utilisé. Souhaitez-vous partager un rapport d’incident avec nous ?" "Vous semblez secouez votre téléphone avec frustration. Souhaitez-vous ouvrir le formulaire pour reporter un problème?" + "Rageshake" + "Seuil de détection" diff --git a/features/rageshake/api/src/main/res/values-it/translations.xml b/features/rageshake/api/src/main/res/values-it/translations.xml index 6d5e7a74c0..d9580064c7 100644 --- a/features/rageshake/api/src/main/res/values-it/translations.xml +++ b/features/rageshake/api/src/main/res/values-it/translations.xml @@ -2,4 +2,6 @@ "%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?" "Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?" + "Rageshake" + "Soglia di rilevamento" diff --git a/features/rageshake/api/src/main/res/values-ro/translations.xml b/features/rageshake/api/src/main/res/values-ro/translations.xml index 2c89703deb..67a72e3d55 100644 --- a/features/rageshake/api/src/main/res/values-ro/translations.xml +++ b/features/rageshake/api/src/main/res/values-ro/translations.xml @@ -2,4 +2,6 @@ "%1$s s-a blocat ultima dată când a fost folosit. Doriți să ne trimiteți un raport?" "Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?" + "Rageshake" + "Prag de detecție" diff --git a/features/rageshake/api/src/main/res/values-ru/translations.xml b/features/rageshake/api/src/main/res/values-ru/translations.xml index d45ecb192d..24ba792de6 100644 --- a/features/rageshake/api/src/main/res/values-ru/translations.xml +++ b/features/rageshake/api/src/main/res/values-ru/translations.xml @@ -2,4 +2,6 @@ "При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом о сбое?" "Похоже, что вы трясете телефон. Хотите открыть экран сообщения об ошибке?" + "Rageshake" + "Порог обнаружения" diff --git a/features/rageshake/api/src/main/res/values-sk/translations.xml b/features/rageshake/api/src/main/res/values-sk/translations.xml index c1d55eab34..69eca0738c 100644 --- a/features/rageshake/api/src/main/res/values-sk/translations.xml +++ b/features/rageshake/api/src/main/res/values-sk/translations.xml @@ -2,4 +2,6 @@ "%1$s zlyhal pri poslednom použití. Chcete zdieľať správu o páde s našim tímom?" "Zdá sa, že zúrivo trasiete telefónom. Chcete otvoriť obrazovku s hlásením chýb?" + "Zúrivé potrasenie" + "Prahová hodnota detekcie" diff --git a/features/rageshake/api/src/main/res/values/localazy.xml b/features/rageshake/api/src/main/res/values/localazy.xml index bb694f2d00..f2d86ab45b 100644 --- a/features/rageshake/api/src/main/res/values/localazy.xml +++ b/features/rageshake/api/src/main/res/values/localazy.xml @@ -2,4 +2,6 @@ "%1$s crashed the last time it was used. Would you like to share a crash report with us?" "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?" + "Rageshake" + "Detection threshold" diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index eced5c78b8..f119cbf6c8 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { api(projects.features.rageshake.api) implementation(libs.androidx.datastore.preferences) implementation(platform(libs.network.okhttp.bom)) - implementation("com.squareup.okhttp3:okhttp") + implementation(libs.network.okhttp.okhttp) implementation(libs.coil) implementation(libs.coil.compose) ksp(libs.showkase.processor) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index 2cbb54cdad..7833c21764 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -64,7 +64,7 @@ fun BugReportView( Box(modifier = modifier) { PreferencePage( - title = stringResource(id = CommonStrings.common_report_a_bug), + title = stringResource(id = CommonStrings.common_report_a_problem), onBackPressed = onBackPressed ) { val isFormEnabled = state.sending !is Async.Loading diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt index 6b5d7c9096..e848082e91 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt @@ -37,7 +37,7 @@ import java.util.logging.Logger * Will be planted in Timber. */ class VectorFileLogger( - context: Context, + private val context: Context, // private val vectorPreferences: VectorPreferences private val dispatcher: CoroutineDispatcher = Dispatchers.IO, ) : Timber.Tree() { @@ -66,7 +66,9 @@ class VectorFileLogger( } private val fileHandler: FileHandler? - private val cacheDirectory = File(context.cacheDir, "logs") + private val cacheDirectory get() = File(context.cacheDir, "logs").apply { + if (!exists()) mkdirs() + } private var fileNamePrefix = "logs" private val prioPrefixes = mapOf( @@ -79,10 +81,6 @@ class VectorFileLogger( ) init { - if (!cacheDirectory.exists()) { - cacheDirectory.mkdirs() - } - for (i in 0..15) { val file = File(cacheDirectory, "elementLogs.${i}.txt") file.safeDelete() diff --git a/features/rageshake/impl/src/main/res/values-ru/translations.xml b/features/rageshake/impl/src/main/res/values-ru/translations.xml index 8f05a3148d..c22a34bf30 100644 --- a/features/rageshake/impl/src/main/res/values-ru/translations.xml +++ b/features/rageshake/impl/src/main/res/values-ru/translations.xml @@ -4,8 +4,8 @@ "Вы можете связаться со мной, если у Вас возникнут какие-либо дополнительные вопросы." "Связаться со мной" "Редактировать снимок экрана" - "Пожалуйста, опишите ошибку. Что вы сделали? Что вы ожидали, что произойдет? Что произошло на самом деле. Пожалуйста, опишите все как можно подробнее." - "Опишите ошибку…" + "Пожалуйста, опишите ошибку. Что вы сделали? Какое поведение вы ожидали? Что произошло на самом деле. Пожалуйста, опишите все как можно подробнее." + "Опишите проблему…" "Если возможно, пожалуйста, напишите описание на английском языке." "Отправка журналов сбоев" "Разрешить ведение журналов" diff --git a/features/rageshake/impl/src/main/res/values/localazy.xml b/features/rageshake/impl/src/main/res/values/localazy.xml index fb02c93780..34ba8b5b30 100644 --- a/features/rageshake/impl/src/main/res/values/localazy.xml +++ b/features/rageshake/impl/src/main/res/values/localazy.xml @@ -4,8 +4,8 @@ "You may contact me if you have any follow up questions." "Contact me" "Edit screenshot" - "Please describe the bug. What did you do? What did you expect to happen? What actually happened. Please go into as much detail as you can." - "Describe the bug…" + "Please describe the problem. What did you do? What did you expect to happen? What actually happened. Please go into as much detail as you can." + "Describe the problem…" "If possible, please write the description in English." "Send crash logs" "Allow logs" 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 f3c6b13253..b5b1b83b98 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 @@ -32,7 +32,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.Add -import androidx.compose.material.icons.outlined.Person import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -57,6 +56,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.MainActionButton +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.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -66,6 +66,9 @@ import io.element.android.libraries.designsystem.theme.components.DropdownMenu import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -324,10 +327,10 @@ private fun NotificationSection( stringResource(R.string.screen_room_details_notification_mode_custom) } PreferenceCategory(modifier = modifier) { - PreferenceText( - title = stringResource(R.string.screen_room_details_notification_title), - subtitle = subtitle, - iconResourceId = CommonDrawables.ic_compound_notifications, + ListItem( + headlineContent = { Text(text = stringResource(R.string.screen_room_details_notification_title)) }, + supportingContent = { Text(text = subtitle) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_compound_notifications)), onClick = openRoomNotificationSettings, ) } @@ -340,10 +343,10 @@ private fun MembersSection( modifier: Modifier = Modifier, ) { PreferenceCategory(modifier = modifier) { - PreferenceText( - title = stringResource(CommonStrings.common_people), - icon = Icons.Outlined.Person, - currentValue = memberCount.toString(), + ListItem( + headlineContent = { Text(stringResource(CommonStrings.common_people)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_user)), + trailingContent = ListItemContent.Text(memberCount.toString()), onClick = openRoomMemberList, ) } @@ -355,9 +358,9 @@ private fun InviteSection( modifier: Modifier = Modifier, ) { PreferenceCategory(modifier = modifier) { - PreferenceText( - title = stringResource(R.string.screen_room_details_invite_people_title), - iconResourceId = CommonDrawables.ic_compound_user_add, + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_details_invite_people_title)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_user_add)), onClick = invitePeople, ) } @@ -366,10 +369,10 @@ private fun InviteSection( @Composable private fun SecuritySection(modifier: Modifier = Modifier) { PreferenceCategory(title = stringResource(R.string.screen_room_details_security_title), modifier = modifier) { - PreferenceText( - title = stringResource(R.string.screen_room_details_encryption_enabled_title), - subtitle = stringResource(R.string.screen_room_details_encryption_enabled_subtitle), - iconResourceId = CommonDrawables.ic_compound_lock, + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_details_encryption_enabled_title)) }, + supportingContent = { Text(stringResource(R.string.screen_room_details_encryption_enabled_subtitle)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_encryption_enabled)), ) } } @@ -377,10 +380,10 @@ private fun SecuritySection(modifier: Modifier = Modifier) { @Composable private fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) { PreferenceCategory(showDivider = false, modifier = modifier) { - PreferenceText( - title = stringResource(R.string.screen_room_details_leave_room_title), - iconResourceId = CommonDrawables.ic_compound_leave, - tintColor = MaterialTheme.colorScheme.error, + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_details_leave_room_title)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_compound_leave)), + style = ListItemStyle.Destructive, onClick = onLeaveRoom, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt index c21e8520b3..05d75211e7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt @@ -16,20 +16,26 @@ package io.element.android.features.roomdetails.impl.blockuser -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Block -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.progressSemantics import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.components.dialogs.RetryDialog +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.PreferenceText +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -65,21 +71,29 @@ private fun PreferenceBlockUser( eventSink: (RoomMemberDetailsEvents) -> Unit, modifier: Modifier = Modifier, ) { + val loadingCurrentValue = @Composable { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(20.dp), + strokeWidth = 2.dp + ) + } if (isBlocked.orFalse()) { - PreferenceText( - title = stringResource(R.string.screen_dm_details_unblock_user), - icon = Icons.Outlined.Block, + ListItem( + headlineContent = { Text(stringResource(R.string.screen_dm_details_unblock_user)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_compound_block)), onClick = { if (!isLoading) eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true)) }, - loadingCurrentValue = isLoading, + trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null, modifier = modifier, ) } else { - PreferenceText( - title = stringResource(R.string.screen_dm_details_block_user), - icon = Icons.Outlined.Block, - tintColor = MaterialTheme.colorScheme.error, + ListItem( + headlineContent = { Text(stringResource(R.string.screen_dm_details_block_user)) }, + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_compound_block)), + style = ListItemStyle.Destructive, onClick = { if (!isLoading) eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true)) }, - loadingCurrentValue = isLoading, + trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null, modifier = modifier, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 96372c0e65..cdb46a2fcc 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -156,14 +156,14 @@ private fun RoomSpecificNotificationSettingsView( AsyncView( async = state.setNotificationSettingAction, onSuccess = {}, - errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) }, + errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) }, ) AsyncView( async = state.restoreDefaultAction, onSuccess = {}, - errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) }, + errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) }, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt index 88a4b8a0a1..e304ee2cfa 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -22,26 +22,22 @@ import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.designsystem.components.async.AsyncView import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ListItem +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.CommonDrawables -import io.element.android.libraries.ui.strings.CommonStrings @Composable fun UserDefinedRoomNotificationSettingsView( @@ -76,10 +72,9 @@ fun UserDefinedRoomNotificationSettingsView( ) } - PreferenceText( - title = stringResource(R.string.screen_room_notification_settings_edit_remove_setting), - icon = ImageVector.vectorResource(CommonDrawables.ic_compound_delete), - tintColor = MaterialTheme.colorScheme.error, + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_notification_settings_edit_remove_setting)) }, + style = ListItemStyle.Destructive, onClick = { state.eventSink(RoomNotificationSettingsEvents.DeleteCustomNotification) } @@ -88,14 +83,14 @@ fun UserDefinedRoomNotificationSettingsView( AsyncView( async = state.setNotificationSettingAction, onSuccess = {}, - errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) }, + errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) }, ) AsyncView( async = state.restoreDefaultAction, onSuccess = { onBackPressed() }, - errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) }, + errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) }, ) } diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index b02fcbc8e8..110257d6ae 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -5,6 +5,7 @@ "%1$d osoby" "%1$d osob" + "Při aktualizaci nastavení oznámení došlo k chybě." "Přidat téma" "Již členem" "Již pozván(a)" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index 5f0d558820..9589c06a1e 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -4,6 +4,7 @@ "%1$d Person" "%1$d Personen" + "Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." "Thema hinzufügen" "Bereits Mitglied" "Bereits eingeladen" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index f899a58873..d2b7f22292 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -4,6 +4,7 @@ "%1$d personne" "%1$d personnes" + "Une erreur s’est produite lors de la mise à jour du paramètre de notification." "Ajouter un sujet" "Déjà membre" "Déjà invité(e)" diff --git a/features/roomdetails/impl/src/main/res/values-ro/translations.xml b/features/roomdetails/impl/src/main/res/values-ro/translations.xml index 958058432a..1a78709241 100644 --- a/features/roomdetails/impl/src/main/res/values-ro/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ro/translations.xml @@ -4,6 +4,7 @@ "o persoană" "%1$d persoane" + "A apărut o eroare în timpul actualizării setărilor pentru notificari." "Adăugare subiect" "Deja membru" "Deja invitat" diff --git a/features/roomdetails/impl/src/main/res/values-ru/translations.xml b/features/roomdetails/impl/src/main/res/values-ru/translations.xml index 920fb1a2f8..7d3eae6fff 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -5,6 +5,7 @@ "%1$d пользователя" "%1$d пользователей" + "При обновлении настроек уведомления произошла ошибка." "Добавить тему" "Уже зарегистрирован" "Уже приглашены" @@ -29,12 +30,13 @@ "Включение этого параметра отменяет настройки по умолчанию" "Уведомить меня в этом чате" "Вы можете изменить его в своем %1$s." - "Основные Настройки" + "основные настройки" "Настройка по умолчанию" "Удалить пользовательскую настройку" "Произошла ошибка при загрузке настроек уведомлений." "Не удалось восстановить режим по умолчанию, попробуйте еще раз." "Не удалось настроить режим, попробуйте еще раз." + "Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, вы не будете получать уведомления в этой комнате." "Все сообщения" "В этой комнате уведомить меня о" "Заблокировать" 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 0cdd7b3b0c..c7e422b920 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -5,6 +5,7 @@ "%1$d osoby" "%1$d osôb" + "Pri aktualizácii nastavenia oznámenia došlo k chybe." "Pridať tému" "Už ste členom" "Už ste pozvaní" @@ -35,6 +36,7 @@ "Pri načítavaní nastavení oznámení došlo k chybe." "Nepodarilo sa obnoviť predvolený režim, skúste to prosím znova." "Nepodarilo sa nastaviť režim, skúste to prosím znova." + "Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v tejto miestnosti nedostanete upozornenie." "Všetky správy" "V tejto miestnosti ma upozorniť na" "Zablokovať" diff --git a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml index 71ed1df861..50202c0e81 100644 --- a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml @@ -3,6 +3,7 @@ "%1$d 位夥伴" + "更新通知設定時發生錯誤。" "新增主題" "已是成員" "已邀請" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 833b06ca40..5363c6a78e 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -4,6 +4,7 @@ "%1$d person" "%1$d people" + "An error occurred while updating the notification setting." "Add topic" "Already a member" "Already invited" @@ -34,6 +35,7 @@ "An error occurred while loading notification settings." "Failed restoring the default mode, please try again." "Failed setting the mode, please try again." + "Your homeserver does not support this option in encrypted rooms, you won\'t get notified in this room." "All messages" "In this room, notify me for" "Block" diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 24f1a42883..863b7c80be 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -18,20 +18,18 @@ package io.element.android.features.roomlist.impl import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables @@ -88,34 +86,25 @@ private fun RoomListModalBottomSheetContent( ) }, modifier = Modifier.clickable { onRoomSettingsClicked(contextMenu.roomId) }, - leadingContent = { - Icon( - resourceId = CommonDrawables.ic_compound_settings, - contentDescription = stringResource(id = CommonStrings.common_settings), - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.onSurface, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Resource( + CommonDrawables.ic_compound_settings, + contentDescription = stringResource(id = CommonStrings.common_settings) ) - } + ), + style = ListItemStyle.Primary, ) ListItem( - headlineContent = { - Text( - text = stringResource(id = CommonStrings.action_leave_room), - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodyLarge, - ) - }, + headlineContent = { Text(text = stringResource(id = CommonStrings.action_leave_room)) }, modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) }, - leadingContent = { - Icon( - resourceId = CommonDrawables.ic_compound_leave, - contentDescription = stringResource(id = CommonStrings.action_leave_room), - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.error, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Resource( + CommonDrawables.ic_compound_leave, + contentDescription = stringResource(id = CommonStrings.action_leave_room) ) - } + ), + style = ListItemStyle.Destructive, ) - Spacer(modifier = Modifier.height(32.dp)) } } 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 afb110e4de..42192a3376 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 @@ -243,7 +243,7 @@ private fun RoomListContent( ) { Icon( // Note cannot use Icons.Outlined.EditSquare, it does not exist :/ - resourceId = CommonDrawables.ic_september_compose_button, + resourceId = CommonDrawables.ic_new_message, contentDescription = stringResource(id = R.string.screen_roomlist_a11y_create_message) ) } 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 57bfccabb7..2689aac427 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 @@ -253,7 +253,7 @@ private fun DefaultRoomListTopBar( showMenu = false onMenuActionClicked(RoomListMenuAction.ReportBug) }, - text = { Text(stringResource(id = CommonStrings.common_report_a_bug)) }, + text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) }, leadingIcon = { Icon( resourceId = CommonDrawables.ic_compound_chat_problem, 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 2016cf5a6b..a1fa410c7f 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 @@ -36,7 +36,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow @@ -47,8 +46,8 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvid import io.element.android.libraries.core.extensions.orEmpty import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom import io.element.android.libraries.designsystem.components.avatar.Avatar -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.roomListRoomMessage @@ -58,7 +57,6 @@ import io.element.android.libraries.designsystem.theme.unreadIndicator import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.theme.ElementTheme -import io.element.android.libraries.ui.strings.CommonStrings internal val minHeight = 84.dp @@ -172,14 +170,22 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { // Unread Row( + modifier = Modifier.height(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { + // Video call + if (room.hasOngoingCall) { + Icon( + modifier = Modifier.size(16.dp), + resourceId = CommonDrawables.ic_compound_video_call, + contentDescription = null, + tint = ElementTheme.colors.unreadIndicator, + ) + } NotificationIcon(room) if (room.hasUnread) { - UnreadIndicatorAtom( - modifier = Modifier.padding(vertical = 3.dp), - ) + UnreadIndicatorAtom() } } } @@ -192,14 +198,14 @@ private fun NotificationIcon(room: RoomListRoomSummary) { RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> Icon( modifier = Modifier.size(16.dp), - contentDescription = stringResource(CommonStrings.screen_notification_settings_mode_mentions), + contentDescription = null, imageVector = ImageVector.vectorResource(CommonDrawables.ic_compound_mention), tint = tint, ) RoomNotificationMode.MUTE -> Icon( modifier = Modifier.size(16.dp), - contentDescription = stringResource(CommonStrings.common_mute), + contentDescription = null, imageVector = ImageVector.vectorResource(CommonDrawables.ic_compound_notifications_solid_off), tint = tint, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index 6a9c152af6..b8af0a60cc 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -161,6 +161,7 @@ class RoomListDataSource @Inject constructor( }.orEmpty(), avatarData = avatarData, notificationMode = roomSummary.details.notificationMode, + hasOngoingCall = roomSummary.details.hasOngoingCall, ) } null -> null 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 bb3405c2d3..3a445e6cdc 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 @@ -33,4 +33,5 @@ data class RoomListRoomSummary constructor( val avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem), val isPlaceholder: Boolean = false, val notificationMode: RoomNotificationMode? = null, + val hasOngoingCall: Boolean = false, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt index f2db8a376c..2649704427 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt @@ -40,6 +40,7 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider> = remember { mutableStateOf(Async.Uninitialized) } + + LaunchedEffect(backupState) { + if (backupState == BackupState.UNKNOWN) { + getKeyBackupStatus(doesBackupExistOnServerAction) + } + } + + fun handleEvents(event: SecureBackupRootEvents) { + when (event) { + SecureBackupRootEvents.RetryKeyBackupState -> localCoroutineScope.getKeyBackupStatus(doesBackupExistOnServerAction) + } + } + return SecureBackupRootState( backupState = backupState, + doesBackupExistOnServer = doesBackupExistOnServerAction.value, recoveryState = recoveryState, appName = buildMeta.applicationName, snackbarMessage = snackbarMessage, + eventSink = ::handleEvents, ) } + + private fun CoroutineScope.getKeyBackupStatus(action: MutableState>) = launch { + suspend { + encryptionService.doesBackupExistOnServer().getOrThrow() + }.runCatchingUpdatingState(action) + } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt index 1eacd9e81a..e2c6c1154c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt @@ -16,13 +16,16 @@ package io.element.android.features.securebackup.impl.root +import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState data class SecureBackupRootState( val backupState: BackupState, + val doesBackupExistOnServer: Async, val recoveryState: RecoveryState, val appName: String, val snackbarMessage: SnackbarMessage?, + val eventSink: (SecureBackupRootEvents) -> Unit, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt index ee15a40f50..ae8b2aa63b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.securebackup.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState @@ -24,9 +25,11 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState open class SecureBackupRootStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aSecureBackupRootState(backupState = BackupState.UNKNOWN), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Uninitialized), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Success(true)), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Success(false)), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Failure(Exception("An error"))), aSecureBackupRootState(backupState = BackupState.ENABLED), - aSecureBackupRootState(backupState = BackupState.DISABLED), aSecureBackupRootState(recoveryState = RecoveryState.UNKNOWN), aSecureBackupRootState(recoveryState = RecoveryState.ENABLED), aSecureBackupRootState(recoveryState = RecoveryState.DISABLED), @@ -37,11 +40,14 @@ open class SecureBackupRootStateProvider : PreviewParameterProvider = Async.Uninitialized, recoveryState: RecoveryState = RecoveryState.UNKNOWN, snackbarMessage: SnackbarMessage? = null, ) = SecureBackupRootState( backupState = backupState, + doesBackupExistOnServer = doesBackupExistOnServer, recoveryState = recoveryState, appName = "Element", snackbarMessage = snackbarMessage, + eventSink = {}, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt index 9c87216b3d..3a27ae553e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt @@ -16,18 +16,27 @@ package io.element.android.features.securebackup.impl.root +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.securebackup.impl.R +import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.components.async.AsyncLoading +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton 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.encryption.BackupState @@ -70,13 +79,58 @@ fun SecureBackupRootView( // Disable / Enable backup when (state.backupState) { - BackupState.WAITING_FOR_SYNC, - BackupState.UNKNOWN -> Unit - BackupState.DISABLED -> { - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), - onClick = onEnableClicked, - ) + BackupState.WAITING_FOR_SYNC -> Unit + BackupState.UNKNOWN -> { + when (state.doesBackupExistOnServer) { + is Async.Success -> when (state.doesBackupExistOnServer.data) { + true -> { + PreferenceText( + title = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable), + tintColor = ElementTheme.colors.textCriticalPrimary, + onClick = onDisableClicked, + ) + } + false -> { + PreferenceText( + title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), + onClick = onEnableClicked, + ) + } + } + is Async.Loading, + Async.Uninitialized -> { + ListItem(headlineContent = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + CircularProgressIndicator() + } + }) + } + is Async.Failure -> { + ListItem( + headlineContent = { + Text( + text = stringResource(id = CommonStrings.error_unknown), + ) + }, + trailingContent = ListItemContent.Custom { + TextButton( + text = stringResource( + id = CommonStrings.action_retry + ), + onClick = { state.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) } + ) + } + ) + + PreferenceText( + title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), + onClick = onEnableClicked, + ) + } + } } BackupState.CREATING, BackupState.ENABLING, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index bdf0509560..7b722dd32b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -114,7 +114,7 @@ private fun RecoveryKeyStaticContent( modifier = Modifier.weight(1f), ) Icon( - resourceId = CommonDrawables.ic_september_copy, + resourceId = CommonDrawables.ic_copy, contentDescription = stringResource(id = CommonStrings.action_copy), tint = ElementTheme.colors.iconSecondary, ) 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 5ae1d4b0ec..a34d77aeee 100644 --- a/features/securebackup/impl/src/main/res/values-cs/translations.xml +++ b/features/securebackup/impl/src/main/res/values-cs/translations.xml @@ -22,6 +22,8 @@ "Klíč pro obnovení byl změněn" "Změnit klíč pro obnovení?" "Zadejte klíč pro obnovení a potvrďte přístup k záloze chatu." + "Zkuste prosím znovu potvrdit přístup k záloze chatu." + "Nesprávný klíč pro obnovení" "Zadejte kód o délce 48 znaků." "Zadejte…" "Klíč pro obnovení potvrzen" diff --git a/features/securebackup/impl/src/main/res/values-ru/translations.xml b/features/securebackup/impl/src/main/res/values-ru/translations.xml index 34a117c53b..fd2bb28208 100644 --- a/features/securebackup/impl/src/main/res/values-ru/translations.xml +++ b/features/securebackup/impl/src/main/res/values-ru/translations.xml @@ -22,7 +22,9 @@ "Ключ восстановления изменен" "Изменить ключ восстановления?" "Введите ключ восстановления, чтобы подтвердить доступ к резервной копии чата." - "Введите 48-значный код." + "Пожалуйста, попробуйте еще раз, чтобы подтвердить доступ к резервной копии чата." + "Неверный ключ восстановления" + "Введите 48 значный код." "Вход…" "Ключ восстановления подтвержден" "Подтвердите ключ восстановления" diff --git a/features/securebackup/impl/src/main/res/values-sk/translations.xml b/features/securebackup/impl/src/main/res/values-sk/translations.xml index 7c702d0be2..ffc7e78c87 100644 --- a/features/securebackup/impl/src/main/res/values-sk/translations.xml +++ b/features/securebackup/impl/src/main/res/values-sk/translations.xml @@ -22,6 +22,8 @@ "Kľúč na obnovenie bol zmenený" "Zmeniť kľúč na obnovenie?" "Zadajte kľúč na obnovenie a potvrďte prístup k zálohe konverzácie." + "Skúste prosím znova potvrdiť prístup k vašej zálohe konverzácie." + "Nesprávny kľúč na obnovenie" "Zadajte 48-znakový kód." "Zadať…" "Kľúč na obnovu potvrdený" diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt index 93031b6d2a..6b88e20748 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt @@ -20,9 +20,12 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.api.encryption.RecoveryState +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.tests.testutils.WarmUpRule @@ -40,9 +43,39 @@ class SecureBackupRootPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(2) val initialState = awaitItem() assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN) + assertThat(initialState.doesBackupExistOnServer.dataOrNull()).isTrue() + assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN) assertThat(initialState.appName).isEqualTo("Element") + assertThat(initialState.snackbarMessage).isNull() + } + } + + @Test + fun `present - Unknown state`() = runTest { + val encryptionService = FakeEncryptionService() + val presenter = createSecureBackupRootPresenter( + encryptionService = encryptionService, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + encryptionService.givenDoesBackupExistOnServerResult(Result.failure(AN_EXCEPTION)) + assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN) + assertThat(initialState.doesBackupExistOnServer).isEqualTo(Async.Uninitialized) + val loadingState1 = awaitItem() + assertThat(loadingState1.doesBackupExistOnServer).isInstanceOf(Async.Loading::class.java) + val errorState = awaitItem() + assertThat(errorState.doesBackupExistOnServer).isEqualTo(Async.Failure(AN_EXCEPTION)) + encryptionService.givenDoesBackupExistOnServerResult(Result.success(false)) + errorState.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) + val loadingState2 = awaitItem() + assertThat(loadingState2.doesBackupExistOnServer).isInstanceOf(Async.Loading::class.java) + val finalState = awaitItem() + assertThat(finalState.doesBackupExistOnServer.dataOrNull()).isFalse() } } 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 83e2c9316f..60e9cf17ce 100644 --- a/features/verifysession/impl/src/main/res/values-cs/translations.xml +++ b/features/verifysession/impl/src/main/res/values-cs/translations.xml @@ -9,6 +9,7 @@ "Opakovat ověření" "Jsem připraven" "Čekání na shodu" + "Porovnejte jedinečnou sadu emotikonů." "Porovnejte jedinečné emotikony a ujistěte se, že jsou zobrazeny ve stejném pořadí." "Neshodují se" "Shodují se" diff --git a/features/verifysession/impl/src/main/res/values-ru/translations.xml b/features/verifysession/impl/src/main/res/values-ru/translations.xml index 2de79c6ac2..564f839591 100644 --- a/features/verifysession/impl/src/main/res/values-ru/translations.xml +++ b/features/verifysession/impl/src/main/res/values-ru/translations.xml @@ -2,13 +2,14 @@ "Кажется, что-то не так. Время ожидания запроса истекло, либо запрос был отклонен." "Убедитесь, что приведенные ниже смайлики совпадают со смайликами, показанными во время другого сеанса." - "Сравните смайлики" + "Сравните емодзи" "Ваш новый сеанс подтвержден. У него есть доступ к вашим зашифрованным сообщениям, и другие пользователи увидят его как доверенное." "Чтобы получить доступ к зашифрованной истории сообщений, докажите, что это вы." "Открыть существующий сеанс" "Повторить проверку" "Я готов" "Ожидание соответствия" + "Сравните уникальный набор эмодзи." "Сравните уникальные смайлики, убедившись, что они расположены в том же порядке." "Они не совпадают" "Они совпадают" diff --git a/features/verifysession/impl/src/main/res/values-sk/translations.xml b/features/verifysession/impl/src/main/res/values-sk/translations.xml index c00a995266..4b13701466 100644 --- a/features/verifysession/impl/src/main/res/values-sk/translations.xml +++ b/features/verifysession/impl/src/main/res/values-sk/translations.xml @@ -9,6 +9,7 @@ "Zopakovať overenie" "Som pripravený/á" "Čaká sa na zhodu" + "Porovnajte jedinečnú sadu emotikonov." "Porovnajte jedinečné emotikony a uistite sa, že sú zobrazené v rovnakom poradí." "Nezhodujú sa" "Zhodujú sa" diff --git a/gradle.properties b/gradle.properties index cf609500c2..0ece8f4e8c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,7 +49,7 @@ signing.element.nightly.keyPassword=Secret # Customise the Lint version to use a more recent version than the one bundled with AGP # https://googlesamples.github.io/android-custom-lint-rules/usage/newer-lint.md.html -android.experimental.lint.version=8.3.0-alpha11 +android.experimental.lint.version=8.3.0-alpha12 # Enable test fixture for all modules by default android.experimental.enableTestFixtures=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b9b775b1a3..7f13c696e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,22 +3,19 @@ [versions] # Project -android_gradle_plugin = "8.1.3" +android_gradle_plugin = "8.1.4" kotlin = "1.9.20" ksp = "1.9.20-1.0.14" -molecule = "1.3.0" +firebaseAppDistribution = "4.0.1" # AndroidX core = "1.12.0" datastore = "1.0.0" constraintlayout = "2.1.4" constraintlayout_compose = "1.0.1" -recyclerview = "1.3.2" -lifecycle = "2.7.0-beta01" -activity = "1.8.0" -startup = "1.1.1" -media3 = "1.1.1" -browser = "1.6.0" +lifecycle = "2.7.0-rc01" +activity = "1.8.1" +media3 = "1.2.0" # Compose compose_bom = "2023.10.01" @@ -36,16 +33,11 @@ test_core = "1.5.0" #other coil = "2.5.0" datetime = "0.4.1" -serialization_json = "1.6.0" +serialization_json = "1.6.1" showkase = "1.0.2" -jsoup = "1.16.2" appyx = "1.4.0" -dependencycheck = "8.4.2" -dependencyanalysis = "1.25.0" -stem = "2.3.0" sqldelight = "2.0.0" -telephoto = "0.6.2" -wysiwyg = "2.16.0" +wysiwyg = "2.18.0" # DI dagger = "2.48.1" @@ -55,12 +47,9 @@ anvil = "2.4.8-1-8" autoservice = "1.1.1" # quality -detekt = "1.23.3" -dependencygraph = "0.12" junit = "4.13.2" androidx-test-ext-junit = "1.1.5" espresso-core = "3.5.1" -appcompat = "1.6.1" [libraries] # Project @@ -68,21 +57,23 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref # https://developer.android.com/studio/write/java8-support#library-desugaring-versions android_desugar = "com.android.tools:desugar_jdk_libs:2.0.4" kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +gms_google_services = "com.google.gms:google-services:4.4.0" # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:32.5.0" +google_firebase_bom = "com.google.firebase:firebase-bom:32.6.0" +firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } # AndroidX androidx_core = { module = "androidx.core:core", version.ref = "core" } androidx_corektx = { module = "androidx.core:core-ktx", version.ref = "core" } -androidx_annotationjvm = { module = "androidx.annotation:annotation-jvm", version = "1.7.0" } +androidx_annotationjvm = "androidx.annotation:annotation-jvm:1.7.0" androidx_datastore_preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } androidx_datastore_datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx_exifinterface = "androidx.exifinterface:exifinterface:1.3.6" androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayout_compose" } -androidx_recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } -androidx_browser = { module = "androidx.browser:browser", version.ref = "browser" } +androidx_recyclerview = "androidx.recyclerview:recyclerview:1.3.2" +androidx_browser = "androidx.browser:browser:1.7.0" androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } androidx_splash = "androidx.core:core-splashscreen:1.0.1" @@ -93,12 +84,18 @@ androidx_biometric = "androidx.biometric:biometric-ktx:1.2.0-alpha05" androidx_activity_activity = { module = "androidx.activity:activity", version.ref = "activity" } androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } -androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = "startup" } +androidx_startup = "androidx.startup:startup-runtime:1.1.1" androidx_preference = "androidx.preference:preference:1.2.1" androidx_webkit = "androidx.webkit:webkit:1.8.0" -androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha10" +androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } +androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha11" +androidx_compose_ui = { module = "androidx.compose.ui:ui" } +androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" } +androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx_compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest" } +androidx_compose_material = { module = "androidx.compose.material:material" } +androidx_compose_material_icons = { module = "androidx.compose.material:material-icons-extended" } # Coroutines coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } @@ -114,6 +111,7 @@ squareup_seismic = "com.squareup:seismic:1.0.3" # network network_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:4.12.0" network_okhttp_logging = { module = "com.squareup.okhttp3:logging-interceptor" } +network_okhttp_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_retrofit = "com.squareup.retrofit2:retrofit:2.9.0" network_retrofit_converter_serialization = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" @@ -124,13 +122,9 @@ test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" } test_arch_core = "androidx.arch.core:core-testing:2.2.0" test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.5.2" -test_uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0" test_junitext = "androidx.test.ext:junit:1.1.5" test_mockk = "io.mockk:mockk:1.13.8" -test_barista = "com.adevinta.android:barista:4.3.0" test_konsist = "com.lemonappdev:konsist:0.13.0" -test_hamcrest = "org.hamcrest:hamcrest:2.2" -test_orchestrator = "androidx.test:orchestrator:1.4.2" test_turbine = "app.cash.turbine:turbine:1.0.0" test_truth = "com.google.truth:truth:1.1.5" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.14" @@ -143,13 +137,14 @@ coil_compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil_gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } 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.6" showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" } showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" } -jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } +jsoup = "org.jsoup:jsoup:1.16.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } -molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } +molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.67" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.68" 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" } @@ -160,16 +155,17 @@ sqlite = "androidx.sqlite:sqlite-ktx:2.4.0" unifiedpush = "com.github.UnifiedPush:android-connector:2.1.1" otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5" vanniktech_blurhash = "com.vanniktech:blurhash:0.1.0" -telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } +telephoto_zoomableimage = "me.saket.telephoto:zoomable-image-coil:0.7.1" statemachine = "com.freeletics.flowredux:compose:1.2.0" maplibre = "org.maplibre.gl:android-sdk:10.2.0" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.2" opusencoder = "io.element.android:opusencoder:1.1.0" +kotlinpoet = "com.squareup:kotlinpoet:1.15.1" # Analytics posthog = "com.posthog.android:posthog:2.0.3" -sentry = "io.sentry:sentry-android:6.33.1" +sentry = "io.sentry:sentry-android:6.34.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:aa14cbcdf81af2746d20a71779ec751f971e1d7f" # Emojibase @@ -194,7 +190,7 @@ android_composeCompiler = { module = "androidx.compose.compiler:compiler", versi junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } -appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +appcompat = "androidx.appcompat:appcompat:1.6.1" [bundles] @@ -207,14 +203,16 @@ kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } anvil = { id = "com.squareup.anvil", version.ref = "anvil" } -detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +detekt = "io.gitlab.arturbosch.detekt:1.23.3" ktlint = "org.jlleitschuh.gradle.ktlint:11.6.1" -dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version.ref = "dependencygraph" } -dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" } -dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyanalysis" } +dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" +dependencycheck = "org.owasp.dependencycheck:8.4.3" +dependencyanalysis = "com.autonomousapps.dependency-analysis:1.26.0" paparazzi = "app.cash.paparazzi:1.3.1" kover = "org.jetbrains.kotlinx.kover:0.6.1" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } +firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } +knit = { id = "org.jetbrains.kotlinx.knit", version = "0.4.0" } # Version '4.3.1.3277' introduced some regressions in CI time (more than 2x slower), so make sure # this is no longer the case before upgrading. diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt new file mode 100644 index 0000000000..d3e2cc3e78 --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.data + +/** + * Returns a list containing first [count] elements matching the given [predicate]. + * If the list contains less elements matching the [predicate], then all of them are returned. + * + * @param T the type of elements contained in the list. + * @param count the maximum number of elements to take. + * @param predicate the predicate used to match elements. + * @return a list containing first [count] elements matching the given [predicate]. + */ +inline fun Iterable.filterUpTo(count: Int, predicate: (T) -> Boolean): List { + val result = mutableListOf() + for (element in this) { + if (predicate(element)) { + result.add(element) + if (result.size == count) { + break + } + } + } + return result +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt index 9ba984f565..7bcb8bf1dd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt @@ -58,3 +58,7 @@ data class AvatarData( .uppercase() } } + +fun AvatarData.getBestName(): String { + return name?.takeIf { it.isNotEmpty() } ?: id +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index b2004ed204..0b4264e8e6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -37,6 +37,9 @@ enum class AvatarSize(val dp: Dp) { TimelineRoom(32.dp), TimelineSender(32.dp), + TimelineReadReceipt(16.dp), + + ReadReceiptList(32.dp), MessageActionSender(32.dp), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt index 801575fe56..aed618cd3b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt @@ -16,7 +16,7 @@ package io.element.android.libraries.designsystem.components.dialogs -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -43,7 +43,7 @@ fun ConfirmationDialog( onCancelClicked: () -> Unit = onDismiss, onThirdButtonClicked: () -> Unit = {}, ) { - AlertDialog(modifier = modifier, onDismissRequest = onDismiss) { + BasicAlertDialog(modifier = modifier, onDismissRequest = onDismiss) { ConfirmationDialogContent( title = title, content = content, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt index 9b080f5403..52779052a6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt @@ -16,7 +16,7 @@ package io.element.android.libraries.designsystem.components.dialogs -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -37,7 +37,7 @@ fun ErrorDialog( title: String = ErrorDialogDefaults.title, submitText: String = ErrorDialogDefaults.submitText, ) { - AlertDialog(modifier = modifier, onDismissRequest = onDismiss) { + BasicAlertDialog(modifier = modifier, onDismissRequest = onDismiss) { ErrorDialogContent( title = title, content = content, 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 5ad13d6a13..83079584c3 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 @@ -19,7 +19,7 @@ package io.element.android.libraries.designsystem.components.dialogs import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -56,7 +56,7 @@ fun ListDialog( ) } } - AlertDialog( + BasicAlertDialog( modifier = modifier, onDismissRequest = onDismissRequest, ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt index 5ed79f2c35..055843edae 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt @@ -19,7 +19,7 @@ package io.element.android.libraries.designsystem.components.dialogs import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -60,7 +60,7 @@ fun MultipleSelectionDialog( ) } } - AlertDialog( + BasicAlertDialog( modifier = modifier, onDismissRequest = onDismissRequest, ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt index 85447b940b..1837041e64 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt @@ -16,7 +16,7 @@ package io.element.android.libraries.designsystem.components.dialogs -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -39,7 +39,7 @@ fun RetryDialog( onRetry: () -> Unit = {}, onDismiss: () -> Unit = {}, ) { - AlertDialog(modifier = modifier, onDismissRequest = onDismiss) { + BasicAlertDialog(modifier = modifier, onDismissRequest = onDismiss) { RetryDialogContent( title = title, content = content, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt index 82b88bc0a1..36869bf270 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt @@ -19,7 +19,7 @@ package io.element.android.libraries.designsystem.components.dialogs import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -57,7 +57,7 @@ fun SingleSelectionDialog( ) } } - AlertDialog( + BasicAlertDialog( modifier = modifier, onDismissRequest = onDismissRequest, ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt index 8074d42056..afde4eaa4e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt @@ -16,10 +16,12 @@ package io.element.android.libraries.designsystem.components.list +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.DpSize @@ -126,7 +128,12 @@ sealed interface ListItemContent { ) } is Text -> TextComponent(modifier = Modifier.widthIn(max = 128.dp), text = text, maxLines = 1, overflow = TextOverflow.Ellipsis) - is Badge -> RedIndicatorAtom() + is Badge -> Box( + modifier = Modifier.size(maxCompactSize), + contentAlignment = Alignment.Center, + ) { + RedIndicatorAtom() + } is Custom -> content() } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt index 589f1fddcd..fc11758104 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt @@ -16,12 +16,14 @@ package io.element.android.libraries.designsystem.components.tooltip +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TooltipState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.window.PopupPositionProvider import androidx.compose.material3.TooltipBox as M3TooltipBox +@OptIn(ExperimentalMaterial3Api::class) @Composable fun TooltipBox( positionProvider: PopupPositionProvider, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt index 3faa030daf..8dee3921ac 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt @@ -96,43 +96,43 @@ internal val iconsCompound = listOf( R.drawable.ic_compound_web_browser, ) -internal val iconsSeptember = listOf( - R.drawable.ic_september_add_reaction, - R.drawable.ic_september_attachment, - R.drawable.ic_september_compose_button, - R.drawable.ic_september_copy, - R.drawable.ic_september_decryption_error, - R.drawable.ic_september_edit_outline, - R.drawable.ic_september_edit_solid_16, - R.drawable.ic_september_forward, - R.drawable.ic_september_location, - R.drawable.ic_september_photo_camera, - R.drawable.ic_september_photo_video_library, - R.drawable.ic_september_reply, - R.drawable.ic_september_send, - R.drawable.ic_september_take_photo_camera, - R.drawable.ic_september_text_formatting, - R.drawable.ic_september_video_call, - R.drawable.ic_september_view_source, -) - // This list and all the drawable it contains should be removed at some point. // All the icons should be defined in Compound. internal val iconsOther = listOf( + R.drawable.ic_add_reaction, + R.drawable.ic_attachment, + R.drawable.ic_copy, R.drawable.ic_developer_options, R.drawable.ic_devices, + R.drawable.ic_edit, + R.drawable.ic_edit_outline, + R.drawable.ic_edit_solid, + R.drawable.ic_encryption_enabled, + R.drawable.ic_forward, R.drawable.ic_groups, + R.drawable.ic_image, R.drawable.ic_indent_decrease, R.drawable.ic_indent_increase, R.drawable.ic_inline_code, R.drawable.ic_italic, R.drawable.ic_link, + R.drawable.ic_location_navigator, + R.drawable.ic_location_navigator_centered, + R.drawable.ic_new_message, R.drawable.ic_numbered_list, R.drawable.ic_plus, R.drawable.ic_poll_end, R.drawable.ic_quote, + R.drawable.ic_reply, + R.drawable.ic_send, + R.drawable.ic_sign_out, R.drawable.ic_strikethrough, + R.drawable.ic_take_photo_camera, + R.drawable.ic_text_formatting, R.drawable.ic_thread_decoration, R.drawable.ic_underline, - R.drawable.ic_sign_out, + R.drawable.ic_user, + R.drawable.ic_user_add, + R.drawable.ic_video_call, + R.drawable.ic_waiting_to_decrypt, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt index 1963e8e994..1e67f097e4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt @@ -38,7 +38,7 @@ import io.element.android.libraries.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList -internal class IconChunkPreviewProvider : PreviewParameterProvider { +internal class CompoundIconListPreviewProvider : PreviewParameterProvider { override val values: Sequence get() { val chunks = iconsCompound.chunked(36) @@ -49,6 +49,17 @@ internal class IconChunkPreviewProvider : PreviewParameterProvider { } } +internal class OtherIconListPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() { + val chunks = iconsOther.chunked(36) + return chunks.mapIndexed { index, chunk -> + IconChunk(index = index+1, total = chunks.size, icons = chunk.toPersistentList()) + } + .asSequence() + } +} + internal data class IconChunk( val index: Int, val total: Int, @@ -57,7 +68,7 @@ internal data class IconChunk( @PreviewsDayNight @Composable -internal fun IconsCompoundPreview(@PreviewParameter(IconChunkPreviewProvider::class) chunk: IconChunk) = ElementPreview { +internal fun IconsCompoundPreview(@PreviewParameter(CompoundIconListPreviewProvider::class) chunk: IconChunk) = ElementPreview { IconsPreview( title = "R.drawable.ic_compound_* ${chunk.index}/${chunk.total}", iconsList = chunk.icons, @@ -69,22 +80,10 @@ internal fun IconsCompoundPreview(@PreviewParameter(IconChunkPreviewProvider::cl @PreviewsDayNight @Composable -internal fun IconsSeptemberPreview() = ElementPreview { +internal fun IconsOtherPreview(@PreviewParameter(OtherIconListPreviewProvider::class) iconChunk: IconChunk) = ElementPreview { IconsPreview( - title = "R.drawable.ic_september_*", - iconsList = iconsSeptember.toPersistentList(), - iconNameTransform = { name -> - name.removePrefix("ic_september_") - .replace("_", " ") - }) -} - -@PreviewsDayNight -@Composable -internal fun IconsOtherPreview() = ElementPreview { - IconsPreview( - title = "R.drawable.ic_*", - iconsList = iconsOther.toPersistentList(), + title = "R.drawable.ic_* ${iconChunk.index}/${iconChunk.total}", + iconsList = iconChunk.icons, iconNameTransform = { name -> name.removePrefix("ic_") .replace("_", " ") 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 e85abb396b..c860239d6a 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 @@ -108,6 +108,36 @@ val SemanticColors.pinDigitBg Color(0xFF26282D) } +val SemanticColors.currentUserMentionPillText + get() = if (isLight) { + // We want LightDesignTokens.colorGreen1100 + Color(0xff005c45) + } else { + // We want DarkDesignTokens.colorGreen1100 + Color(0xff1fc090) + } + +val SemanticColors.currentUserMentionPillBackground + get() = if (isLight) { + // We want LightDesignTokens.colorGreenAlpha400 + Color(0x3b07b661) + } else { + // We want DarkDesignTokens.colorGreenAlpha500 + Color(0xff003d29) + } + +val SemanticColors.mentionPillText + get() = textPrimary + +val SemanticColors.mentionPillBackground + get() = if (isLight) { + // We want LightDesignTokens.colorGray400 + Color(0x1f052e61) + } else { + // We want DarkDesignTokens.colorGray500 + Color(0x26f4f7fa) + } + @PreviewsDayNight @Composable internal fun ColorAliasesPreview() = ElementPreview { diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_add_reaction.xml b/libraries/designsystem/src/main/res/drawable/ic_add_reaction.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_add_reaction.xml rename to libraries/designsystem/src/main/res/drawable/ic_add_reaction.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_attachment.xml b/libraries/designsystem/src/main/res/drawable/ic_attachment.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_attachment.xml rename to libraries/designsystem/src/main/res/drawable/ic_attachment.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_compound_chat_problem.xml b/libraries/designsystem/src/main/res/drawable/ic_compound_chat_problem.xml index 6bfcc9331c..4d16775c66 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_compound_chat_problem.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_compound_chat_problem.xml @@ -20,12 +20,12 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_photo_video_library.xml b/libraries/designsystem/src/main/res/drawable/ic_compound_plus.xml similarity index 58% rename from libraries/designsystem/src/main/res/drawable/ic_september_photo_video_library.xml rename to libraries/designsystem/src/main/res/drawable/ic_compound_plus.xml index 49d06f1a40..a56299f569 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_september_photo_video_library.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_compound_plus.xml @@ -20,6 +20,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_copy.xml b/libraries/designsystem/src/main/res/drawable/ic_copy.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_copy.xml rename to libraries/designsystem/src/main/res/drawable/ic_copy.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_edit.xml b/libraries/designsystem/src/main/res/drawable/ic_edit.xml new file mode 100644 index 0000000000..187e190046 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_edit_outline.xml b/libraries/designsystem/src/main/res/drawable/ic_edit_outline.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_edit_outline.xml rename to libraries/designsystem/src/main/res/drawable/ic_edit_outline.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_edit_solid_16.xml b/libraries/designsystem/src/main/res/drawable/ic_edit_solid.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_edit_solid_16.xml rename to libraries/designsystem/src/main/res/drawable/ic_edit_solid.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_encryption_enabled.xml b/libraries/designsystem/src/main/res/drawable/ic_encryption_enabled.xml new file mode 100644 index 0000000000..df9b6b2cad --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_encryption_enabled.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_forward.xml b/libraries/designsystem/src/main/res/drawable/ic_forward.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_forward.xml rename to libraries/designsystem/src/main/res/drawable/ic_forward.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_compose_button.xml b/libraries/designsystem/src/main/res/drawable/ic_image.xml similarity index 51% rename from libraries/designsystem/src/main/res/drawable/ic_september_compose_button.xml rename to libraries/designsystem/src/main/res/drawable/ic_image.xml index fa706f56c8..f2f41c7369 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_september_compose_button.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_image.xml @@ -20,6 +20,9 @@ android:viewportWidth="24" android:viewportHeight="24"> + diff --git a/libraries/designsystem/src/main/res/drawable/ic_location_navigator.xml b/libraries/designsystem/src/main/res/drawable/ic_location_navigator.xml new file mode 100644 index 0000000000..0d66e9c237 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_location_navigator.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_location_navigator_centered.xml b/libraries/designsystem/src/main/res/drawable/ic_location_navigator_centered.xml new file mode 100644 index 0000000000..374af787a9 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_location_navigator_centered.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_location.xml b/libraries/designsystem/src/main/res/drawable/ic_location_pin.xml similarity index 75% rename from libraries/designsystem/src/main/res/drawable/ic_september_location.xml rename to libraries/designsystem/src/main/res/drawable/ic_location_pin.xml index 7843eb22cc..6e55afca13 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_september_location.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_location_pin.xml @@ -20,6 +20,6 @@ android:viewportWidth="24" android:viewportHeight="24"> + android:pathData="M12,19.35C14.033,17.483 15.542,15.788 16.525,14.262C17.508,12.738 18,11.383 18,10.2C18,8.383 17.421,6.896 16.263,5.738C15.104,4.579 13.683,4 12,4C10.317,4 8.896,4.579 7.738,5.738C6.579,6.896 6,8.383 6,10.2C6,11.383 6.492,12.738 7.475,14.262C8.458,15.788 9.967,17.483 12,19.35ZM12,21.325C11.767,21.325 11.533,21.283 11.3,21.2C11.067,21.117 10.858,20.992 10.675,20.825C9.592,19.825 8.633,18.85 7.8,17.9C6.967,16.95 6.271,16.029 5.713,15.137C5.154,14.246 4.729,13.387 4.438,12.563C4.146,11.738 4,10.95 4,10.2C4,7.7 4.804,5.708 6.412,4.225C8.021,2.742 9.883,2 12,2C14.117,2 15.979,2.742 17.587,4.225C19.196,5.708 20,7.7 20,10.2C20,10.95 19.854,11.738 19.563,12.563C19.271,13.387 18.846,14.246 18.288,15.137C17.729,16.029 17.033,16.95 16.2,17.9C15.367,18.85 14.408,19.825 13.325,20.825C13.142,20.992 12.933,21.117 12.7,21.2C12.467,21.283 12.233,21.325 12,21.325ZM12,12C12.55,12 13.021,11.804 13.413,11.413C13.804,11.021 14,10.55 14,10C14,9.45 13.804,8.979 13.413,8.587C13.021,8.196 12.55,8 12,8C11.45,8 10.979,8.196 10.587,8.587C10.196,8.979 10,9.45 10,10C10,10.55 10.196,11.021 10.587,11.413C10.979,11.804 11.45,12 12,12Z" + android:fillColor="#1B1D22"/> diff --git a/libraries/designsystem/src/main/res/drawable/ic_lock_outline.xml b/libraries/designsystem/src/main/res/drawable/ic_lock_outline.xml index 51831bf3c1..349b532bd0 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_lock_outline.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_lock_outline.xml @@ -15,15 +15,11 @@ --> - - - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/libraries/designsystem/src/main/res/drawable/ic_new_message.xml b/libraries/designsystem/src/main/res/drawable/ic_new_message.xml new file mode 100644 index 0000000000..3e306ce7c5 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_new_message.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_pause.xml b/libraries/designsystem/src/main/res/drawable/ic_pause.xml similarity index 100% rename from libraries/textcomposer/impl/src/main/res/drawable/ic_pause.xml rename to libraries/designsystem/src/main/res/drawable/ic_pause.xml diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_play.xml b/libraries/designsystem/src/main/res/drawable/ic_play.xml similarity index 100% rename from libraries/textcomposer/impl/src/main/res/drawable/ic_play.xml rename to libraries/designsystem/src/main/res/drawable/ic_play.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_reply.xml b/libraries/designsystem/src/main/res/drawable/ic_reply.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_reply.xml rename to libraries/designsystem/src/main/res/drawable/ic_reply.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_send.xml b/libraries/designsystem/src/main/res/drawable/ic_send.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_send.xml rename to libraries/designsystem/src/main/res/drawable/ic_send.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_sending.xml b/libraries/designsystem/src/main/res/drawable/ic_sending.xml new file mode 100644 index 0000000000..92a8312f70 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_sending.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_sent.xml b/libraries/designsystem/src/main/res/drawable/ic_sent.xml new file mode 100644 index 0000000000..9a3ea31479 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_sent.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_decryption_error.xml b/libraries/designsystem/src/main/res/drawable/ic_september_decryption_error.xml deleted file mode 100644 index 4751e55ff0..0000000000 --- a/libraries/designsystem/src/main/res/drawable/ic_september_decryption_error.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_photo_camera.xml b/libraries/designsystem/src/main/res/drawable/ic_september_photo_camera.xml deleted file mode 100644 index 38d7f8af44..0000000000 --- a/libraries/designsystem/src/main/res/drawable/ic_september_photo_camera.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/libraries/designsystem/src/main/res/drawable/ic_stop.xml b/libraries/designsystem/src/main/res/drawable/ic_stop.xml new file mode 100644 index 0000000000..e4cd1507bb --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_stop.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_take_photo_camera.xml b/libraries/designsystem/src/main/res/drawable/ic_take_photo_camera.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_take_photo_camera.xml rename to libraries/designsystem/src/main/res/drawable/ic_take_photo_camera.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_text_formatting.xml b/libraries/designsystem/src/main/res/drawable/ic_text_formatting.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_text_formatting.xml rename to libraries/designsystem/src/main/res/drawable/ic_text_formatting.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_user.xml b/libraries/designsystem/src/main/res/drawable/ic_user.xml new file mode 100644 index 0000000000..25a1d604f2 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_user.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_user_add.xml b/libraries/designsystem/src/main/res/drawable/ic_user_add.xml new file mode 100644 index 0000000000..22f3f64a7e --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_user_add.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_video_call.xml b/libraries/designsystem/src/main/res/drawable/ic_video_call.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_september_video_call.xml rename to libraries/designsystem/src/main/res/drawable/ic_video_call.xml diff --git a/libraries/designsystem/src/main/res/drawable/ic_waiting_to_decrypt.xml b/libraries/designsystem/src/main/res/drawable/ic_waiting_to_decrypt.xml new file mode 100644 index 0000000000..7b4ea2ac6d --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_waiting_to_decrypt.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index b37dd538fd..9bb6d2f862 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -55,12 +55,6 @@ enum class FeatureFlags( description = "Allow user to lock/unlock the app with a pin code or biometrics", defaultValue = true, ), - InRoomCalls( - key = "feature.elementcall", - title = "Element call in rooms", - description = "Allow user to start or join a call in a room", - defaultValue = true, - ), Mentions( key = "feature.mentions", title = "Mentions", @@ -72,5 +66,11 @@ enum class FeatureFlags( title = "Chat backup", description = "Allow access to backup and restore chat history settings", defaultValue = false, - ) + ), + ReadReceipts( + key = "feature.readreceipts", + title = "Show read receipts", + description = null, + defaultValue = false, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 6c9f24c979..a1a2c3665c 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -39,9 +39,9 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.NotificationSettings -> true FeatureFlags.VoiceMessages -> true FeatureFlags.PinUnlock -> true - FeatureFlags.InRoomCalls -> true FeatureFlags.Mentions -> false FeatureFlags.SecureStorage -> false + FeatureFlags.ReadReceipts -> false } } else { false diff --git a/libraries/matrix/api/build.gradle.kts b/libraries/matrix/api/build.gradle.kts index 770f6459a0..f8a600df1e 100644 --- a/libraries/matrix/api/build.gradle.kts +++ b/libraries/matrix/api/build.gradle.kts @@ -18,7 +18,7 @@ plugins { id("io.element.android-compose-library") id("kotlin-parcelize") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.20" + alias(libs.plugins.kotlin.serialization) } android { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt index 98c45a1c9e..03f650e316 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt @@ -31,6 +31,5 @@ enum class BackupState { RESUMING, ENABLED, DOWNLOADING, - DISABLING, - DISABLED; + DISABLING; } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index 9652c53678..534f5b68e2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -40,6 +40,8 @@ interface EncryptionService { suspend fun disableRecovery(): Result + suspend fun doesBackupExistOnServer(): Result + /** * Note: accept bot recoveryKey and passphrase. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt index ba9cdc1e80..4a89d05276 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt @@ -133,13 +133,15 @@ object PermalinkParser { } private fun String.getViaParameters(): List { - return UrlQuerySanitizer(this) - .parameterList - .filter { - it.mParameter == "via" - } - .map { - URLDecoder.decode(it.mValue, "UTF-8") - } + return runCatching { + UrlQuerySanitizer(this) + .parameterList + .filter { + it.mParameter == "via" + } + .map { + URLDecoder.decode(it.mValue, "UTF-8") + } + }.getOrDefault(emptyList()) } } 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 1f2de7720f..e9ad35dafa 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 @@ -90,13 +90,13 @@ interface MatrixRoom : Closeable { suspend fun userAvatarUrl(userId: UserId): Result - suspend fun sendMessage(body: String, htmlBody: String?): Result + suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result - suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result + suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?, mentions: List): Result suspend fun enterSpecialMode(eventId: EventId?): Result - suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result + suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result suspend fun redactEvent(eventId: EventId, reason: String? = null): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt new file mode 100644 index 0000000000..30aba3c3c0 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room + +sealed interface Mention { + data class User(val userId: String): Mention + data object AtRoom: Mention +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt index 4638fc6e03..0a6aa7bbaa 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt @@ -44,4 +44,5 @@ data class RoomSummaryDetails( val unreadNotificationCount: Int, val inviter: RoomMember? = null, val notificationMode: RoomNotificationMode? = null, + val hasOngoingCall: Boolean = false, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt index af411216e5..5fa55c357f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt @@ -24,7 +24,8 @@ interface MatrixTimeline { data class PaginationState( val isBackPaginating: Boolean, - val hasMoreToLoadBackwards: Boolean + val hasMoreToLoadBackwards: Boolean, + val beginningOfRoomReached: Boolean, ) { val canBackPaginate = !isBackPaginating && hasMoreToLoadBackwards } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 49108f8d54..9f38ce9441 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -30,6 +30,7 @@ data class EventTimelineItem( val isRemote: Boolean, val localSendState: LocalEventSendState?, val reactions: List, + val receipts: List, val sender: UserId, val senderProfile: ProfileTimelineDetails, val timestamp: Long, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt new file mode 100644 index 0000000000..f638d71c89 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.timeline.item.event + +import io.element.android.libraries.matrix.api.core.UserId + +data class Receipt( + val userId: UserId, + val timestamp: Long, +) diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 35c68001f3..81b5f70a11 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -17,7 +17,7 @@ plugins { id("io.element.android-library") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.20" + alias(libs.plugins.kotlin.serialization) } android { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt index dbdd48da6c..ec7bc8e4d9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt @@ -29,7 +29,6 @@ class BackupStateMapper { RustBackupState.ENABLED -> BackupState.ENABLED RustBackupState.DOWNLOADING -> BackupState.DOWNLOADING RustBackupState.DISABLING -> BackupState.DISABLING - RustBackupState.DISABLED -> BackupState.DISABLED } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 11f9842d62..30b8c651a0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -130,6 +130,12 @@ internal class RustEncryptionService( } } + override suspend fun doesBackupExistOnServer(): Result = withContext(dispatchers.io) { + runCatching { + service.backupExistsOnServer() + } + } + override fun waitForBackupUploadSteadyState(): Flow { return callbackFlow { runCatching { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index 12a273f03c..fab0146f9d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -30,18 +30,17 @@ import java.io.File import org.matrix.rustcomponents.sdk.MediaSource as RustMediaSource class RustMediaLoader( - baseCacheDirectory: File, + private val baseCacheDirectory: File, dispatchers: CoroutineDispatchers, private val innerClient: Client, ) : MatrixMediaLoader { @OptIn(ExperimentalCoroutinesApi::class) private val mediaDispatcher = dispatchers.io.limitedParallelism(32) - private val cacheDirectory = File(baseCacheDirectory, "temp/media").apply { - if (!exists()) { - mkdirs() + private val cacheDirectory + get() = File(baseCacheDirectory, "temp/media").apply { + if (!exists()) mkdirs() // Must always ensure that this directory exists because "Clear cache" does not restart an app's process. } - } @OptIn(ExperimentalUnsignedTypes::class) override suspend fun loadMediaContent(source: MediaSource): Result = diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt new file mode 100644 index 0000000000..463496ffe7 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.room + +import io.element.android.libraries.matrix.api.room.Mention +import org.matrix.rustcomponents.sdk.Mentions + +fun List.map(): Mentions { + val hasAtRoom = any { it is Mention.AtRoom } + val userIds = filterIsInstance().map { it.userId } + return Mentions(userIds, hasAtRoom) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index d539ec6a53..2bfef368a9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -21,12 +21,11 @@ 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.room.ForwardEventException import io.element.android.libraries.matrix.impl.roomlist.roomOrNull +import io.element.android.libraries.matrix.impl.timeline.runWithTimelineListenerRegistered import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListService -import org.matrix.rustcomponents.sdk.TimelineDiff -import org.matrix.rustcomponents.sdk.TimelineListener import kotlin.time.Duration.Companion.milliseconds /** @@ -56,16 +55,14 @@ class RoomContentForwarder( val failedForwardingTo = mutableSetOf() targetRooms.parallelMap { room -> room.use { targetRoom -> - val result = runCatching { + runCatching { // Sending a message requires a registered timeline listener - targetRoom.addTimelineListener(NoOpTimelineListener) - withTimeout(timeoutMs.milliseconds) { - targetRoom.send(content) + targetRoom.runWithTimelineListenerRegistered { + withTimeout(timeoutMs.milliseconds) { + targetRoom.send(content) + } } } - // After sending, we remove the timeline - targetRoom.removeTimeline() - result }.onFailure { failedForwardingTo.add(RoomId(room.id())) if (it is CancellationException) { @@ -75,11 +72,7 @@ class RoomContentForwarder( } if (failedForwardingTo.isNotEmpty()) { - throw ForwardEventException(toRoomIds.toList()) + throw ForwardEventException(failedForwardingTo.toList()) } } - - private object NoOpTimelineListener : TimelineListener { - override fun onUpdate(diff: List) = Unit - } } 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 ab7317590e..35a61c1a1d 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 @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState +import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType @@ -250,22 +251,28 @@ class RustMatrixRoom( } } - override suspend fun sendMessage(body: String, htmlBody: String?): Result = withContext(roomDispatcher) { - messageEventContentFromParts(body, htmlBody).use { content -> + override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result = withContext(roomDispatcher) { + messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content -> runCatching { innerRoom.send(content) } } } - override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result = + override suspend fun editMessage( + originalEventId: EventId?, + transactionId: TransactionId?, + body: String, + htmlBody: String?, + mentions: List, + ): Result = withContext(roomDispatcher) { if (originalEventId != null) { runCatching { val editedEvent = specialModeEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(originalEventId.value) editedEvent.use { innerRoom.edit( - newContent = messageEventContentFromParts(body, htmlBody), + newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), editItem = it, ) } @@ -289,11 +296,11 @@ class RustMatrixRoom( } } - override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result = withContext(roomDispatcher) { + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result = withContext(roomDispatcher) { runCatching { val inReplyTo = specialModeEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(eventId.value) inReplyTo.use { eventTimelineItem -> - innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventTimelineItem) + innerRoom.sendReply(messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), eventTimelineItem) } specialModeEventTimelineItem = null } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index a9d46296db..41c8ad40dd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -41,6 +41,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto lastMessageTimestamp = latestRoomMessage?.originServerTs, inviter = roomInfo.inviter?.let(RoomMemberMapper::map), notificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode), + hasOngoingCall = roomInfo.hasRoomCall, ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt index bddd2bc872..e87ae74f30 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt @@ -70,3 +70,17 @@ internal fun Room.backPaginationStatusFlow(): Flow = subscribeToBackPaginationStatus(listener) } }.buffer(Channel.UNLIMITED) + +internal suspend fun Room.runWithTimelineListenerRegistered(action: suspend () -> Unit) { + val result = addTimelineListener(NoOpTimelineListener) + try { + action() + } finally { + result.itemsStream.cancelAndDestroy() + result.items.destroyAll() + } +} + +private object NoOpTimelineListener : TimelineListener { + override fun onUpdate(diff: List) = Unit +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 53ae41d887..85e6905f0a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -72,7 +72,11 @@ class RustMatrixTimeline( MutableStateFlow(emptyList()) private val _paginationState = MutableStateFlow( - MatrixTimeline.PaginationState(hasMoreToLoadBackwards = true, isBackPaginating = false) + MatrixTimeline.PaginationState( + hasMoreToLoadBackwards = true, + isBackPaginating = false, + beginningOfRoomReached = false, + ) ) private val encryptedHistoryPostProcessor = TimelineEncryptedHistoryPostProcessor( @@ -155,6 +159,7 @@ class RustMatrixTimeline( return@getAndUpdate currentPaginationState.copy( isBackPaginating = false, hasMoreToLoadBackwards = false, + beginningOfRoomReached = false, ) } when (status) { @@ -173,7 +178,8 @@ class RustMatrixTimeline( BackPaginationStatus.TIMELINE_START_REACHED -> { currentPaginationState.copy( isBackPaginating = false, - hasMoreToLoadBackwards = false + hasMoreToLoadBackwards = false, + beginningOfRoomReached = true, ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 21e7d51638..d761e91d6c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -20,18 +20,20 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender +import io.element.android.libraries.matrix.api.timeline.item.event.Receipt +import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import org.matrix.rustcomponents.sdk.Reaction import org.matrix.rustcomponents.sdk.EventItemOrigin as RustEventItemOrigin import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails +import org.matrix.rustcomponents.sdk.Receipt as RustReceipt class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) { @@ -45,6 +47,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap isRemote = it.isRemote(), localSendState = it.localSendState()?.map(), reactions = it.reactions().map(), + receipts = it.readReceipts().map(), sender = UserId(it.sender()), senderProfile = it.senderProfile().map(), timestamp = it.timestamp().toLong(), @@ -92,6 +95,15 @@ private fun List?.map(): List { } ?: emptyList() } +private fun Map.map(): List { + return map { + Receipt( + userId = UserId(it.key), + timestamp = it.value.timestamp?.toLong() ?: 0 + ) + }.sortedByDescending { it.timestamp } +} + private fun RustEventTimelineItemDebugInfo.map(): TimelineItemDebugInfo { return TimelineItemDebugInfo( model = model, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt index 60de981780..25241ff3e1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt @@ -45,7 +45,8 @@ class TimelineEncryptedHistoryPostProcessor( paginationStateFlow.getAndUpdate { it.copy( isBackPaginating = false, - hasMoreToLoadBackwards = false + hasMoreToLoadBackwards = false, + beginningOfRoomReached = false, ) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt index d3af9a5670..cf5c3682c2 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt @@ -98,7 +98,13 @@ class TimelineEncryptedHistoryPostProcessorTest { @Test fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, they're replaced and the user can't back paginate`() = runTest { - val paginationStateFlow = MutableStateFlow(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = true, isBackPaginating = false)) + val paginationStateFlow = MutableStateFlow( + MatrixTimeline.PaginationState( + hasMoreToLoadBackwards = true, + isBackPaginating = false, + beginningOfRoomReached = false, + ) + ) val processor = createPostProcessor(paginationStateFlow = paginationStateFlow) val items = listOf( MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)), @@ -111,7 +117,13 @@ class TimelineEncryptedHistoryPostProcessorTest { MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1)) ) ) - assertThat(paginationStateFlow.value).isEqualTo(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = false, isBackPaginating = false)) + assertThat(paginationStateFlow.value).isEqualTo( + MatrixTimeline.PaginationState( + hasMoreToLoadBackwards = false, + isBackPaginating = false, + beginningOfRoomReached = false, + ) + ) } private fun TestScope.createPostProcessor( @@ -119,7 +131,13 @@ class TimelineEncryptedHistoryPostProcessorTest { isRoomEncrypted: Boolean = true, isKeyBackupEnabled: Boolean = false, paginationStateFlow: MutableStateFlow = - MutableStateFlow(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = true, isBackPaginating = false)) + MutableStateFlow( + MatrixTimeline.PaginationState( + hasMoreToLoadBackwards = true, + isBackPaginating = false, + beginningOfRoomReached = false, + ) + ) ) = TimelineEncryptedHistoryPostProcessor( lastLoginTimestamp = lastLoginTimestamp, isRoomEncrypted = isRoomEncrypted, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index 825d929c0e..9f10f4ba35 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -34,6 +34,7 @@ class FakeEncryptionService : EncryptionService { private var waitForBackupUploadSteadyStateFlow: Flow = flowOf() private var fixRecoveryIssuesFailure: Exception? = null + private var doesBackupExistOnServerResult: Result = Result.success(true) override suspend fun enableBackups(): Result = simulateLongTask { return Result.success(Unit) @@ -52,6 +53,14 @@ class FakeEncryptionService : EncryptionService { return Result.success(Unit) } + fun givenDoesBackupExistOnServerResult(result: Result) { + doesBackupExistOnServerResult = result + } + + override suspend fun doesBackupExistOnServer(): Result = simulateLongTask { + return doesBackupExistOnServerResult + } + override suspend fun fixRecoveryIssues(recoveryKey: String): Result = simulateLongTask { fixRecoveryIssuesFailure?.let { return Result.failure(it) } return Result.success(Unit) 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 244a769895..a041263e2c 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 @@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState +import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -108,6 +109,7 @@ class FakeMatrixRoom( private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io") private var getWidgetDriverResult: Result = Result.success(FakeWidgetDriver()) private var canUserTriggerRoomNotificationResult: Result = Result.success(true) + var sendMessageMentions = emptyList() val editMessageCalls = mutableListOf>() var sendMediaCount = 0 @@ -190,7 +192,8 @@ class FakeMatrixRoom( userAvatarUrlResult } - override suspend fun sendMessage(body: String, htmlBody: String?) = simulateLongTask { + override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List) = simulateLongTask { + sendMessageMentions = mentions Result.success(Unit) } @@ -219,7 +222,14 @@ class FakeMatrixRoom( return cancelSendResult } - override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result { + override suspend fun editMessage( + originalEventId: EventId?, + transactionId: TransactionId?, + body: String, + htmlBody: String?, + mentions: List + ): Result { + sendMessageMentions = mentions editMessageCalls += body to htmlBody return Result.success(Unit) } @@ -231,7 +241,8 @@ class FakeMatrixRoom( return Result.success(Unit) } - override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result { + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result { + sendMessageMentions = mentions replyMessageParameter = body to htmlBody return Result.success(Unit) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index ae473d8da8..d12f168789 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -37,6 +37,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -107,6 +108,7 @@ fun anEventTimelineItem( isRemote: Boolean = false, localSendState: LocalEventSendState? = null, reactions: List = emptyList(), + receipts: List = emptyList(), sender: UserId = A_USER_ID, senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), timestamp: Long = 0L, @@ -121,6 +123,7 @@ fun anEventTimelineItem( isRemote = isRemote, localSendState = localSendState, reactions = reactions, + receipts = receipts, sender = sender, senderProfile = senderProfile, timestamp = timestamp, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt index 73bc5fb597..6cf6f05cbe 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt @@ -29,7 +29,11 @@ import kotlinx.coroutines.flow.getAndUpdate class FakeMatrixTimeline( initialTimelineItems: List = emptyList(), - initialPaginationState: MatrixTimeline.PaginationState = MatrixTimeline.PaginationState(hasMoreToLoadBackwards = true, isBackPaginating = false) + initialPaginationState: MatrixTimeline.PaginationState = MatrixTimeline.PaginationState( + hasMoreToLoadBackwards = true, + isBackPaginating = false, + beginningOfRoomReached = false, + ) ) : MatrixTimeline { private val _paginationState: MutableStateFlow = MutableStateFlow(initialPaginationState) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt index 651f276f7b..40c58fb578 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt @@ -105,7 +105,7 @@ fun AttachmentThumbnail( } AttachmentThumbnailType.File -> { Icon( - resourceId = CommonDrawables.ic_september_attachment, + resourceId = CommonDrawables.ic_attachment, contentDescription = info.textContent, modifier = Modifier.rotate(-45f) ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt index 05287e04e1..343886c146 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt @@ -28,16 +28,18 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.ui.media.AvatarAction @@ -98,12 +100,10 @@ private fun AvatarActionBottomSheetContent( color = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, ) }, - leadingContent = { - Icon( - resourceId = action.iconResourceId, - contentDescription = stringResource(action.titleResId), - tint = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary, - ) + leadingContent = ListItemContent.Icon(IconSource.Resource(action.iconResourceId)), + style = when { + action.destructive -> ListItemStyle.Destructive + else -> ListItemStyle.Primary } ) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt index f97227be7e..289c37530a 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt @@ -26,8 +26,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.AddAPhoto import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -40,6 +38,7 @@ 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.theme.components.Icon +import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable fun EditableAvatarView( @@ -87,7 +86,7 @@ fun EditableAvatarView( ) { Icon( modifier = Modifier.size(16.dp), - imageVector = Icons.Outlined.AddAPhoto, + resourceId = CommonDrawables.ic_edit, contentDescription = "", tint = MaterialTheme.colorScheme.onPrimary, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt index 1380d15e8b..947afd8bbd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt @@ -31,11 +31,13 @@ fun MatrixUserRow( matrixUser: MatrixUser, modifier: Modifier = Modifier, avatarSize: AvatarSize = AvatarSize.UserListItem, + trailingContent: @Composable (() -> Unit)? = null, ) = UserRow( avatarData = matrixUser.getAvatarData(avatarSize), name = matrixUser.getBestName(), subtext = if (matrixUser.displayName.isNullOrEmpty()) null else matrixUser.userId.value, modifier = modifier, + trailingContent, ) @PreviewsDayNight diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt index 5e59ce7aef..7aa11ba193 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt @@ -38,6 +38,7 @@ internal fun UserRow( name: String, subtext: String?, modifier: Modifier = Modifier, + trailingContent: @Composable (() -> Unit)? = null, ) { Row( modifier = modifier @@ -49,7 +50,8 @@ internal fun UserRow( Avatar(avatarData) Column( modifier = Modifier - .padding(start = 12.dp), + .padding(start = 12.dp) + .weight(1f), ) { // Name Text( @@ -70,5 +72,6 @@ internal fun UserRow( ) } } + trailingContent?.invoke() } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt index 99a6083565..e5305b86ae 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt @@ -30,12 +30,12 @@ sealed class AvatarAction( ) { data object TakePhoto : AvatarAction( titleResId = CommonStrings.action_take_photo, - iconResourceId = CommonDrawables.ic_september_take_photo_camera, + iconResourceId = CommonDrawables.ic_take_photo_camera, ) data object ChoosePhoto : AvatarAction( titleResId = CommonStrings.action_choose_photo, - iconResourceId = CommonDrawables.ic_september_photo_video_library, + iconResourceId = CommonDrawables.ic_image, ) data object Remove : AvatarAction( diff --git a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt index 46c5b40ead..3598edccf3 100644 --- a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt +++ b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt @@ -38,6 +38,7 @@ interface MediaPlayer : AutoCloseable { uri: String, mediaId: String, mimeType: String, + startPositionMs: Long = 0, ): State /** @@ -69,6 +70,10 @@ interface MediaPlayer : AutoCloseable { * Whether the player is currently playing. */ val isPlaying: Boolean, + /** + * Whether the player has reached the end of the current media. + */ + val isEnded: Boolean, /** * The id of the media which is currently playing. * diff --git a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt index 420b01acc7..04919b0e56 100644 --- a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt @@ -77,6 +77,7 @@ class MediaPlayerImpl @Inject constructor( _state.update { it.copy( isReady = playbackState == Player.STATE_READY, + isEnded = playbackState == Player.STATE_ENDED, currentPosition = player.currentPosition, duration = duration, ) @@ -95,16 +96,22 @@ class MediaPlayerImpl @Inject constructor( MediaPlayer.State( isReady = false, isPlaying = false, + isEnded = false, mediaId = null, currentPosition = 0L, - duration = 0L + duration = null, ) ) override val state: StateFlow = _state.asStateFlow() @OptIn(FlowPreview::class) - override suspend fun setMedia(uri: String, mediaId: String, mimeType: String): MediaPlayer.State { + override suspend fun setMedia( + uri: String, + mediaId: String, + mimeType: String, + startPositionMs: Long, + ): MediaPlayer.State { player.pause() // Must pause here otherwise if the player was playing it would keep on playing the new media item. player.clearMediaItems() player.setMediaItem( @@ -112,11 +119,12 @@ class MediaPlayerImpl @Inject constructor( .setUri(uri) .setMediaId(mediaId) .setMimeType(mimeType) - .build() + .build(), + startPositionMs, ) player.prepare() // Will throw TimeoutCancellationException if the player is not ready after 1 second. - return state.timeout(1.seconds).first { it.isReady } + return state.timeout(1.seconds).first { it.isReady && it.mediaId == mediaId } } override fun play() { @@ -129,7 +137,7 @@ class MediaPlayerImpl @Inject constructor( // playing no sound. // This is a workaround which will reload the media file. player.getCurrentMediaItem()?.let { - player.setMediaItem(it) + player.setMediaItem(it, 0) player.prepare() player.play() } diff --git a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt index 1395c69e57..ff8ff786ec 100644 --- a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt @@ -35,7 +35,7 @@ interface SimplePlayer { val playbackState: Int val duration: Long fun clearMediaItems() - fun setMediaItem(mediaItem: MediaItem) + fun setMediaItem(mediaItem: MediaItem, startPositionMs: Long) fun getCurrentMediaItem(): MediaItem? fun prepare() fun play() @@ -81,7 +81,7 @@ class SimplePlayerImpl( override fun clearMediaItems() = p.clearMediaItems() - override fun setMediaItem(mediaItem: MediaItem) = p.setMediaItem(mediaItem) + override fun setMediaItem(mediaItem: MediaItem, startPositionMs: Long) = p.setMediaItem(mediaItem, startPositionMs) override fun getCurrentMediaItem(): MediaItem? = p.currentMediaItem diff --git a/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt index 4f7d19df47..9bfb974622 100644 --- a/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt +++ b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt @@ -26,31 +26,37 @@ import kotlinx.coroutines.flow.update /** * Fake implementation of [MediaPlayer] for testing purposes. */ -class FakeMediaPlayer : MediaPlayer { - companion object { - private const val FAKE_TOTAL_DURATION_MS = 10_000L - private const val FAKE_PLAYED_DURATION_MS = 1000L - } +class FakeMediaPlayer( + private val fakeTotalDurationMs: Long = 10_000L, + private val fakePlayedDurationMs: Long = 1000L, +) : MediaPlayer { private val _state = MutableStateFlow( MediaPlayer.State( isReady = false, isPlaying = false, + isEnded = false, mediaId = null, currentPosition = 0L, - duration = 0L + duration = null ) ) override val state: StateFlow = _state.asStateFlow() - override suspend fun setMedia(uri: String, mediaId: String, mimeType: String): MediaPlayer.State { + override suspend fun setMedia( + uri: String, + mediaId: String, + mimeType: String, + startPositionMs: Long, + ): MediaPlayer.State { _state.update { it.copy( isReady = false, isPlaying = false, + isEnded = false, mediaId = mediaId, - currentPosition = 0, + currentPosition = startPositionMs, duration = null, ) } @@ -58,7 +64,7 @@ class FakeMediaPlayer : MediaPlayer { _state.update { it.copy( isReady = true, - duration = FAKE_TOTAL_DURATION_MS, + duration = fakeTotalDurationMs, ) } return _state.value @@ -66,10 +72,20 @@ class FakeMediaPlayer : MediaPlayer { override fun play() { _state.update { - it.copy( - isPlaying = true, - currentPosition = it.currentPosition + FAKE_PLAYED_DURATION_MS, - ) + val newPosition = it.currentPosition + fakePlayedDurationMs + if (newPosition < fakeTotalDurationMs) { + it.copy( + isPlaying = true, + currentPosition = newPosition, + ) + } else { + it.copy( + isReady = false, + isPlaying = false, + isEnded = true, + currentPosition = fakeTotalDurationMs, + ) + } } } diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt index d62fb7e6cf..2bd1fc6064 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt @@ -28,5 +28,8 @@ interface PreferencesStore { suspend fun setCustomElementCallBaseUrl(string: String?) fun getCustomElementCallBaseUrlFlow(): Flow + suspend fun setTheme(theme: String) + fun getThemeFlow(): Flow + suspend fun reset() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt index 66a46d1ca3..d00b7505d7 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt @@ -39,6 +39,7 @@ private val Context.dataStore: DataStore by preferencesDataStore(na private val richTextEditorKey = booleanPreferencesKey("richTextEditor") private val developerModeKey = booleanPreferencesKey("developerMode") private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseUrl") +private val themeKey = stringPreferencesKey("theme") @ContributesBinding(AppScope::class) class DefaultPreferencesStore @Inject constructor( @@ -89,6 +90,18 @@ class DefaultPreferencesStore @Inject constructor( } } + override suspend fun setTheme(theme: String) { + store.edit { prefs -> + prefs[themeKey] = theme + } + } + + override fun getThemeFlow(): Flow { + return store.data.map { prefs -> + prefs[themeKey] + } + } + override suspend fun reset() { store.edit { it.clear() } } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt index 6dea8910ed..c4a26215c2 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt @@ -24,10 +24,12 @@ class InMemoryPreferencesStore( isRichTextEditorEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false, customElementCallBaseUrl: String? = null, + theme: String? = null, ) : PreferencesStore { private var _isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled) private var _isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) private var _customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl) + private var _theme = MutableStateFlow(theme) override suspend fun setRichTextEditorEnabled(enabled: Boolean) { _isRichTextEditorEnabled.value = enabled @@ -53,6 +55,14 @@ class InMemoryPreferencesStore( return _customElementCallBaseUrl } + override suspend fun setTheme(theme: String) { + _theme.value = theme + } + + override fun getThemeFlow(): Flow { + return _theme + } + override suspend fun reset() { // No op } diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 6c7844bd96..c67d805fb6 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -16,7 +16,7 @@ plugins { id("io.element.android-library") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.20" + alias(libs.plugins.kotlin.serialization) } android { @@ -36,6 +36,7 @@ dependencies { implementation(libs.serialization.json) implementation(libs.coil) + implementation(projects.appconfig) implementation(projects.libraries.architecture) implementation(projects.libraries.core) implementation(projects.libraries.designsystem) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index b49f8f4299..d2650f853d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.push.impl import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appconfig.PushConfig import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope @@ -24,7 +25,6 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData -import io.element.android.libraries.push.impl.config.PushConfig import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.libraries.pushstore.api.UserPushStoreFactory diff --git a/libraries/push/impl/src/main/res/values-cs/translations.xml b/libraries/push/impl/src/main/res/values-cs/translations.xml index 84525a427e..b9488e008b 100644 --- a/libraries/push/impl/src/main/res/values-cs/translations.xml +++ b/libraries/push/impl/src/main/res/values-cs/translations.xml @@ -8,6 +8,10 @@ "Vstoupit" "Odmítnout" "Vás pozval(a) do chatu" + "%1$s vás zmínil(a). +%2$s" + "Byli jste zmíněni. +%1$s" "Nové zprávy" "Reagoval(a) s %1$s" "Označit jako přečtené" diff --git a/libraries/push/impl/src/main/res/values-ru/translations.xml b/libraries/push/impl/src/main/res/values-ru/translations.xml index 697a0f01d8..aec7180374 100644 --- a/libraries/push/impl/src/main/res/values-ru/translations.xml +++ b/libraries/push/impl/src/main/res/values-ru/translations.xml @@ -8,6 +8,10 @@ "Присоединиться" "Отклонить" "Пригласил вас в чат" + "%1$s упомянул вас. +%2$s" + "Вас уже упомянули. +%1$s" "Новые сообщения" "Отреагировал на %1$s" "Отметить как прочитанное" diff --git a/libraries/push/impl/src/main/res/values-sk/translations.xml b/libraries/push/impl/src/main/res/values-sk/translations.xml index 16eb94685d..0bc3338204 100644 --- a/libraries/push/impl/src/main/res/values-sk/translations.xml +++ b/libraries/push/impl/src/main/res/values-sk/translations.xml @@ -8,6 +8,10 @@ "Pripojiť sa" "Zamietnuť" "Vás pozval/a na konverzáciu" + "%1$s vás spomenul/-a. +%2$s" + "Boli ste spomenutí. +%1$s" "Nové správy" "Reagoval/a s %1$s" "Označiť ako prečítané" diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index 987728304a..5cadfb90ac 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -8,6 +8,10 @@ "Join" "Reject" "Invited you to chat" + "%1$s mentioned you. +%2$s" + "You have been mentioned. +%1$s" "New Messages" "Reacted with %1$s" "Mark as read" diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index 41bffc5461..733309e00d 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -16,7 +16,7 @@ plugins { id("io.element.android-library") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.20" + alias(libs.plugins.kotlin.serialization) } android { @@ -41,7 +41,7 @@ dependencies { implementation(projects.libraries.network) implementation(platform(libs.network.okhttp.bom)) - implementation("com.squareup.okhttp3:okhttp") + implementation(libs.network.okhttp.okhttp) implementation(libs.network.retrofit) implementation(libs.serialization.json) diff --git a/libraries/textcomposer/impl/build.gradle.kts b/libraries/textcomposer/impl/build.gradle.kts index db97a96787..6cce2b855d 100644 --- a/libraries/textcomposer/impl/build.gradle.kts +++ b/libraries/textcomposer/impl/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiUtils) - implementation(libs.matrix.richtexteditor) + api(libs.matrix.richtexteditor) api(libs.matrix.richtexteditor.compose) ksp(libs.showkase.processor) @@ -42,4 +42,7 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.truth) testImplementation(libs.coroutines.test) + testImplementation(libs.test.robolectric) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) } 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 fef9d40a5b..75dbc6c1c0 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 @@ -44,6 +44,7 @@ import androidx.compose.runtime.remember 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.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -57,6 +58,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail @@ -66,23 +68,26 @@ import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.textcomposer.components.ComposerOptionsButton import io.element.android.libraries.textcomposer.components.DismissTextFormattingButton -import io.element.android.libraries.textcomposer.components.RecordButton +import io.element.android.libraries.textcomposer.components.VoiceMessageRecorderButton import io.element.android.libraries.textcomposer.components.SendButton import io.element.android.libraries.textcomposer.components.TextFormatting import io.element.android.libraries.textcomposer.components.VoiceMessageDeleteButton import io.element.android.libraries.textcomposer.components.VoiceMessagePreview import io.element.android.libraries.textcomposer.components.VoiceMessageRecording import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape +import io.element.android.libraries.textcomposer.mentions.rememberMentionSpanProvider import io.element.android.libraries.textcomposer.model.Message import io.element.android.libraries.textcomposer.model.MessageComposerMode -import io.element.android.libraries.textcomposer.model.PressEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.wysiwyg.compose.PillStyle import io.element.android.wysiwyg.compose.RichTextEditor import io.element.android.wysiwyg.compose.RichTextEditorState +import io.element.android.wysiwyg.display.TextDisplay import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import uniffi.wysiwyg_composer.MenuAction @@ -95,6 +100,7 @@ fun TextComposer( composerMode: MessageComposerMode, enableTextFormatting: Boolean, enableVoiceMessages: Boolean, + currentUserId: UserId, modifier: Modifier = Modifier, showTextFormatting: Boolean = false, subcomposing: Boolean = false, @@ -103,7 +109,7 @@ fun TextComposer( onResetComposerMode: () -> Unit = {}, onAddAttachment: () -> Unit = {}, onDismissTextFormatting: () -> Unit = {}, - onVoiceRecordButtonEvent: (PressEvent) -> Unit = {}, + onVoiceRecorderEvent: (VoiceMessageRecorderEvent) -> Unit = {}, onVoicePlayerEvent: (VoiceMessagePlayerEvent) -> Unit = {}, onSendVoiceMessage: () -> Unit = {}, onDeleteVoiceMessage: () -> Unit = {}, @@ -143,6 +149,7 @@ fun TextComposer( val textInput: @Composable () -> Unit = remember(state, subcomposing, composerMode, onResetComposerMode, onError) { @Composable { + val mentionSpanProvider = rememberMentionSpanProvider(currentUserId) TextInput( state = state, subcomposing = subcomposing, @@ -153,6 +160,8 @@ fun TextComposer( }, composerMode = composerMode, onResetComposerMode = onResetComposerMode, + resolveMentionDisplay = { text, url -> TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) }, + resolveRoomMentionDisplay = { TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor("@room", "#")) }, onError = onError, ) } @@ -167,16 +176,15 @@ fun TextComposer( ) } val recordVoiceButton = @Composable { - RecordButton( - onPressStart = { onVoiceRecordButtonEvent(PressEvent.PressStart) }, - onLongPressEnd = { onVoiceRecordButtonEvent(PressEvent.LongPressEnd) }, - onTap = { onVoiceRecordButtonEvent(PressEvent.Tapped) }, + VoiceMessageRecorderButton( + isRecording = voiceMessageState is VoiceMessageState.Recording, + onEvent = onVoiceRecorderEvent, ) } val sendVoiceButton = @Composable { SendButton( canSendMessage = voiceMessageState is VoiceMessageState.Preview, - onClick = { onSendVoiceMessage() }, + onClick = onSendVoiceMessage, composerMode = composerMode, ) } @@ -223,8 +231,12 @@ fun TextComposer( } val voiceDeleteButton = @Composable { - if (voiceMessageState is VoiceMessageState.Preview) { - VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending, onClick = onDeleteVoiceMessage) + when (voiceMessageState) { + is VoiceMessageState.Preview -> + VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending, onClick = onDeleteVoiceMessage) + is VoiceMessageState.Recording -> + VoiceMessageDeleteButton(enabled = true, onClick = { onVoiceRecorderEvent(VoiceMessageRecorderEvent.Cancel) }) + else -> {} } } @@ -286,7 +298,7 @@ private fun StandardLayout( verticalAlignment = Alignment.Bottom, ) { if (enableVoiceMessages && voiceMessageState !is VoiceMessageState.Idle) { - if (voiceMessageState is VoiceMessageState.Preview) { + if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) { Box( modifier = Modifier .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) @@ -382,6 +394,8 @@ private fun TextInput( placeholder: String, composerMode: MessageComposerMode, onResetComposerMode: () -> Unit, + resolveRoomMentionDisplay: () -> TextDisplay, + resolveMentionDisplay: (text: String, url: String) -> TextDisplay, modifier: Modifier = Modifier, onError: (Throwable) -> Unit = {}, ) { @@ -429,7 +443,11 @@ private fun TextInput( .fillMaxWidth(), style = ElementRichTextEditorStyle.create( hasFocus = state.hasFocus + ).copy( + pill = PillStyle(Color.Red) ), + resolveMentionDisplay = resolveMentionDisplay, + resolveRoomMentionDisplay = resolveRoomMentionDisplay, onError = onError ) } @@ -472,7 +490,7 @@ private fun EditingModeView( .padding(start = 12.dp) ) { Icon( - resourceId = CommonDrawables.ic_september_edit_solid_16, + resourceId = CommonDrawables.ic_edit_solid, contentDescription = stringResource(CommonStrings.common_editing), tint = ElementTheme.materialColors.secondary, modifier = Modifier @@ -581,6 +599,7 @@ internal fun TextComposerSimplePreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost"), ) }, { TextComposer( @@ -591,6 +610,7 @@ internal fun TextComposerSimplePreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -604,6 +624,7 @@ internal fun TextComposerSimplePreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -614,6 +635,7 @@ internal fun TextComposerSimplePreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }) ) @@ -630,6 +652,7 @@ internal fun TextComposerFormattingPreview() = ElementPreview { composerMode = MessageComposerMode.Normal, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -639,6 +662,7 @@ internal fun TextComposerFormattingPreview() = ElementPreview { composerMode = MessageComposerMode.Normal, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -648,6 +672,7 @@ internal fun TextComposerFormattingPreview() = ElementPreview { composerMode = MessageComposerMode.Normal, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) })) } @@ -664,6 +689,7 @@ internal fun TextComposerEditPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) })) } @@ -688,6 +714,7 @@ internal fun TextComposerReplyPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { @@ -707,6 +734,7 @@ internal fun TextComposerReplyPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -728,6 +756,7 @@ internal fun TextComposerReplyPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -749,6 +778,7 @@ internal fun TextComposerReplyPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -770,6 +800,7 @@ internal fun TextComposerReplyPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }, { TextComposer( @@ -791,6 +822,7 @@ internal fun TextComposerReplyPreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) }) ) @@ -810,6 +842,7 @@ internal fun TextComposerVoicePreview() = ElementPreview { onResetComposerMode = {}, enableTextFormatting = true, enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") ) PreviewColumn(items = persistentListOf({ VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, createFakeWaveform())) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/RecordButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/RecordButton.kt deleted file mode 100644 index 0a0710095e..0000000000 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/RecordButton.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:OptIn(ExperimentalMaterial3Api::class) - -package io.element.android.libraries.textcomposer.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.TooltipState -import androidx.compose.material3.rememberTooltipState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.components.tooltip.ElementTooltipDefaults -import io.element.android.libraries.designsystem.components.tooltip.PlainTooltip -import io.element.android.libraries.designsystem.components.tooltip.TooltipBox -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.libraries.designsystem.theme.components.IconButton -import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.libraries.textcomposer.R -import io.element.android.libraries.textcomposer.utils.PressState -import io.element.android.libraries.textcomposer.utils.PressStateEffects -import io.element.android.libraries.textcomposer.utils.rememberPressState -import io.element.android.libraries.theme.ElementTheme -import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun RecordButton( - modifier: Modifier = Modifier, - initialTooltipIsVisible: Boolean = false, - onPressStart: () -> Unit = {}, - onLongPressEnd: () -> Unit = {}, - onTap: () -> Unit = {}, -) { - val coroutineScope = rememberCoroutineScope() - val pressState = rememberPressState() - val hapticFeedback = LocalHapticFeedback.current - - val performHapticFeedback = { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - } - - val tooltipState = rememberTooltipState( - initialIsVisible = initialTooltipIsVisible - ) - - PressStateEffects( - pressState = pressState.value, - onPressStart = { - onPressStart() - performHapticFeedback() - }, - onLongPressEnd = { - onLongPressEnd() - performHapticFeedback() - }, - onTap = { - onTap() - performHapticFeedback() - coroutineScope.launch { tooltipState.show() } - }, - ) - Box(modifier = modifier) { - HoldToRecordTooltip( - tooltipState = tooltipState, - spacingBetweenTooltipAndAnchor = 0.dp, // Accounts for the 48.dp size of the record button - anchor = { - RecordButtonView( - isPressed = pressState.value is PressState.Pressing, - modifier = Modifier - .pointerInput(Unit) { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - coroutineScope.launch { - when (event.type) { - PointerEventType.Press -> pressState.press() - PointerEventType.Release -> pressState.release() - } - } - } - } - } - ) - } - ) - } -} - -@Composable -private fun RecordButtonView( - isPressed: Boolean, - modifier: Modifier = Modifier, -) { - IconButton( - modifier = modifier - .size(48.dp), - onClick = {}, - ) { - Icon( - modifier = Modifier.size(24.dp), - resourceId = if (isPressed) { - CommonDrawables.ic_compound_mic_on_solid - } else { - CommonDrawables.ic_compound_mic_on_outline - }, - contentDescription = stringResource(CommonStrings.a11y_voice_message_record), - tint = ElementTheme.colors.iconSecondary, - ) - } -} - -@Composable -private fun HoldToRecordTooltip( - tooltipState: TooltipState, - spacingBetweenTooltipAndAnchor: Dp, - modifier: Modifier = Modifier, - anchor: @Composable () -> Unit, -) { - TooltipBox( - positionProvider = ElementTooltipDefaults.rememberPlainTooltipPositionProvider( - spacingBetweenTooltipAndAnchor = spacingBetweenTooltipAndAnchor, - ), - tooltip = { - PlainTooltip { - Text( - text = stringResource(R.string.screen_room_voice_message_tooltip), - color = ElementTheme.colors.textOnSolidPrimary, - style = ElementTheme.typography.fontBodySmMedium, - ) - } - }, - state = tooltipState, - modifier = modifier, - focusable = false, - enableUserInput = false, - content = anchor, - ) -} - -@PreviewsDayNight -@Composable -internal fun RecordButtonPreview() = ElementPreview { - Row { - RecordButtonView(isPressed = false) - RecordButtonView(isPressed = true) - } -} - -@PreviewsDayNight -@Composable -internal fun HoldToRecordTooltipPreview() = ElementPreview { - Box(modifier = Modifier.fillMaxSize()) { - RecordButton( - modifier = Modifier.align(Alignment.BottomEnd), - initialTooltipIsVisible = true, - ) - } -} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt index bbe947f6a1..eecd9d6c26 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt @@ -54,7 +54,7 @@ internal fun SendButton( ) { val iconId = when (composerMode) { is MessageComposerMode.Edit -> CommonDrawables.ic_compound_check - else -> CommonDrawables.ic_september_send + else -> CommonDrawables.ic_send } val iconSize = when (composerMode) { is MessageComposerMode.Edit -> 24.dp diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt index 7197c07307..bcf307ec2e 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt @@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.textcomposer.R +import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.time.formatShort @@ -108,7 +108,7 @@ internal fun VoiceMessagePreview( playbackProgress = playbackProgress, showCursor = showCursor, waveform = waveform, - seekEnabled = false, // TODO enable seeking + seekEnabled = true, onSeek = onSeek, ) } @@ -145,14 +145,14 @@ private fun PlayerButton( @Composable private fun PauseIcon() = Icon( - resourceId = R.drawable.ic_pause, + resourceId = CommonDrawables.ic_pause, contentDescription = stringResource(id = CommonStrings.a11y_pause), modifier = Modifier.size(20.dp), ) @Composable private fun PlayIcon() = Icon( - resourceId = R.drawable.ic_play, + resourceId = CommonDrawables.ic_play, contentDescription = stringResource(id = CommonStrings.a11y_play), modifier = Modifier.size(20.dp), ) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt new file mode 100644 index 0000000000..d2a8c2cae1 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package io.element.android.libraries.textcomposer.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.utils.CommonDrawables +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +internal fun VoiceMessageRecorderButton( + isRecording: Boolean, + modifier: Modifier = Modifier, + onEvent: (VoiceMessageRecorderEvent) -> Unit = {}, +) { + val hapticFeedback = LocalHapticFeedback.current + + val performHapticFeedback = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + } + + if (isRecording) { + StopButton( + modifier = modifier, + onClick = { + performHapticFeedback() + onEvent(VoiceMessageRecorderEvent.Stop) + } + ) + } else { + StartButton( + modifier = modifier, + onClick = { + performHapticFeedback() + onEvent(VoiceMessageRecorderEvent.Start) + } + ) + } +} + +@Composable +private fun StartButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) = IconButton( + modifier = modifier.size(48.dp), + onClick = onClick, +) { + Icon( + modifier = Modifier.size(24.dp), + resourceId = CommonDrawables.ic_compound_mic_on_outline, + contentDescription = stringResource(CommonStrings.a11y_voice_message_record), + tint = ElementTheme.colors.iconSecondary, + ) +} + +@Composable +private fun StopButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) = IconButton( + modifier = modifier + .size(48.dp), + onClick = onClick, +) { + Box( + Modifier + .size(36.dp) + .background( + color = ElementTheme.colors.bgActionPrimaryRest, + shape = CircleShape, + ) + ) + Icon( + modifier = Modifier.size(24.dp), + resourceId = CommonDrawables.ic_stop, + contentDescription = stringResource(CommonStrings.a11y_voice_message_stop_recording), + tint = ElementTheme.colors.iconOnSolidPrimary, + ) +} + +@PreviewsDayNight +@Composable +internal fun VoiceMessageRecorderButtonPreview() = ElementPreview { + Row { + VoiceMessageRecorderButton(isRecording = false) + VoiceMessageRecorderButton(isRecording = true) + } +} 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 new file mode 100644 index 0000000000..609a050abf --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.textcomposer.mentions + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.text.style.ReplacementSpan +import kotlin.math.roundToInt + +class MentionSpan( + val backgroundColor: Int, + val textColor: Int, +) : ReplacementSpan() { + + override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { + return paint.measureText(text, start, end).roundToInt() + 40 + } + + override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { + val textSize = paint.measureText(text, start, end) + val rect = RectF(x, top.toFloat(), x + textSize + 40, bottom.toFloat()) + paint.color = backgroundColor + canvas.drawRoundRect(rect, rect.height() / 2, rect.height() / 2, paint) + paint.color = textColor + canvas.drawText(text!!, start, end, x + 20, y.toFloat(), paint) + } +} 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 new file mode 100644 index 0000000000..77674e1254 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.textcomposer.mentions + +import android.graphics.Color +import android.view.ViewGroup +import android.widget.TextView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.text.buildSpannedString +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.currentUserMentionPillBackground +import io.element.android.libraries.designsystem.theme.currentUserMentionPillText +import io.element.android.libraries.designsystem.theme.mentionPillBackground +import io.element.android.libraries.designsystem.theme.mentionPillText +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.theme.ElementTheme + +@Stable +class MentionSpanProvider( + private val currentSessionId: SessionId, + private var currentUserTextColor: Int = 0, + private var currentUserBackgroundColor: Int = Color.WHITE, + private var otherTextColor: Int = 0, + private var otherBackgroundColor: Int = Color.WHITE, +) { + + @Suppress("ComposableNaming") + @Composable + internal fun setup() { + currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb() + currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb() + otherTextColor = ElementTheme.colors.mentionPillText.toArgb() + otherBackgroundColor = ElementTheme.colors.mentionPillBackground.toArgb() + } + + fun getMentionSpanFor(text: String, url: String): MentionSpan { + val permalinkData = PermalinkParser.parse(url) + return when { + permalinkData is PermalinkData.UserLink -> { + val isCurrentUser = permalinkData.userId == currentSessionId.value + MentionSpan( + backgroundColor = if (isCurrentUser) currentUserBackgroundColor else otherBackgroundColor, + textColor = if (isCurrentUser) currentUserTextColor else otherTextColor, + ) + } + text == "@room" && permalinkData is PermalinkData.FallbackLink -> { + MentionSpan( + backgroundColor = otherBackgroundColor, + textColor = otherTextColor, + ) + } + else -> { + MentionSpan( + backgroundColor = otherBackgroundColor, + textColor = otherTextColor, + ) + } + } + } +} + +@Composable +fun rememberMentionSpanProvider(currentUserId: SessionId): MentionSpanProvider { + val provider = remember(currentUserId) { + MentionSpanProvider(currentUserId) + } + provider.setup() + return provider +} + +@PreviewsDayNight +@Composable +internal fun MentionSpanPreview() { + val provider = rememberMentionSpanProvider(SessionId("@me:matrix.org")) + ElementPreview { + provider.setup() + + val textColor = ElementTheme.colors.textPrimary.toArgb() + val mentionSpan = provider.getMentionSpanFor("me", "https://matrix.to/#/@me:matrix.org") + val mentionSpan2 = provider.getMentionSpanFor("other", "https://matrix.to/#/@other:matrix.org") + AndroidView(factory = { context -> + TextView(context).apply { + layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + text = buildSpannedString { + append("This is a ") + append("@mention", mentionSpan, 0) + append(" to the current user and this is a ") + append("@mention", mentionSpan2, 0) + append(" to other user") + } + setTextColor(textColor) + } + }) + } +} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/PressEvent.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt similarity index 77% rename from libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/PressEvent.kt rename to libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt index 340540886d..17091930c5 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/PressEvent.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt @@ -16,8 +16,8 @@ package io.element.android.libraries.textcomposer.model -sealed interface PressEvent { - data object PressStart: PressEvent - data object Tapped: PressEvent - data object LongPressEnd: PressEvent +sealed interface VoiceMessageRecorderEvent { + data object Start: VoiceMessageRecorderEvent + data object Stop: VoiceMessageRecorderEvent + data object Cancel: VoiceMessageRecorderEvent } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateEffects.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateEffects.kt deleted file mode 100644 index aaee6bae0f..0000000000 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateEffects.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.textcomposer.utils - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect - -/** - * React to [PressState] changes. - */ -@Composable -internal fun PressStateEffects( - pressState: PressState, - onPressStart: () -> Unit = {}, - onLongPressStart: () -> Unit = {}, - onTap: () -> Unit = {}, - onLongPressEnd: () -> Unit = {}, -) { - LaunchedEffect(pressState) { - when (pressState) { - is PressState.Idle -> - when (pressState.lastPress) { - PressState.Tapping -> onTap() - PressState.LongPressing -> onLongPressEnd() - null -> {} // Do nothing - } - is PressState.LongPressing -> onLongPressStart() - PressState.Tapping -> onPressStart() - } - } -} - - diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolder.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolder.kt deleted file mode 100644 index 7021b8ac46..0000000000 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolder.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.textcomposer.utils - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalViewConfiguration -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import timber.log.Timber - -@Composable -internal fun rememberPressState( - longPressTimeoutMillis: Long = LocalViewConfiguration.current.longPressTimeoutMillis, -): PressStateHolder { - return remember(longPressTimeoutMillis) { - PressStateHolder(longPressTimeoutMillis = longPressTimeoutMillis) - } -} - -/** - * State machine that keeps track of the pressed state. - * - * When a press is started, the state will transition through: - * [PressState.Idle] -> [PressState.Tapping] -> ... - * - * If a press is held for a longer time, the state will continue through: - * ... -> [PressState.LongPressing] -> ... - * - * When the press is released the states will then transition back to idle. - * ... -> [PressState.Idle] - * - * Whether a press should be considered a tap or a long press can be determined by - * looking at the last press when in the idle state. - * - * @see [PressStateEffects] - * @see [rememberPressState] - */ -internal class PressStateHolder( - private val longPressTimeoutMillis: Long, -) : State { - private var state: PressState by mutableStateOf(PressState.Idle(lastPress = null)) - - override val value: PressState - get() = state - - private var longPressTimer: Job? = null - - suspend fun press() = coroutineScope { - when (state) { - is PressState.Idle -> { - state = PressState.Tapping - } - is PressState.Pressing -> - Timber.e("Pointer pressed but it has not been released") - } - - longPressTimer = launch { - delay(longPressTimeoutMillis) - yield() - - if (isActive && state == PressState.Tapping) { - state = PressState.LongPressing - } - } - } - - fun release() { - longPressTimer?.cancel() - longPressTimer = null - when (val lastState = state) { - is PressState.Pressing -> - state = PressState.Idle(lastPress = lastState) - is PressState.Idle -> - Timber.e("Pointer pressed but it has not been released") - } - } -} - diff --git a/libraries/designsystem/src/main/res/drawable/ic_september_view_source.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_developer_options.xml similarity index 97% rename from libraries/designsystem/src/main/res/drawable/ic_september_view_source.xml rename to libraries/textcomposer/impl/src/main/res/drawable/ic_developer_options.xml index 3ad615a748..ed18daf9a2 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_september_view_source.xml +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_developer_options.xml @@ -21,5 +21,5 @@ android:viewportHeight="24"> + android:fillColor="#1B1D22"/> diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_devices.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_devices.xml new file mode 100644 index 0000000000..a369edca9f --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_devices.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_edit.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_edit.xml new file mode 100644 index 0000000000..5acb318c99 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_encryption_enabled.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_encryption_enabled.xml new file mode 100644 index 0000000000..d08f53b5bf --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_encryption_enabled.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_location_navigator.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_location_navigator.xml new file mode 100644 index 0000000000..0d66e9c237 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_location_navigator.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_new_message.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_new_message.xml new file mode 100644 index 0000000000..dd3584beb0 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_new_message.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_reply.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_reply.xml new file mode 100644 index 0000000000..bfd655cbac --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_reply.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_sign_out.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_sign_out.xml new file mode 100644 index 0000000000..0137b88cd4 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_sign_out.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_user.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_user.xml new file mode 100644 index 0000000000..a516526a49 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_user.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_user_add.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_user_add.xml new file mode 100644 index 0000000000..c497043188 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_user_add.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_waiting_to_decrypt.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_waiting_to_decrypt.xml new file mode 100644 index 0000000000..30975a62e9 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_waiting_to_decrypt.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt new file mode 100644 index 0000000000..748a06bb2c --- /dev/null +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.textcomposer.impl.mentions + +import android.graphics.Color +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider +import io.element.android.tests.testutils.WarmUpRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class MentionSpanProviderTest { + + @JvmField @Rule + val warmUpRule = WarmUpRule() + + private val myUserColor = Color.RED + private val otherColor = Color.BLUE + private val currentUserId = A_SESSION_ID + + private val mentionSpanProvider = MentionSpanProvider( + currentSessionId = currentUserId, + currentUserBackgroundColor = myUserColor, + currentUserTextColor = myUserColor, + otherBackgroundColor = otherColor, + otherTextColor = otherColor, + ) + + @Test + fun `getting mention span for current user should return a MentionSpan with custom colors`() { + val mentionSpan = mentionSpanProvider.getMentionSpanFor("me", "https://matrix.to/#/${currentUserId.value}") + assertThat(mentionSpan.backgroundColor).isEqualTo(myUserColor) + assertThat(mentionSpan.textColor).isEqualTo(myUserColor) + } + + @Test + fun `getting mention span for other user should return a MentionSpan with normal colors`() { + val mentionSpan = mentionSpanProvider.getMentionSpanFor("other", "https://matrix.to/#/@other:matrix.org") + assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) + assertThat(mentionSpan.textColor).isEqualTo(otherColor) + } + + @Test + fun `getting mention span for @room should return a MentionSpan with normal colors`() { + val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#") + assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) + assertThat(mentionSpan.textColor).isEqualTo(otherColor) + } +} diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolderTest.kt deleted file mode 100644 index 615692911e..0000000000 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolderTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.textcomposer.utils - -import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.textcomposer.utils.PressState.Idle -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runTest -import org.junit.Test -import kotlin.time.Duration.Companion.milliseconds - -@OptIn(ExperimentalCoroutinesApi::class) class PressStateHolderTest { - companion object { - const val LONG_PRESS_TIMEOUT_MILLIS = 1L - } - @Test - fun `it starts in idle state`() = runTest { - val stateHolder = createStateHolder() - assertThat(stateHolder.value).isEqualTo(Idle(lastPress = null)) - } - - @Test - fun `when press, it moves to tapping state`() = runTest { - val stateHolder = createStateHolder() - val press = async { stateHolder.press() } - advanceTimeBy(1.milliseconds) - assertThat(stateHolder.value).isEqualTo(PressState.Tapping) - press.await() - } - - @Test - fun `when release after short delay, it moves through tap states`() = runTest { - val stateHolder = createStateHolder() - val press = async { stateHolder.press() } - advanceTimeBy(1.milliseconds) - assertThat(stateHolder.value).isEqualTo(PressState.Tapping) - stateHolder.release() - advanceTimeBy(1.milliseconds) // wait for the long press timeout which should not be triggered - assertThat(stateHolder.value).isEqualTo(Idle(lastPress = PressState.Tapping)) - press.await() - } - - @Test - fun `when hold, it moves through long press states`() = runTest { - val stateHolder = createStateHolder() - val press = async { stateHolder.press() } - advanceTimeBy(1.milliseconds) - assertThat(stateHolder.value).isEqualTo(PressState.Tapping) - advanceTimeBy(1.milliseconds) - assertThat(stateHolder.value).isEqualTo(PressState.LongPressing) - stateHolder.release() - assertThat(stateHolder.value).isEqualTo(Idle(lastPress = PressState.LongPressing)) - press.await() - } - - @Test - fun `when release and repress, it doesn't enter long press states`() = runTest { - val stateHolder = createStateHolder() - val press1 = async { stateHolder.press() } - advanceTimeBy(1.milliseconds) - assertThat(stateHolder.value).isEqualTo(PressState.Tapping) - stateHolder.release() - val press2 = async { stateHolder.press() } - advanceTimeBy(1.milliseconds) - assertThat(stateHolder.value).isEqualTo(PressState.Tapping) - press1.await() - press2.await() - } - - @Test - fun `when press twice without releasing, it doesn't throw an error`() = runTest { - val stateHolder = createStateHolder() - stateHolder.press() - stateHolder.press() - } - - @Test - fun `when release without first pressing, it doesn't throw an error`() = runTest { - val stateHolder = createStateHolder() - stateHolder.release() - } - - @Test - fun `when release twice without pressing, it doesn't throw an error `() = runTest { - val stateHolder = createStateHolder() - stateHolder.press() - stateHolder.release() - stateHolder.release() - } - - private fun createStateHolder() = - PressStateHolder( - LONG_PRESS_TIMEOUT_MILLIS, - ) -} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/MoveActivityToBackgroundBackHandler.kt b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/theme/Theme.kt similarity index 52% rename from appnav/src/main/kotlin/io/element/android/appnav/MoveActivityToBackgroundBackHandler.kt rename to libraries/theme/src/main/kotlin/io/element/android/libraries/theme/theme/Theme.kt index 5d959f9464..2d77a66c98 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/MoveActivityToBackgroundBackHandler.kt +++ b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/theme/Theme.kt @@ -14,26 +14,33 @@ * limitations under the License. */ -package io.element.android.appnav +package io.element.android.libraries.theme.theme -import android.content.Context -import android.content.ContextWrapper -import androidx.activity.ComponentActivity -import androidx.activity.compose.BackHandler +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +enum class Theme { + System, + Dark, + Light; +} + +val themes = listOf(Theme.System, Theme.Dark, Theme.Light) @Composable -fun MoveActivityToBackgroundBackHandler(enabled: Boolean = true) { - - fun Context.findActivity(): ComponentActivity? = when (this) { - is ComponentActivity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null - } - - val context = LocalContext.current - BackHandler(enabled = enabled) { - context.findActivity()?.moveTaskToBack(false) +fun Theme.isDark(): Boolean { + return when (this) { + Theme.System -> isSystemInDarkTheme() + Theme.Dark -> true + Theme.Light -> false + } +} + +fun Flow.mapToTheme(): Flow = map { + when (it) { + null -> Theme.System + else -> Theme.valueOf(it) } } 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 55e1f01e06..9889bf4697 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -13,7 +13,8 @@ "Zobrazit heslo" "Zahájit hovor" "Uživatelské menu" - "Nahrajte hlasovou zprávu. Dvojitě klepněte a podržte pro záznam. Uvolněním ukončíte nahrávání." + "Nahrajte hlasovou zprávu." + "Zastavit nahrávání" "Přijmout" "Přidat na časovou osu" "Zpět" @@ -115,6 +116,7 @@ "Odkaz zkopírován do schránky" "Načítání…" "Zpráva" + "Akce zprávy" "Rozložení zprávy" "Zpráva byla odstraněna" "Moderní" @@ -134,6 +136,7 @@ "Obnovování…" "Odpověď na %1$s" "Nahlásit chybu" + "Nahlásit problém" "Zpráva odeslána" "Editor formátovaného textu" "Místnost" @@ -174,16 +177,9 @@ "Čekání na dešifrovací klíč" "Opravdu chcete ukončit toto hlasování?" "Hlasování: %1$s" + "Ověřit zařízení" "Potvrzení" "Upozornění" - "Aktivity" - "Vlajky" - "Jídlo a nápoje" - "Zvířata a příroda" - "Předměty" - "Smajlíci a lidé" - "Cestování a místa" - "Symboly" "Vytvoření trvalého odkazu se nezdařilo" "%1$s nemohl načíst mapu. Zkuste to prosím později." "Načítání zpráv se nezdařilo" @@ -196,9 +192,6 @@ "Omlouváme se, došlo k chybě" "🔐️ Připojte se ke mně na %1$s" "Ahoj, ozvi se mi na %1$s: %2$s" - "Opravdu chcete opustit tuto místnost? Jste tu jediná osoba. Pokud odejdete, nikdo se v budoucnu nebude moci připojit, včetně vás." - "Opravdu chcete opustit tuto místnost? Tato místnost není veřejná a bez pozvánky se nebudete moci znovu připojit." - "Opravdu chcete opustit místnost?" "%1$s Android" "zadána %1$d číslice" @@ -216,43 +209,9 @@ "%d hlasů" "Zatřeste zařízením pro nahlášení chyby" - "Tato zpráva bude nahlášena správci vašeho domovského serveru. Nebude si moci přečíst žádné šifrované zprávy." - "Důvod nahlášení tohoto obsahu" - "Toto je začátek %1$s." - "Toto je začátek této konverzace." - "Nové" - "Sdílet analytická data" "Výběr média se nezdařil, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." - "Další nastavení" - "Halsové a video hovory" - "Neshoda konfigurace" - "Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností. - -Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní. - -Pokud budete pokračovat, některá nastavení se mohou změnit." - "Přímé zprávy" - "Vlastní nastavení pro chat" - "Při aktualizaci nastavení oznámení došlo k chybě." - "Všechny zprávy" - "Pouze zmínky a klíčová slova" - "V přímých zprávách mě upozornit na" - "Ve skupinových chatech mě upozornit na" - "Povolit oznámení na tomto zařízení" - "Konfigurace nebyla opravena, zkuste to prosím znovu." - "Skupinové chaty" - "Zmínky" - "Vše" - "Zmínky" - "Upozornit mě na" - "Upozornit mě na @room" - "Chcete-li dostávat oznámení, změňte prosím svůj %1$s." - "systémová nastavení" - "Systémová oznámení byla vypnuta" - "Oznámení" - "Zaškrtněte, pokud chcete skrýt všechny aktuální a budoucí zprávy od tohoto uživatele" "Sdílet polohu" "Sdílet moji polohu" "Otevřít v Mapách Apple" @@ -260,14 +219,8 @@ Pokud budete pokračovat, některá nastavení se mohou změnit." "Otevřít v OpenStreetMap" "Sdílet tuto polohu" "Poloha" - "Rageshake" - "Práh detekce" "Verze: %1$s (%2$s)" "en" "Chyba" "Úspěch" - "Sdílejte anonymní údaje o používání, které nám pomohou identifikovat problémy." - "Můžete si přečíst všechny naše podmínky %1$s." - "zde" - "Zablokovat uživatele" 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 dd84815254..e473e6efb9 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -155,14 +155,6 @@ "Umfrage: %1$s" "Bestätigung" "Warnung" - "Aktivitäten" - "Flaggen" - "Essen & Trinken" - "Tiere & Natur" - "Objekte" - "Smileys & Menschen" - "Reisen & Orte" - "Symbole" "Fehler beim Erstellen des Permalinks" "%1$s konnte die Karte nicht laden. Bitte versuche es später erneut." "Fehler beim Laden der Nachrichten" @@ -173,9 +165,6 @@ "Entschuldigung, es ist ein Fehler aufgetreten" "🔐️ Begleite mich auf %1$s" "Hey, sprich mit mir auf %1$s: %2$s" - "Bist du sicher, dass du diesen Raum verlassen möchtest? Du bist die einzige Person hier. Wenn du austritst, kann in Zukunft niemand mehr eintreten, auch du nicht." - "Bist du sicher, dass du diesen Raum verlassen möchtest? Dieser Raum ist nicht öffentlich und du kannst ihm ohne Einladung nicht erneut beitreten." - "Bist du sicher, dass du den Raum verlassen willst?" "%1$s Android" "%1$d Mitglied" @@ -186,39 +175,9 @@ "%d Stimmen" "Schüttel heftig zum Melden von Fehlern" - "Diese Meldung wird an den Administrator deines Homeservers weitergeleitet. Dieser kann keine verschlüsselten Nachrichten lesen." - "Grund für die Meldung dieses Inhalts" - "Dies ist der Anfang von %1$s." - "Dies ist der Anfang dieses Gesprächs." - "Neu" - "Analysedaten teilen" "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut." - "Zusätzliche Einstellungen" - "Audio- und Videoanrufe" - "Konfiguration stimmt nicht überein" - "Wir haben die Einstellungen für Benachrichtigungen vereinfacht, damit die Optionen leichter zu finden sind. Einige benutzerdefinierte Einstellungen, die du in der Vergangenheit gewählt hast, werden hier nicht angezeigt, sind aber immer noch aktiv. Wenn du fortfährst, können sich einige deiner Einstellungen ändern." - "Direkte Chats" - "Benutzerdefinierte Einstellung pro Chat" - "Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." - "Alle Nachrichten" - "Nur Erwähnungen und Schlüsselwörter" - "Bei direkten Chats, benachrichtige mich bei" - "Bei Gruppenchats benachrichtige mich bei" - "Benachrichtigungen auf diesem Gerät aktivieren" - "Die Konfiguration wurde nicht korrigiert, bitte versuche es erneut." - "Gruppenchats" - "Erwähnungen" - "Alle" - "Erwähnungen" - "Benachrichtige mich bei" - "Benachrichtige mich bei @room" - "Um Benachrichtigungen zu erhalten, ändere bitte deine %1$s." - "Systemeinstellungen" - "Systembenachrichtigungen deaktiviert" - "Benachrichtigungen" - "Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest" "Standort teilen" "Meinen Standort teilen" "In Apple Maps öffnen" @@ -226,14 +185,8 @@ "In OpenStreetMap öffnen" "Diesen Standort teilen" "Standort" - "Rageshake" - "Erkennungsschwelle" "Version: %1$s (%2$s)" "en" "Fehler" "Erfolg" - "Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen." - "Du kannst alle unsere Bedingungen lesen %1$s." - "hier" - "Benutzer sperren" diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index 8bc500d7fe..eaab1988d5 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -94,39 +94,19 @@ "Esperando…" "Confirmar" "Atención" - "Actividades" - "Banderas" - "Comida y bebida" - "Animales y naturaleza" - "Objetos" - "Emojis y personas" - "Viajes y lugares" - "Símbolos" "No se pudo crear el enlace permanente" "Error al cargar mensajes" "Algunos mensajes no se han enviado" "Lo siento, se ha producido un error" "Hola, puedes hablar conmigo en %1$s: %2$s" - "¿Estás seguro de que quieres salir de esta sala? Eres la única persona aquí. Si te vas, nadie podrá unirse en el futuro, ni siquiera tú." - "¿Estás seguro de que quieres abandonar esta sala? Esta sala no es pública y no podrás volver a entrar sin una invitación." - "¿Seguro que quieres salir de la habitación?" "%1$s Android" "%1$d miembro" "%1$d miembros" "Agitar con fuerza para informar de un error" - "Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado." - "Motivo para denunciar este contenido" - "Este es el principio de %1$s." - "Este es el principio de esta conversación." - "Nuevos" - "Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario" - "Agitar con fuerza" - "Umbral de detección" "Versión: %1$s (%2$s)" "es" "Error" "Terminado" - "Bloquear usuario" 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 f655b1eafa..ac67487989 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -176,14 +176,6 @@ "Sondage : %1$s" "Confirmation" "Attention" - "Activités" - "Drapeaux" - "Nourriture et boissons" - "Animaux et nature" - "Objets" - "Émoticônes et personnes" - "Voyages & lieux" - "Symboles" "Échec de la création du permalien" "%1$s n’a pas pu charger la carte. Veuillez réessayer ultérieurement." "Échec du chargement des messages" @@ -196,9 +188,6 @@ "Désolé, une erreur s’est produite" "🔐️ Rejoignez-moi sur %1$s" "Salut, parle-moi sur %1$s : %2$s" - "Êtes-vous sûr de vouloir quitter ce salon ? Vous êtes la seule personne ici. Si vous partez, personne ne pourra rejoindre le salon à l’avenir, y compris vous." - "Êtes-vous sûr de vouloir quitter ce salon ? Ce salon n’est pas public et vous ne pourrez pas le rejoindre sans invitation." - "Êtes-vous sûr de vouloir quitter le salon ?" "%1$s Android" "%1$d chiffre saisi" @@ -213,43 +202,9 @@ "%d votes" "Rageshake pour signaler un problème" - "Ce message sera signalé à l’administrateur de votre serveur d’accueil. Il ne pourra lire aucun message chiffré." - "Raison du signalement de ce contenu" - "Ceci est le début de %1$s." - "Ceci est le début de cette conversation." - "Nouveau" - "Partagez des données de statistiques d’utilisation" "Échec de la sélection du média, veuillez réessayer." "Échec du traitement des médias à télécharger, veuillez réessayer." "Échec du téléchargement du média, veuillez réessayer." - "Réglages supplémentaires" - "Appels audio et vidéo" - "Incompatibilité de configuration" - "Nous avons simplifié les paramètres des notifications pour que les options soient plus faciles à trouver. - -Certains paramètres personnalisés que vous avez choisis par le passé ne sont pas affichés ici, mais ils sont toujours actifs. - -Si vous continuez, il est possible que certains de vos paramètres soient modifiés." - "Discussions directes" - "Paramétrage personnalisé par salon" - "Une erreur s’est produite lors de la mise à jour du paramètre de notification." - "Tous les messages" - "Mentions et mots clés uniquement" - "Sur les discussions directes, prévenez-moi pour" - "Lors de discussions de groupe, prévenez-moi pour" - "Activer les notifications sur cet appareil" - "La configuration n’a pas été corrigée, veuillez réessayer." - "Discussions de groupe" - "Mentions" - "Tous" - "Mentions" - "Prévenez-moi pour" - "Prévenez-moi si un message contient \"@room\"" - "Pour recevoir des notifications, veuillez modifier votre %1$s." - "paramètres du système" - "Les notifications du système sont désactivées" - "Notifications" - "Cochez si vous souhaitez masquer tous les messages actuels et futurs de cet utilisateur." "Partage de position" "Partager ma position" "Ouvrir dans Apple Maps" @@ -257,14 +212,8 @@ Si vous continuez, il est possible que certains de vos paramètres soient modifi "Ouvrir dans OpenStreetMap" "Partager cet position" "Position" - "Rageshake" - "Seuil de détection" "Version : %1$s ( %2$s )" "Ang." "Erreur" "Succès" - "Partagez des données d’utilisation anonymes pour nous aider à identifier les problèmes." - "Vous pouvez lire toutes nos conditions %1$s." - "ici" - "Bloquer l’utilisateur" 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 303ab94c50..cfd24d7c08 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -94,39 +94,19 @@ "In attesa…" "Conferma" "Attenzione" - "Attività" - "Bandiere" - "Cibi & Bevande" - "Animali & Natura" - "Oggetti" - "Faccine & Persone" - "Viaggi & Luoghi" - "Simboli" "Impossibile creare il collegamento permanente" "Caricamento dei messaggi non riuscito" "Alcuni messaggi non sono stati inviati" "Siamo spiacenti, si è verificato un errore" "Ehi, parlami su %1$s: %2$s" - "Sei sicuro di voler lasciare questa stanza? Sei l\'unica persona presente. Se esci, nessuno potrà unirsi in futuro, te compreso." - "Sei sicuro di voler lasciare questa stanza? Questa stanza non è pubblica e non potrai rientrare senza un invito." - "Sei sicuro di voler lasciare la stanza?" "%1$s Android" "%1$d membro" "%1$d membri" "Scuoti per segnalare un problema" - "Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati." - "Motivo della segnalazione di questo contenuto" - "Questo è l\'inizio di %1$s." - "Questo è l\'inizio della conversazione." - "Nuovo" - "Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente" - "Rageshake" - "Soglia di rilevamento" "Versione: %1$s (%2$s)" "it" "Errore" "Operazione riuscita" - "Blocca utente" diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index 566133a6a5..cddf19ae13 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -137,14 +137,6 @@ "Se aşteaptă…" "Confirmare" "Avertisment" - "Activități" - "Steaguri" - "Mâncare & Băutură" - "Animale și Natură" - "Obiecte" - "Fețe zâmbitoare & Oameni" - "Călătorii & Locuri" - "Simboluri" "Crearea permalink-ului a eșuat" "%1$s nu a putut încărca harta. Vă rugăm să încercați din nou mai târziu." "Încărcarea mesajelor a eșuat" @@ -155,9 +147,6 @@ "Ne pare rău, a apărut o eroare" "🔐️ Alăturați-vă mie pe %1$s" "Hei, vorbește cu mine pe %1$s: %2$s" - "Sunteți sigur că vreți să părăsiți această cameră? Sunteți singura persoană de aici. Dacă o părasiți, nimeni nu se va mai putea alătura în viitor, inclusiv dumneavoastra." - "Sunteți sigur că vrei să părăsiți această cameră? Această cameră nu este publică și nu va veti putea alătura din nou fără o invitație." - "Sunteți sigur că vreți să părăsiți camera?" "%1$s Android" "%1$d membru" @@ -170,43 +159,9 @@ "%d voturi" "Rageshake pentru a raporta erori" - "Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat." - "Motivul raportării acestui conținut" - "Acesta este începutul conversației %1$s." - "Acesta este începutul acestei conversații." - "Nou" - "Partajați datele analitice" "Selectarea fișierelor media a eșuat, încercați din nou." "Procesarea datelor media a eșuat, vă rugăm să încercați din nou." "Încărcarea fișierelor media a eșuat, încercați din nou." - "Setări adiționale" - "Apeluri audio și video" - "Nepotrivire de configurație" - "Am simplificat Setările pentru notificări pentru a face opțiunile mai ușor de găsit. - -Unele setări personalizate pe care le-ați ales în trecut nu sunt afișate aici, dar sunt încă active. - -Dacă continuați, unele dintre setările dumneavoastră pot fi modificate." - "Discuții directe" - "Setare personalizată per chat" - "A apărut o eroare în timpul actualizării setărilor pentru notificari." - "Toate mesajele" - "Numai mențiuni și cuvinte cheie" - "În conversațiile directe, anunță-mă pentru" - "În conversațiile de grup, anunțați-mă pentru" - "Activați notificările pe acest dispozitiv" - "Configurația nu a fost corectată, vă rugăm să încercați din nou." - "Discuții de grup" - "Mențiuni" - "Toate" - "Mențiuni" - "Anunță-mă pentru" - "Anunțați-mă pentru @room" - "Pentru a primi notificări, vă rugăm să vă schimbați %1$s." - "Setări de sistem" - "Notificările de sistem sunt dezactivate" - "Notificări" - "Confirmați că doriți să ascundeți toate mesajele curente și viitoare de la acest utilizator" "Partajați locația" "Distribuiți locația mea" "Deschideți în Apple Maps" @@ -214,14 +169,8 @@ Dacă continuați, unele dintre setările dumneavoastră pot fi modificate.""Deschideți în OpenStreetMap" "Distribuiți această locație" "Locație" - "Rageshake" - "Prag de detecție" "Versiunea: %1$s (%2$s)" "ro" "Eroare" "Succes" - "Distribuiți date anonime de utilizare pentru a ne ajuta să identificăm probleme." - "Puteți citi toate condițiile noastre %1$s." - "aici" - "Blocați utilizatorul" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index ddac747bcd..0b4700235b 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -9,11 +9,14 @@ "Воспроизвести" "Опрос" "Опрос завершен" + "Прочитано %1$s" "Отправить файлы" "Показать пароль" "Начать звонок" "Меню пользователя" - "Запишите голосовое сообщение. Дважды нажмите и удерживайте, чтобы записать. Отпустите, чтобы закончить запись." + "Записать голосовое сообщение." + "Остановить запись" + "Прочитано %1$s и %2$s" "Разрешить" "Добавить в хронологию" "Назад" @@ -76,10 +79,11 @@ "Все равно выйти" "Пропустить" "Начать" - "Начать чат " + "Начать чат" "Начать подтверждение" "Нажмите, чтобы загрузить карту" "Сделать фото" + "Нажмите для просмотра вариантов" "Повторить попытку" "Показать источник" "Да" @@ -88,12 +92,14 @@ "Политика допустимого использования" "Дополнительные параметры" "Аналитика" + "Оформление" "Аудио" "Пузыри" "Резервная копия чатов" "Авторское право" "Создание комнаты…" "Покинул комнату" + "Темная" "Ошибка расшифровки" "Для разработчика" "(изменено)" @@ -112,9 +118,11 @@ "Установить APK" "Идентификатор Matrix ID не найден, приглашение может быть не получено." "Покинуть комнату" + "Светлая" "Ссылка скопирована в буфер обмена" "Загрузка…" "Сообщение" + "Действия с сообщением" "Оформление сообщений" "Сообщение удалено" "Современный" @@ -134,6 +142,7 @@ "Обновление…" "Отвечает на %1$s" "Сообщить об ошибке" + "Сообщить о проблеме" "Отчет отправлен" "Редактор форматированного текста" "Комната" @@ -143,7 +152,10 @@ "Поиск человека" "Результаты поиска" "Безопасность" + "Просмотрено" "Отправка…" + "Сбой отправки" + "Отправлено" "Сервер не поддерживается" "Адрес сервера" "Настройки" @@ -154,6 +166,7 @@ "Успешно" "Рекомендации" "Синхронизация" + "Системная" "Текст" "Уведомление о третьей стороне" "Обсуждение" @@ -174,16 +187,9 @@ "Ожидание ключа расшифровки" "Вы действительно хотите завершить данный опрос?" "Опрос: %1$s" + "Подтверждение устройства" "Подтверждение" "Предупреждение" - "Деятельность" - "Флаги" - "Еда и напитки" - "Животные и природа" - "Объекты" - "Смайлы и люди" - "Путешествия и места" - "Символы" "Не удалось создать постоянную ссылку" "Не удалось загрузить карту %1$s. Пожалуйста, повторите попытку позже." "Не удалось загрузить сообщения" @@ -196,15 +202,17 @@ "Извините, произошла ошибка" "🔐️ Присоединяйтесь ко мне в %1$s" "Привет, поговори со мной по %1$s: %2$s" - "Вы уверены, что хотите покинуть эту комнату? Вы здесь единственный человек. Если вы уйдете, никто не сможет присоединиться в будущем, включая вас." - "Вы уверены, что хотите покинуть эту комнату? Эта комната не является публичной, и Вы не сможете присоединиться к ней без приглашения." - "Вы уверены, что хотите покинуть комнату?" "%1$s Android" "Введена цифра %1$d" "Ведено %1$d цифр" "Введено много цифр" + + "Прочитано %1$s и %2$d другим" + "Прочитано %1$s и %2$d другими" + "Прочитано %1$s и %2$d другими" + "%1$d участник" "%1$d участников" @@ -216,43 +224,9 @@ "%d голосов" "Rageshake сообщит об ошибке" - "Это сообщение будет передано администратору вашего домашнего сервера. Они не смогут прочитать зашифрованные сообщения." - "Причина, по которой вы пожаловались на этот контент" - "Это начало %1$s." - "Это начало разговора." - "Новый" - "Делитесь данными аналитики" "Не удалось выбрать носитель, попробуйте еще раз." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось загрузить медиафайлы, попробуйте еще раз." - "Дополнительные параметры" - "Аудио и видео звонки" - "Несоответствие конфигурации" - "Мы упростили настройки уведомлений, чтобы упростить поиск опций. - -Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны. - -Если вы продолжите, некоторые настройки могут быть изменены." - "Прямые чаты" - "Индивидуальные настройки для каждого чата" - "При обновлении настроек уведомления произошла ошибка." - "Все сообщения" - "Только упоминания и ключевые слова" - "Уведомлять меня в личных чатах" - "Уведомлять меня в групповых чатах" - "Включить уведомления на данном устройстве" - "Конфигурация не была исправлена, попробуйте еще раз." - "Групповые чаты" - "Упоминания" - "Все" - "Упоминания" - "Уведомить меня" - "Уведомить меня в @room" - "Чтобы получать уведомления, измените свой %1$s." - "Настройки системы" - "Системные уведомления выключены" - "Уведомления" - "Отметьте, хотите ли вы скрыть все текущие и будущие сообщения от этого пользователя" "Поделиться местоположением" "Поделиться моим местоположением" "Открыть в Apple Maps" @@ -260,14 +234,8 @@ "Открыть в OpenStreetMap" "Поделиться этим местоположением" "Местоположение" - "Rageshake" - "Порог обнаружения" "Версия: %1$s (%2$s)" "en" "Ошибка" "Успешно" - "Предоставлять анонимные данные об использовании, чтобы помочь нам выявить проблемы." - "Вы можете ознакомиться со всеми нашими условиями %1$s." - "здесь" - "Заблокировать пользователя" diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index b9cabf3cf8..7fe0ffd9d1 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -9,11 +9,14 @@ "Prehrať" "Anketa" "Ukončená anketa" + "Prečítal/a %1$s" "Odoslať súbory" "Zobraziť heslo" "Začať hovor" "Používateľské menu" - "Nahrávanie hlasovej správy. Nahrávanie spustíte dvojitým ťuknutím a podržaním. Uvoľnením nahrávanie ukončíte." + "Nahrať hlasovú správu." + "Zastaviť nahrávanie" + "Prečítal/a %1$s a %2$s" "Prijať" "Pridať na časovú os" "Späť" @@ -80,6 +83,7 @@ "Spustiť overovanie" "Ťuknutím načítate mapu" "Urobiť fotku" + "Klepnutím získate možnosti" "Skúste to znova" "Zobraziť zdroj" "Áno" @@ -88,12 +92,14 @@ "Zásady prijateľného používania" "Pokročilé nastavenia" "Analytika" + "Vzhľad" "Zvuk" "Bubliny" "Záloha konverzácie" "Autorské práva" "Vytváranie miestnosti…" "Opustil/a miestnosť" + "Tmavý" "Chyba dešifrovania" "Možnosti pre vývojárov" "(upravené)" @@ -112,9 +118,11 @@ "Inštalovať APK" "Toto Matrix ID sa nedá nájsť, takže pozvánka nemusí byť prijatá." "Opustenie miestnosti" + "Svetlý" "Odkaz bol skopírovaný do schránky" "Načítava sa…" "Správa" + "Akcie správy" "Štýl správ" "Správa odstránená" "Moderné" @@ -134,6 +142,7 @@ "Obnovuje sa…" "Odpoveď na %1$s" "Nahlásiť chybu" + "Nahlásiť problém" "Nahlásenie bolo odoslané" "Rozšírený textový editor" "Miestnosť" @@ -143,7 +152,10 @@ "Vyhľadať niekoho" "Výsledky hľadania" "Bezpečnosť" + "Videné" "Odosiela sa…" + "Odoslanie zlyhalo" + "Odoslané" "Server nie je podporovaný" "URL adresa servera" "Nastavenia" @@ -154,6 +166,7 @@ "Úspech" "Návrhy" "Synchronizuje sa" + "Systém" "Text" "Oznámenia tretích strán" "Vlákno" @@ -174,16 +187,9 @@ "Čaká sa na dešifrovací kľúč" "Ste si istí, že chcete ukončiť túto anketu?" "Anketa: %1$s" + "Overiť zariadenie" "Potvrdenie" "Upozornenie" - "Aktivity" - "Vlajky" - "Jedlo a nápoje" - "Zvieratá a príroda" - "Predmety" - "Smajlíky a ľudia" - "Cestovanie a miesta" - "Symboly" "Nepodarilo sa vytvoriť trvalý odkaz" "%1$s nedokázal načítať mapu. Skúste to prosím neskôr." "Načítanie správ zlyhalo" @@ -196,15 +202,17 @@ "Prepáčte, vyskytla sa chyba" "🔐️ Pripojte sa ku mne na %1$s" "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" - "Ste si istí, že chcete opustiť túto miestnosť? Ste tu jediná osoba. Ak odídete, nikto sa do nej nebude môcť v budúcnosti pripojiť, vrátane vás." - "Ste si istí, že chcete opustiť túto miestnosť? Táto miestnosť nie je verejná a bez pozvania sa do nej nebudete môcť vrátiť." - "Ste si istí, že chcete opustiť miestnosť?" "%1$s Android" "%1$d zadaná číslica" "%1$d zadané číslice" "%1$d zadaných číslic" + + "Prečítal/a %1$s a %2$d ďalší" + "Prečítal/a %1$s a %2$d ďalší" + "Prečítal/a %1$s a %2$d ďalších" + "%1$d člen" "%1$d členovia" @@ -216,43 +224,9 @@ "%d hlasov" "Zúrivo potriasť pre nahlásenie chyby" - "Táto správa bude nahlásená správcovi vášho domovského servera. Nebude môcť prečítať žiadne šifrované správy." - "Dôvod nahlásenia tohto obsahu" - "Toto je začiatok %1$s." - "Toto je začiatok tejto konverzácie." - "Nové" - "Zdieľať analytické údaje" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa nahrať médiá, skúste to prosím znova." - "Ďalšie nastavenia" - "Audio a video hovory" - "Nezhoda konfigurácie" - "Zjednodušili sme Nastavenia oznámení, aby ste ľahšie našli možnosti. - -Niektoré vlastné nastavenia, ktoré ste si nastavili v minulosti, sa tu nezobrazujú, ale sú stále aktívne. - -Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť." - "Priame konverzácie" - "Vlastné nastavenie pre konverzácie" - "Pri aktualizácii nastavenia oznámenia došlo k chybe." - "Všetky správy" - "Iba zmienky a kľúčové slová" - "Pri priamych rozhovoroch ma upozorniť na" - "Pri skupinových rozhovoroch ma upozorniť na" - "Povoliť oznámenia na tomto zariadení" - "Konfigurácia nebola opravená, skúste to prosím znova." - "Skupinové rozhovory" - "Zmienky" - "Všetky" - "Zmienky" - "Upozorniť ma na" - "Upozorniť ma na @miestnosť" - "Ak chcete dostávať oznámenia, zmeňte prosím svoje %1$s." - "nastavenia systému" - "Systémové oznámenia sú vypnuté" - "Oznámenia" - "Označte, či chcete skryť všetky aktuálne a budúce správy od tohto používateľa" "Zdieľať polohu" "Zdieľať moju polohu" "Otvoriť v Apple Maps" @@ -260,14 +234,8 @@ Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť.""Otvoriť v OpenStreetMap" "Zdieľajte túto polohu" "Poloha" - "Zúrivé potrasenie" - "Prahová hodnota detekcie" "Verzia: %1$s (%2$s)" "sk" "Chyba" "Úspech" - "Zdieľajte anonymné údaje o používaní, aby sme mohli identifikovať problémy." - "Môžete si prečítať všetky naše podmienky %1$s." - "tu" - "Zablokovať používateľa" diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index 37b372f46b..6f6fa08892 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -152,14 +152,6 @@ "投票:%1$s" "確認" "警告" - "活動" - "旗幟" - "食物與飲料" - "動物與大自然" - "物品" - "表情與人物" - "旅行與景點" - "標誌" "無法建立永久連結" "%1$s無法載入地圖。請稍後再試。" "無法載入訊息" @@ -168,9 +160,6 @@ "%1$s 沒有權限存取您的位置。請在下方開啟權限。" "有些訊息尚未傳送" "嘿,來 %1$s 和我聊天:%2$s" - "您確定要離開聊天室嗎?這裡只有您一個人。如果您離開了,包含您在內的所有人都無法再進入此聊天室。" - "您確定要離開聊天室嗎?此聊天室不是公開的,如果沒有收到邀請,您無法重新加入。" - "您確定要離開聊天室嗎?" "%1$s Android" "%1$d 位成員" @@ -178,22 +167,7 @@ "%d 票" - "檢舉這個內容的原因" - "新訊息" - "分享分析數據" "無法上傳媒體檔案,請稍後再試。" - "其他設定" - "私訊" - "更新通知設定時發生錯誤。" - "所有訊息" - "僅限提及與關鍵字" - "在這個裝置上開啟通知" - "群組聊天" - "提及" - "提及" - "系統設定" - "已關閉系統通知" - "通知" "分享位置" "分享我的位置" "在 Apple Maps 中開啟" @@ -205,8 +179,4 @@ "zh-tw" "錯誤" "成功" - "分享匿名的使用數據以協助我們釐清問題。" - "您可以到%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 0aaeeb2b7b..c2f969551c 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -9,11 +9,14 @@ "Play" "Poll" "Ended poll" + "Read by %1$s" "Send files" "Show password" "Start a call" "User menu" - "Record voice message. Double tap and hold to record. Release to end recording." + "Record voice message." + "Stop recording" + "Read by %1$s and %2$s" "Accept" "Add to timeline" "Back" @@ -80,6 +83,7 @@ "Start verification" "Tap to load map" "Take photo" + "Tap for options" "Try again" "View source" "Yes" @@ -88,12 +92,14 @@ "Acceptable use policy" "Advanced settings" "Analytics" + "Appearance" "Audio" "Bubbles" "Chat backup" "Copyright" "Creating room…" "Left room" + "Dark" "Decryption error" "Developer options" "(edited)" @@ -112,9 +118,11 @@ "Install APK" "This Matrix ID can\'t be found, so the invite might not be received." "Leaving room" + "Light" "Link copied to clipboard" "Loading…" "Message" + "Message actions" "Message layout" "Message removed" "Modern" @@ -134,6 +142,7 @@ "Refreshing…" "Replying to %1$s" "Report a bug" + "Report a problem" "Report submitted" "Rich text editor" "Room" @@ -143,7 +152,10 @@ "Search for someone" "Search results" "Security" + "Seen by" "Sending…" + "Sending failed" + "Sent" "Server not supported" "Server URL" "Settings" @@ -154,6 +166,7 @@ "Success" "Suggestions" "Syncing" + "System" "Text" "Third-party notices" "Thread" @@ -172,20 +185,11 @@ "Voice message" "Waiting…" "Waiting for this message" - "Report a problem" "Are you sure you want to end this poll?" "Poll: %1$s" "Verify device" "Confirmation" "Warning" - "Activities" - "Flags" - "Food & Drink" - "Animals & Nature" - "Objects" - "Smileys & People" - "Travel & Places" - "Symbols" "Failed creating the permalink" "%1$s could not load the map. Please try again later." "Failed loading messages" @@ -198,14 +202,15 @@ "Sorry, an error occurred" "🔐️ Join me on %1$s" "Hey, talk to me on %1$s: %2$s" - "Are you sure that you want to leave this room? You\'re the only person here. If you leave, no one will be able to join in the future, including you." - "Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite." - "Are you sure that you want to leave the room?" "%1$s Android" "%1$d digit entered" "%1$d digits entered" + + "Read by %1$s and %2$d other" + "Read by %1$s and %2$d others" + "%1$d member" "%1$d members" @@ -215,41 +220,9 @@ "%d votes" "Rageshake to report bug" - "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages." - "Reason for reporting this content" - "This is the beginning of %1$s." - "This is the beginning of this conversation." - "New" - "Share analytics data" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." - "Additional settings" - "Audio and video calls" - "Configuration mismatch" - "We’ve simplified Notifications Settings to make options easier to find. Some custom settings you’ve chosen in the past are not shown here, but they’re still active. - -If you proceed, some of your settings may change." - "Direct chats" - "Custom setting per chat" - "An error occurred while updating the notification setting." - "All messages" - "Mentions and Keywords only" - "On direct chats, notify me for" - "On group chats, notify me for" - "Enable notifications on this device" - "The configuration has not been corrected, please try again." - "Group chats" - "Mentions" - "All" - "Mentions" - "Notify me for" - "Notify me on @room" - "To receive notifications, please change your %1$s." - "system settings" - "System notifications turned off" - "Notifications" - "Check if you want to hide all current and future messages from this user" "Share location" "Share my location" "Open in Apple Maps" @@ -257,15 +230,9 @@ If you proceed, some of your settings may change." "Open in OpenStreetMap" "Share this location" "Location" - "Rageshake" - "Detection threshold" "Version: %1$s (%2$s)" "en" "en" "Error" "Success" - "Share anonymous usage data to help us identify issues." - "You can read all our terms %1$s." - "here" - "Block user" diff --git a/libraries/voicerecorder/api/build.gradle.kts b/libraries/voicerecorder/api/build.gradle.kts index bed69b7d28..f704935bbd 100644 --- a/libraries/voicerecorder/api/build.gradle.kts +++ b/libraries/voicerecorder/api/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") alias(libs.plugins.anvil) } diff --git a/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt b/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt index 42035615d5..2bb4f575e3 100644 --- a/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt +++ b/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt @@ -16,9 +16,11 @@ package io.element.android.libraries.voicerecorder.api +import androidx.compose.runtime.Immutable import java.io.File import kotlin.time.Duration +@Immutable sealed interface VoiceRecorderState { /** * The recorder is idle and not recording. diff --git a/libraries/voicerecorder/impl/build.gradle.kts b/libraries/voicerecorder/impl/build.gradle.kts index 6ebfb28997..d73f06b868 100644 --- a/libraries/voicerecorder/impl/build.gradle.kts +++ b/libraries/voicerecorder/impl/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { api(libs.opusencoder) implementation(libs.dagger) + implementation(projects.appconfig) implementation(projects.libraries.matrix.api) implementation(projects.libraries.core) implementation(projects.libraries.di) diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt index 042ecacfbe..5a1cfe537b 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.voicerecorder.impl import android.Manifest import androidx.annotation.RequiresPermission import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appconfig.VoiceMessageConfig import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.di.RoomScope @@ -46,7 +47,6 @@ import java.io.File import java.util.UUID import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.minutes import kotlin.time.TimeSource @SingleIn(RoomScope::class) @@ -94,7 +94,7 @@ class VoiceRecorderImpl @Inject constructor( val elapsedTime = startedAt.elapsedNow() - if (elapsedTime > 30.minutes) { + if (elapsedTime > VoiceMessageConfig.maxVoiceMessageDuration) { Timber.w("Voice message time limit reached") stopRecord(false) return@record diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt index e3b2100ccc..d67a44f6f3 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt @@ -20,6 +20,7 @@ import android.media.AudioFormat import android.media.MediaRecorder import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.appconfig.VoiceMessageConfig import io.element.android.libraries.voicerecorder.api.VoiceRecorderState import io.element.android.libraries.voicerecorder.impl.audio.Audio import io.element.android.libraries.voicerecorder.impl.audio.AudioConfig @@ -77,8 +78,8 @@ class VoiceRecorderImplTest { voiceRecorder.startRecord() assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(0.minutes, listOf(1.0f))) - timeSource += 30.minutes - assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(30.minutes, listOf())) + timeSource += VoiceMessageConfig.maxVoiceMessageDuration + assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(VoiceMessageConfig.maxVoiceMessageDuration, listOf())) timeSource += 1.milliseconds assertThat(awaitItem()).isEqualTo( @@ -86,7 +87,7 @@ class VoiceRecorderImplTest { file = File(FILE_PATH), mimeType = "audio/ogg", waveform = List(100) { 1f }, - duration = 30.minutes, + duration = VoiceMessageConfig.maxVoiceMessageDuration, ) ) } diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index 9986d490fa..c820902905 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -27,7 +27,6 @@ dependencies { implementation(libs.android.gradle.plugin) implementation(libs.kotlin.gradle.plugin) implementation(platform(libs.google.firebase.bom)) - // FIXME: using the bom ^, it should not be necessary to provide the version v... - implementation("com.google.firebase:firebase-appdistribution-gradle:4.0.1") + implementation(libs.firebase.appdistribution.gradle) implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) } diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 85018b4b01..8dac5b7612 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 3 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 1 +private const val versionPatch = 2 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index c6786ef8b3..f6ec8b7cf1 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -42,16 +42,16 @@ fun DependencyHandlerScope.composeDependencies(libs: LibrariesForLibs) { val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) androidTestImplementation(composeBom) - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.material:material") - implementation("androidx.compose.material3:material3") - implementation("androidx.compose.material:material-icons-extended") - implementation("androidx.compose.ui:ui-tooling-preview") + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.material.icons) + implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.activity.compose) - debugImplementation("androidx.compose.ui:ui-tooling") - debugImplementation("androidx.compose.ui:ui-test-manifest") + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.test.manifest) implementation(libs.showkase) - implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5") + implementation(libs.kotlinx.collections.immutable) } private fun DependencyHandlerScope.addImplementationProjects( diff --git a/tests/konsist/build.gradle.kts b/tests/konsist/build.gradle.kts index 6658c0c5e0..1ce4e9f232 100644 --- a/tests/konsist/build.gradle.kts +++ b/tests/konsist/build.gradle.kts @@ -25,7 +25,7 @@ android { dependencies { val composeBom = platform(libs.androidx.compose.bom) testImplementation(composeBom) - testImplementation("androidx.compose.ui:ui-tooling-preview") + testImplementation(libs.androidx.compose.ui.tooling.preview) testImplementation(libs.test.junit) testImplementation(libs.test.konsist) testImplementation(libs.test.truth) diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index d906063ae3..9ecf90ebe6 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -59,7 +59,7 @@ class KonsistClassNameTest { .substringBefore("<") .removeSuffix("?") .replace(".", "") - it.name.endsWith("Provider") && it.name.contains(providedType) + it.name.endsWith("Provider") && (it.name.contains("IconList") || it.name.contains(providedType)) } } } diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_4_null_0,NEXUS_5,1.0,en].png index 37eb2546da..26a31135df 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b513c5dc3dcab57aa0161dda218a23bca21591e5d3e74c57affded1ee9160ba8 -size 23575 +oid sha256:79f771e31445db99b25df34ae686a940d0bb7e44745abe5ed2da79bf258122ed +size 23090 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_5_null_0,NEXUS_5,1.0,en].png index 0f8d42fc4d..4b79f83582 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_5_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_5_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a326bbd2a5b8682aba465f5ffe988243893121d4dcc615159982cc1d3a6c52f -size 22039 +oid sha256:e83a879d73aec0356e2a5a2db628183108b2d045a9bbb72d86324419d3407b0f +size 21460 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-2_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-2_2_null,NEXUS_5,1.0,en].png index 8bff737507..bcc1a519e6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-2_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-2_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:812de54cf01d30f00db9606f7907f106453eba5f9bba4b9fd38a61d8944e780c -size 288364 +oid sha256:261face86126e646561ef480904b0af7e7aaf990e5a6fd198d0d953f4a613e11 +size 288233 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-2_3_null,NEXUS_5,1.0,en].png index 565e3dd06d..d61546f9d8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-2_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-2_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc89eabc29dd72962b2ccece5bc003ffaf233b81817acf5b59f4574d4774af41 -size 391205 +oid sha256:c935cbdc16bead41781b486da0427a2d6df3530fe8f35a8bb031c660db8e7e73 +size 391103 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_0,NEXUS_5,1.0,en].png index c162c61c2d..6eb4f902e9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:322558b9928b3e408320e9c002439b0ebd6b09a86c03cd3668ffd64f03d0b5c8 -size 22317 +oid sha256:4fe166a1cb630b39bf4d21f78da1cca582a466e7cf50a879546db9ccb123cb7e +size 22385 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_1,NEXUS_5,1.0,en].png index eef86b480c..80f5a454bd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3555a707ef787466d493247a5198131469c3e61d4f2aaf10e7a86e99f7fcccd6 -size 41368 +oid sha256:2ee8c200c15a0b9d9aefb2de0f3dec576742ac4fe59c71ce0ee016cf23415d5a +size 41422 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_2,NEXUS_5,1.0,en].png index 764d0fbdd5..cb9b8a183b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d902d99d379baa2f4fb0eebb9479fc6c497cf1ed62059cda799f4acea72efd7 -size 39897 +oid sha256:cc7691f9c3643ad352bf687a898512b4adb4ea0b12e536ebda3bce7c8f2512d1 +size 39954 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_3,NEXUS_5,1.0,en].png index c162c61c2d..6eb4f902e9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:322558b9928b3e408320e9c002439b0ebd6b09a86c03cd3668ffd64f03d0b5c8 -size 22317 +oid sha256:4fe166a1cb630b39bf4d21f78da1cca582a466e7cf50a879546db9ccb123cb7e +size 22385 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_4,NEXUS_5,1.0,en].png index 50c97e76a1..f6d0210995 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Day-0_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d3b5105a703083f7ef66c74e54232a4626e47c2409637d72170381212e2becd -size 22456 +oid sha256:7e083f35c919d664603c4edeb24a32e9a9e594a480d4b4ebabd92863a77d7af3 +size 22504 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_0,NEXUS_5,1.0,en].png index 6fb5eeaed2..cfd15d6bad 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3700451074385a0cf8601faa11432124b60c05060cf79459b8aab35cefa0bc0c -size 20723 +oid sha256:b1cd421332c29fe6ea004f95c113fa8bfb0ccc64b34c77b5d03c93debf6fd758 +size 20780 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_1,NEXUS_5,1.0,en].png index 20dfafc434..0a931fcc53 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e7a461733795c789f0f6507874fbcb7d911b2b93ad0fd44a446d6882def8141 -size 38048 +oid sha256:38539e5bf70c45e79cea35d6d5cd15ac1476a51083a177d82eb2765673733fc0 +size 38110 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_2,NEXUS_5,1.0,en].png index 144654d0e5..5123e9e4fe 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6d34735c7ad55744a0309897d826d6fd2d935ee2ffb7a4f6192d1b5b5e9925e -size 36499 +oid sha256:7d9fb2ef1976c318c4dc7191a10d482e98ceb8651a90cdef933c100cff4bdb6a +size 36560 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_3,NEXUS_5,1.0,en].png index 6fb5eeaed2..cfd15d6bad 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3700451074385a0cf8601faa11432124b60c05060cf79459b8aab35cefa0bc0c -size 20723 +oid sha256:b1cd421332c29fe6ea004f95c113fa8bfb0ccc64b34c77b5d03c93debf6fd758 +size 20780 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_4,NEXUS_5,1.0,en].png index d99081af1a..eab3525a36 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_SendLocationView_null_SendLocationView-Night-0_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c9bba26f9a57eeb103087ed9b630eb9964983db2692e1019a3d6b4fc66c3296 -size 20888 +oid sha256:0e197302e95daa0a1bb1461e0564afd234723db0300521c623cc3640c56f431b +size 20938 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png index af7782f5d7..cd2c26834e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85692ea3847fe5a79f955bb71153ccb6e5b24cf451292c7b9d56f26e2eff95b7 -size 28840 +oid sha256:1aecc7465d731f46d32ea9222022b6f640a3aa86c81a77c93487d5f35bfcad1b +size 28689 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png index ac13aa8b52..8e3f139608 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e7684929f93f437c50549b309e1ba75b95cddb506ad9c8ef97373d0552bddfb -size 39017 +oid sha256:28300c9143f5701e46ed3b8d77e93d34584f82949a335003bab685ffb4cd1434 +size 38850 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png index 18aaab9fd1..ae317f2718 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56f71535fd676a86e79b0e987701d34c85c96276e5338f77117b13e1b3a05144 -size 45715 +oid sha256:f79e915bd6c47b424fd34dfa38130a083b00288e0f6eb15c685d78bb9ce29e0b +size 45508 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png index 2f2e8ea4eb..d607db48b1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f594c95c1de47b3fa74add85f87b70005c54ed9230d29967742a8a8291b6844 -size 46255 +oid sha256:f332e7d12d5f64a1704d17e5adc42e9eb0ea9ed21a2d116955e2e0e276153182 +size 46090 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png index 0c6cb3114f..9f763b3134 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d398070330d5c66358b7ac17d4e1b93795fb9001ffc6eabe533d3bae86de7d58 -size 39810 +oid sha256:ecc28b4311d27eaaa1c32128ed3c1f3ca03c5bb0c1390b8685c82caed2d6bc3b +size 39662 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png index c458b9a7b8..8907e18435 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d32becec465a3f01e35aac9ebbeb8f48507cc2146f35b8e6c6393bbd410cca3e -size 39370 +oid sha256:6b269d050154e28ebf9b1eaa51856e1199490ea7aecc6439fc567bd3b8cbb7d8 +size 39224 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png index 975fb15bc4..ecbc7a1bad 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8e50be96a1b204b8187fd76802650569a740a927739135a6b6307481f2d5568 -size 40453 +oid sha256:19f262d8c0786fe8e1781c5934e7ff86f89d36decb2d0566ca35772962b3866c +size 40306 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png index 74448d6967..be972081fb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:979138753000ada021a67f4bc14a89b912430c4331ac69aa702b2e50c050d7cb -size 41200 +oid sha256:c3ca04db3a4e6096aa75e547fd059a199aca4b91a31b36e3ae84254a08fbfbf1 +size 41043 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png index 3cb12c4c94..718abd867e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e06e1a32d8c9d06a307a5116a396a03308d5e5486f911d34002139485e7d9ffe -size 28176 +oid sha256:d847380eed52b6842f2926143e189208f11baf8ce906acae23dcf797384cd1c1 +size 28015 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png index ee2fa7b46f..6dd671d42a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5df2d3b6e4698867277712214fa526e08a9bf790140cd000cfe2bf94c70e77f4 -size 27525 +oid sha256:eabb7309eef1e5bf0d496e053c1531f44262e109b3847e2de5e60ffd6df4e942 +size 27505 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png index 2e051be73c..90f1fbbf34 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb4be22a56036d53f7c394c3829a9f8f09820276d7529ace0d3731965a8a92f9 -size 37556 +oid sha256:9740a5e50f62b47a4daccccfb795b6844033b6578227b93584b3e29a949c0448 +size 37510 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png index ea837fc372..78a31c272b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:369e8b4e3f741476cfa5ff7e3f9d5d1e1399fcf3494fadb95540129436ef4d68 -size 43744 +oid sha256:6460478383d3dfd4043b129743c508ca6cc7bc64e0635dee51d1de7ba073d3c7 +size 43672 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png index 40a48bf88f..ff0850af1e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd4fad16706caabe092b40fc2ffbc748db5b763ba649f88fe7463ab17f73ca4e -size 44329 +oid sha256:b561a070230bb3fb50a189a969cda1fa723fb519915c8af0b21ec516a71cc630 +size 44220 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png index cb01f9579d..6476423b27 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b2aaa54bf065e6496957c3a60d61336f65e6b76f97e5dbc773284ee49fac91d -size 37988 +oid sha256:88c8e6bcecef8c8da37603cea72dfdeab1d975d674ff049d8994ab12e9799451 +size 37939 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png index dbe1f4d267..4ee31f59c8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a99566280864e66ec327d2d6d8016c74b12f1c62ff27fd0d2d4ba0109ae9756a -size 37747 +oid sha256:2a639070cb7293c750b153489d1b88a946db8231f818063da86ea38bce282863 +size 37693 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png index 872af5733e..85a62da13d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83f6cbcdbc00c49aed762339769e0f639604286406c46b1336b7757e2aad8b41 -size 38641 +oid sha256:d1a18f1544bd6b2f9592acf3e0347792136116b0c5159591cc6bc4a6071638a3 +size 38557 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png index 8d45efac8c..91639aed2f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:752abe37a810cc8e4b36ae5f4c4aa38c9f22757b6458f272ba991050c69aee53 -size 39409 +oid sha256:0c18f92cda3af1e3a5524fd6191b45810e20eb3449335fa357e390465e051be4 +size 39309 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png index 811cb7e4cd..5d33fdbe24 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c53fdbe427ddb7088d9bd8f9b46a1281d145fd6f9ccc3a7de339750e3079075 -size 26498 +oid sha256:04c5ab1c0d9abebd7e189beb5cebecd5dfaa27a90a24ff499ad72a2ab4d003be +size 26307 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Day-4_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Day-4_4_null,NEXUS_5,1.0,en].png index 920d470185..d743ef4fd4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Day-4_4_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Day-4_4_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b426d17bd2623e57d21ae8bb1c5f47247a729bb3c402da830dde4ca7554e8319 -size 26026 +oid sha256:4d3bd52edfa46104861e6362cdd608988d5ad1f3efba1e4318c0df4a5a129652 +size 26042 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Night-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Night-4_5_null,NEXUS_5,1.0,en].png index b45fb3e146..331f2958d9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Night-4_5_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_AttachmentSourcePickerMenu_null_AttachmentSourcePickerMenu-Night-4_5_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c3e621c437cbc681672bd938fc8e5f3e9c956463d7d1b185e3aabe6717fd024 -size 23729 +oid sha256:8a06d8d83e44809aa1f205a425de0f92dcd556ddcaea0904074cb05cd7ab87fd +size 23750 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png index 3ec6004b6d..521717b05b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d5209087d7841a80f7213f26aaf8322cc5fee03466b9059e06b0daf5f489c1b -size 10012 +oid sha256:5cd4e624c0ed8abe5d22d60992f64f81e91fa41ea7d8772acc5e3832eadbcbb7 +size 10581 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png index dcddb1ae01..3dd0888ff4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b8fca1de81e424f01a518e3a2547b58c0e9eb7249327f3fcdb1cf62ab655972 -size 9429 +oid sha256:01eda47acbf273bf61e60dbbc466aac5e9a4eac7690af4cde2e06e7e1f190bcf +size 10152 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_ProgressButton_null_ProgressButton-Day-43_43_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_ProgressButton_null_ProgressButton-Day-43_43_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5b8f0832b2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_ProgressButton_null_ProgressButton-Day-43_43_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:726cb736f33634d3b7efd1ae01b5f7808ffba8933ceda115b3b368070f26ede7 +size 5474 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_ProgressButton_null_ProgressButton-Night-43_44_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_ProgressButton_null_ProgressButton-Night-43_44_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1fffb2a53c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_ProgressButton_null_ProgressButton-Night-43_44_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99d85977724a11c52831391fb2d1bcb5bcd2fd697a9e1073b067cfc25a9cbcc7 +size 5437 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-29_29_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-29_29_null,NEXUS_5,1.0,en].png index be47b705f5..df5304ac9f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-29_29_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-29_29_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78884f6cdde3d493293186d078360d51d894e42f134b44729a9d44b7d9e1b260 -size 9822 +oid sha256:aa891e34644dca91750381bfdfa87b951f3326606ae517274e153c7959d20501 +size 10012 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-29_30_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-29_30_null,NEXUS_5,1.0,en].png index 63f4ed66bc..3db0f7a27b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-29_30_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-29_30_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3ee25c2e1aed91646e07465eabaa7419019f376b5870af6653016dc2403df0d -size 9701 +oid sha256:53286f8a92a448729ba15bb45076de77d0506112c6ca65ff5c35046314066552 +size 9879 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Day-42_42_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Day-42_42_null,NEXUS_5,1.0,en].png index edf4815074..7bda39becb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Day-42_42_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Day-42_42_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:327a1fd51b242e1547878e58f32e6511e013cb440f4cd457a20e179ca3a1ed39 -size 46377 +oid sha256:cb6bf3cc0868def05297678d308ecfda99f36239bda2ad67a144c5054cd09f19 +size 45680 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Night-42_43_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Night-42_43_null,NEXUS_5,1.0,en].png index a954c65665..b916f8b832 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Night-42_43_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceViewUnified_null_TimelineItemVoiceViewUnified-Night-42_43_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c50b273463475a1ecdeb1393f688da883448be9fefa28b8ea15232a5ef7c601 -size 45417 +oid sha256:5f9f5fbbaa510904e7f21fee456789ba9154dc7fd927caf5719daf4bce49519d +size 44517 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_0,NEXUS_5,1.0,en].png index 1e6264eeac..82c25db6e1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f19f38428d2e17f2cb7a8b3de1af39cf01e48e5c854d3a64208a78c4687b867 -size 6101 +oid sha256:9b7baba714a54c9be4611677b938705277e2f967c7361af4fd8e16651b732cc0 +size 5729 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_10,NEXUS_5,1.0,en].png index 2e123fb511..0ca95a9c4a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef25bb048d7feb642509f1d5bbc646b867b89264efa32e77730650f31f0233ad -size 9895 +oid sha256:111c3aafacfbb3fd16421c74eae2b3c6ba7c2f6f3b2203c0ab91a226e5015e2a +size 9529 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_5,NEXUS_5,1.0,en].png index 2ec7b91240..733df194eb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Day-41_41_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9ea30725d03749028f907e341ee09bb98a67cc6bda76757f7b3d266fed3722f -size 7177 +oid sha256:bcb3a451a7ae4271fc2acbda8ba5f40a21167bb36a81d14b9006c68baddff3c0 +size 6790 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_0,NEXUS_5,1.0,en].png index 223f192095..2f0181bf55 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bfffebea253932b08a308ac098a392910233e4a8524a898b459842849854c91 -size 6036 +oid sha256:6780dcb4537aed0168fecc8a7df77d17b38d57f5e2fa697a353728e9bd28aa59 +size 5697 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_10,NEXUS_5,1.0,en].png index 1da72edd97..a71c8488c8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca13f10ca5fd6194829c3cef02fa5cd0c00790f23c82d2cf70ac5d1d58babb54 -size 9656 +oid sha256:a6d969780e06a96c68cc547f7731d839d4eb3855bc39bacd7e7fc95692bc8544 +size 9324 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_5,NEXUS_5,1.0,en].png index aa9de6ea89..a57555c928 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemVoiceView_null_TimelineItemVoiceView-Night-41_42_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4a2824fdc49adb0e3fc155e7da319414544a87dc083bdece78e85ba72669d78 -size 7155 +oid sha256:12ad14137042e62096d78434481b73a9e8668a617f5b465ee97695fc0a008382 +size 6795 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Day-43_43_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Day-44_44_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Day-43_43_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Day-44_44_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Night-43_44_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Night-44_45_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Night-43_44_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_GroupHeaderView_null_GroupHeaderView-Night-44_45_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-44_44_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Day-45_45_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-44_45_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_HtmlDocument_null_HtmlDocument-Night-45_46_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Day-45_45_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Day-46_46_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Day-45_45_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Day-46_46_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Night-45_46_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Night-46_47_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Night-45_46_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_SheetContent_null_SheetContent-Night-46_47_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b829f5c0e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2d69362d225e97cf9ca07e071de06c89d8ca4694214dfebde90b771a2c11f54 +size 6707 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..aeaf2f7dd7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5af53d9bbf0fb985efeb48b3e8f1c808e10fff496d6aafc068e7e50cfafcd798 +size 12367 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6ddbf182b0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9779c9c980c32b3e8ede34e5f9e7beeca6e0000fc77e1fc1cf0a8c0eb1da0be7 +size 17760 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d8023b8b76 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbcfa1b91e13109969c31b6e2d09c5099a1c27ed15912401db7ac946323a1027 +size 22729 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2ac6c8fa54 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97fd46b3b79c90a4104c2fd4e9f5667abf67d998d9f7136b9c18da920af4cb99 +size 27836 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8881b0cfed --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e767451a900ff3629047e9931784c1b5edd3a52e44dd4d172107c71fe92671d3 +size 33561 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..366399824d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63620fd5bfea92c632a0a4b1d88eabd51a973e7e4281af2b662b8955cc209e0a +size 6549 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..65bb8111f9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a8a1900a56b87bb96135cbbfe92e504b486775b5b0ba02bb6626a351081ef37 +size 12272 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..13a2cf3633 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63e229114afc8317c1690f817e4ba4a275c581c9f107c75e88c99728c6698219 +size 17755 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cd31a0814a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:175c2bed417fdfd3c80b8ce3079675decc3a27cd8df6d06c00db0015d193cb4e +size 22808 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b7eec1e1a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb15b22c4fc99d3f7c655e85d1e2c1b9d0ae4478908a4f40234a8a4651df7d49 +size 28080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7372f4b2a7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21078a2cfa738bc3a2a7f36c52ea7c6ff9c83a9885f6059fe472121f08a08c05 +size 33681 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..601cda52a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec996e41aaecde031e8b4568f9dbe08b359b4390589b7e25c8da084a0fa9fc93 +size 5502 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e13c280efc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:379c809e565791ae199367bf9e7fe5d2e7767bbd5f33f51309d0caacc976dd5f +size 5356 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..601cda52a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec996e41aaecde031e8b4568f9dbe08b359b4390589b7e25c8da084a0fa9fc93 +size 5502 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..54d5ed96d5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61d4e36b624371171b6ee1909318dcc2ba96c55b42a92fb880b25abbbff48645 +size 5080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c9e7c2f15d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:821606acb7dcd9bdcad17bff0c33268f2ab22569e846e610c4401d852e17be2e +size 5628 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..173509112b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3552965164107b4a6881a5834bef98dfba2494c7c2186f6a6b8315a64c0c0d6b +size 6233 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..523cbf9120 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fedc10863be4b1777614ecf777ee24b43a4d9fb48059833cd08a122e6cec221 +size 6410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2de7d24afd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b980fa478c1d508284323e094281db096a7b6360339493c59b0c1fd69c2288c3 +size 6609 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c7bd8a176b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3663e96a724a0a75e693c4c516e6adedc4c20967604c6931142972fcee8fe488 +size 5475 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..86623d35e8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fec37a2d969d74fba0c26a0ff179f204c52c8e3d0c4a2b2d189ba071e4428887 +size 5339 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c7bd8a176b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3663e96a724a0a75e693c4c516e6adedc4c20967604c6931142972fcee8fe488 +size 5475 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ad0545303d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d33ed61dd503fa4b3956e27cc7a06090c6eb589cf5dbdfd0dd781dfd335b774d +size 5213 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7bb404a775 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d29f6726900317e6869ae00759b9a861a1036a1a8280621bc1ed47c37752a18 +size 5901 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7c235a780c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e69990e066a3eb36e66820e2414397fdb415151ba76d9c062588e58e01e6e97 +size 6425 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..234380bde4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3cfd9aaf78a8e66993d269e3d5b49966eb067f2df1dc2582ba066d5e6a086b0 +size 6608 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8d0f250d51 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91961cd20e51f2065a48720fe3a5067d3f640a7e87d5f369eb45842ba1f256f7 +size 6811 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-46_46_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-46_46_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-46_46_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-46_46_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-46_47_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-46_47_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-46_47_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-46_47_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-47_47_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-47_47_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-47_47_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-47_47_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-47_47_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-47_47_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-47_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-47_48_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-47_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-47_48_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-47_48_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-47_48_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-48_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-48_48_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-48_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-48_48_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-48_49_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-48_49_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-48_49_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-48_49_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-52_52_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-52_52_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e7d317471e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-52_52_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c9d54aa37eacbd7b563ce2ea460e6a548da98fb4cffe6dd765f5e7ce8037dfd +size 5364 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-52_53_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-52_53_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..80cdadfb7f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-52_53_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:676173205d2716d0f57354dc48e53e0e50e65e831a228845721a624b64d905ca +size 5365 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-53_53_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-53_53_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0f29064f65 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-53_53_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a0fbf492a08f213d4603e9a7d4a0670cd4174441faa1a7b54e6793c234fde6a +size 15830 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-53_54_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-53_54_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0075b63b12 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-53_54_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14316aa45f398154680fde84673a4d1117c49317a0e81641d52184da7b5ff2e1 +size 15472 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-49_49_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-54_54_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-49_49_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-54_54_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-49_50_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-54_55_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-49_50_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-54_55_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-50_50_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-55_55_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-50_50_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-55_55_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-50_51_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-55_56_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-50_51_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-55_56_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Day-8_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Day-8_8_null_1,NEXUS_5,1.0,en].png index ea99de4248..96deb94eef 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Day-8_8_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Day-8_8_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56d2a4fe8ceadfb945622519910e13fbe3422561745efc4fe0362eb599fcb2ac -size 73633 +oid sha256:f0fa1ad2fe7085badb03368def655e9f175c497e91eb1de440df8fb64b2fe858 +size 74398 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Night-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Night-8_9_null_1,NEXUS_5,1.0,en].png index 62c10e1bab..529f9ad95f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Night-8_9_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_TimelineView_null_TimelineView-Night-8_9_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55fbd1123d267fea510345fc272a1b1a9bda7a8222eebf7e9d96a8202f071813 -size 70610 +oid sha256:d5537a6de5da545df178a198a402dd5fe67f08ff64f448c3ecd6245484766cc1 +size 71354 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_11,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2251ff798a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_11,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:083476cb480c6641924331689121581300247fd9d1f61c155a35389a07601c69 +size 51237 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_11,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a4bc4d4f6b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_11,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33b0fb2e0990a008ea6699a0d0fc59478bc3ce755637584af4e2577fc007e2c3 +size 45809 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_0,NEXUS_5,1.0,en].png index 87469e8d76..a8b6a93bdd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20ed65388204495106f428d0445c3091bac5b953e46931d21d5ee73c5ae876cc -size 37889 +oid sha256:d6bc45093c8a96a5446c18b42590d6b866a40ea34268e4764a29505dcf97965c +size 42565 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_1,NEXUS_5,1.0,en].png index 4588e60de3..05647b9c50 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bd91af47b7b336b5bd1abf52ebfcd588d29cb8b0bbb67115fbeaac8ba5bb469 -size 37473 +oid sha256:290bb5d6134171c6d3f343ee3bad95d244175fd18696c5c27a526b9d61948d5f +size 42232 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_2,NEXUS_5,1.0,en].png index d9d2d0a9f5..e8a91dae18 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40bddbf21b6ca1d3f2647ff5168bd7b620b36d5cf3c88f15bab148963c3426b7 -size 37530 +oid sha256:a9ee0f94d9067eda01bc4b93bd5bf5c7493d0f6790bd5c1d1d542ed029e0a07d +size 42149 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_3,NEXUS_5,1.0,en].png index 2c48d3e55d..33ebf89295 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4e1af15c571d1f087005849b627d79387f8f5557bbc4233768bb3c2d940d628 -size 48510 +oid sha256:cd3770364cdfb8804008c0cd86129e689f239a4685ec4690b2d8f65f73b57ac4 +size 36621 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_0,NEXUS_5,1.0,en].png index 11451941bf..ffd6bc821b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21e918bf1bbc6f024099d7bb8a1f38eaa356a50a244a9348411489a678579afa -size 35444 +oid sha256:917073ea5c55d001279b622a268a61ce2b56b94d396c99802f92013b6ddbb71c +size 39924 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_1,NEXUS_5,1.0,en].png index d9300615dc..bf66ea4700 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c051bfc9f9eafd112ccc60eb77b7ffbc9d91c493f2b588fbf51476c2be39ab1a -size 35069 +oid sha256:e6e41d8672db7f6b89f3db5e174569c9ccec381e0f3c34ab410a85768234764f +size 39683 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_2,NEXUS_5,1.0,en].png index fe0e7dfff1..0fee0836a4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:943800b5c6fbdda0c39569ab44375ec2e4487c54644663fc21417b077ce5b8e3 -size 35181 +oid sha256:577f6db9a8a987a685b3596644b0cae992694662aee10b5e64b4c748e21e1a5c +size 39647 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_3,NEXUS_5,1.0,en].png index 519d9d4d10..f194912300 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa25ebf20fe62af56a548c3e962ae2e76e6e8e1b7e685d021306b733613e49eb -size 45462 +oid sha256:d14440500ffac475aa57cedbc3de507f182cb1e79b0b93b4f4b9b90c3c5c13a5 +size 32136 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_0,NEXUS_5,1.0,en].png index 00797a4f11..89d469e66d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c221b092112d787bdc0f9db9e9da3afe75d6b5754b98db119df3c63514da03b4 -size 53808 +oid sha256:b040737c81fb04596312a207e58c75580e48fdd70c1f5ee6abd3c73a846c0337 +size 58489 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_1,NEXUS_5,1.0,en].png index 00797a4f11..89d469e66d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c221b092112d787bdc0f9db9e9da3afe75d6b5754b98db119df3c63514da03b4 -size 53808 +oid sha256:b040737c81fb04596312a207e58c75580e48fdd70c1f5ee6abd3c73a846c0337 +size 58489 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..312f3bb41c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-3_3_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b01d0d26eddb4b6edd900f0f17568d76108e4be97c979d63c5dcca1ca0ed83e +size 56942 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_0,NEXUS_5,1.0,en].png index 46ec60efb6..0682509a00 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:419ba4098c8b77a6e6ea2af72eeb5a614a9982d04e003fedfcdee92772484eb1 -size 48839 +oid sha256:491beebfede0316e804b818c65794bd0c8f8125fc94f4a89f8e1b3ded5afbf3a +size 53597 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_1,NEXUS_5,1.0,en].png index 46ec60efb6..0682509a00 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:419ba4098c8b77a6e6ea2af72eeb5a614a9982d04e003fedfcdee92772484eb1 -size 48839 +oid sha256:491beebfede0316e804b818c65794bd0c8f8125fc94f4a89f8e1b3ded5afbf3a +size 53597 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..39d9744130 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-3_4_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16b24b31812e18fbf54568f2bae63c0827dc30e73d7924cdab6387df0c2ad41b +size 52062 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_0,NEXUS_5,1.0,en].png index ef6a89b482..197546dc67 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7ceba6d884500e6966d548aa048115f7dc326ed14420739b045de7fe75c0cce -size 45210 +oid sha256:b5c68ea146c7f1c3a8e39a6ecdb51e95fb99e371e4f2bb976fdde7d5849db071 +size 45105 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_1,NEXUS_5,1.0,en].png index cfaff5a960..f2520abbf0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9ba978ff9b76534a9c90ca681268b249ee60865438361b13782269d3d4a7a53 -size 44542 +oid sha256:c1cc43c5157a456317171b85c98042dec9865d21121a0663530fda1fe1236a3f +size 44434 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_0,NEXUS_5,1.0,en].png index 1617e91c7a..6995463dd3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0fe7924589afab07164f2c201c97c1715f38d39d0447614bc67eeb893e3701c -size 48261 +oid sha256:d4d1146622da22dd72d53d9d5e239b6f45d23ae4f3ed42f2b0d0902f25256569 +size 48168 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_1,NEXUS_5,1.0,en].png index 9bbba9c45d..3c0beefa8a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fa38f9897d0c743c0d44b5db8b57d88e522b8d603af04f02eb7b2f480e90756 -size 48162 +oid sha256:ad8dcdb0292dcdeed9c50d8abe2bc0bf09c728cac11bfddeac6e74512ee224e2 +size 48068 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_10_null_0,NEXUS_5,1.0,en].png index 00ebc234da..096c9b98af 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_10_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_10_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a935000d8191f1682929c49d0dd3cf2fb8ab7908de574370717a1865967060c -size 22694 +oid sha256:066b94b72b0f5ee1be0e8eec636e927a5cb38d17e1a69ddf6355e588e60cbec0 +size 22538 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_11_null_0,NEXUS_5,1.0,en].png index 90b2807e3e..64e940f97c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_11_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_11_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e13e24436b8d15cc981bc3e997af4c0140ee95513157bc23e0b4429de800aaf3 -size 21105 +oid sha256:86b8fa4f3b1b40e34fafe861c74ecdbb4a21dbd970f1b2445c9326d936e55630 +size 20888 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_0,NEXUS_5,1.0,en].png index 5f5be1fdc5..b7bb6bcf4c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf178a51be45cfd5fee676628ae3192374c1bc873e6eef726053ae593974aafe -size 68108 +oid sha256:3928ea1a60bf2b5c1cc94ef566ca67ce03679994430f826f50636c781976e8e2 +size 70068 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_1,NEXUS_5,1.0,en].png index 15e4e18e8e..7a638cc9f8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:922ffb470cc64a29cd2d9a65840d267cb742efa523f3d93676ec148f818850e1 -size 204829 +oid sha256:62ddb0f9d6764b639b1a1438535c19138d1f81afdaaebfe03b021fc7f64a5291 +size 206670 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_2,NEXUS_5,1.0,en].png index aa132627e5..10d0f335e2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:332e7a7baa12fb54fed4ad99f53470a96182e6059f3f24631ef9c5a7ed9c9f54 -size 59420 +oid sha256:6d546a6a3a5e517e99758f05396a72cc7bf802f3e207f5c7d881e5805f103b67 +size 61356 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_3,NEXUS_5,1.0,en].png index 5f5be1fdc5..b7bb6bcf4c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Day-0_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf178a51be45cfd5fee676628ae3192374c1bc873e6eef726053ae593974aafe -size 68108 +oid sha256:3928ea1a60bf2b5c1cc94ef566ca67ce03679994430f826f50636c781976e8e2 +size 70068 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_0,NEXUS_5,1.0,en].png index adc80daf5f..d8461b8165 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:310bd5d524d61cbc3b8237811c8d6623a6fb1046bb8a9823ff5173c2f2b3c862 -size 65218 +oid sha256:53cb4046ffb2391e880bb9f4567534483f9c3e8bdbea7d7706d3fb93ec0acdec +size 67105 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_1,NEXUS_5,1.0,en].png index 618c296fbf..8e8c828451 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bd787f27931a5f71da9ed4248ff7c310acce37168b36e2575995ea1a1dc2cac -size 200484 +oid sha256:22c01def033c5ce1e7bc12dc7b5ec6fae11bb827f29818d3921f156a4cf04386 +size 202460 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_2,NEXUS_5,1.0,en].png index cda48a2eff..29f1413708 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e93d40807105a64a933180a71a73c661c4b792f5a008af7ada5fc8fd80a1446b -size 54960 +oid sha256:8960e097e94be82ab519bbc1ed3679955dd200349ae32e55d11fad1cbaf586ae +size 56261 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_3,NEXUS_5,1.0,en].png index adc80daf5f..d8461b8165 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.rageshake.impl.bugreport_BugReportView_null_BugReportView-Night-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:310bd5d524d61cbc3b8237811c8d6623a6fb1046bb8a9823ff5173c2f2b3c862 -size 65218 +oid sha256:53cb4046ffb2391e880bb9f4567534483f9c3e8bdbea7d7706d3fb93ec0acdec +size 67105 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_0,NEXUS_5,1.0,en].png index 8a25f4290a..93e1766598 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fd6f767bee84dc91389caa5b31699a2faba02c17cefbdbc033269bb7c9d79db -size 30376 +oid sha256:7550701aefd586a9ab2a4c17ac3d83f6d0055c38f13c8eb07a71fa04abbbed25 +size 30279 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_1,NEXUS_5,1.0,en].png index 4179c023a7..aba49600c7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a56708fb7d9de71d778a675ab586876355fbd13384c9a348905dfb165e1f0554 -size 23701 +oid sha256:f2205a9500649d1faf74a6a327262b27a8ea51a040a0d98cb8485f5a23e0b226 +size 23616 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_2,NEXUS_5,1.0,en].png index 08f65eefdb..a72956f4a2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a136414791bb62050d1972eb0cab956d47806c6ea21b1a7c2b7041c61151fe62 -size 56031 +oid sha256:0f827998598c1927cd389a470b26523741fa81b101af4378f61988012b28d88d +size 55922 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_3,NEXUS_5,1.0,en].png index ae12b78071..0ff186467c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1184486c1440d78b181802ab5e67f9c2c52891e1c9d4e5eec36bc67ec4570b3 -size 30096 +oid sha256:8346a81bac3012380a5c437f5ad035aa59a195fc486268b62723e704d0c531ae +size 29995 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_4,NEXUS_5,1.0,en].png index 44007d9af6..152cb2ef59 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88654c6ff85cb596e4bd07a63c263b8a25a01baa6859f9cf8fb3e96e1a9e17f1 -size 30097 +oid sha256:b47b1dffc574fedd3a8f973ee00b588e9c5d28f232d6f7d11aeb394b642f83cc +size 29999 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_5,NEXUS_5,1.0,en].png index 0d931cfa14..ebef8377bd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e80a2dc627a919484e7a906546e6fd06169b5052124af6876c6f0f4b89fb06a -size 30309 +oid sha256:4374b71e56d6fef460876f3f6351091e1d91e3fc74ae09e14370b6213067194c +size 30182 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_6,NEXUS_5,1.0,en].png index edffdf8e16..92a05bad78 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Day-0_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f507fac42cf9411f557e3b49e9979450c56ded313cb8071524fc2d9263054689 -size 27706 +oid sha256:d464efa5c974378b0b9565e6c4ebdaa183a5f92847f4cf8b63f5738972ea0d81 +size 27589 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_0,NEXUS_5,1.0,en].png index 656ae5e057..fe1b454aae 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8949fa811aff6197026661079d4176e7199e399925470cd044d7e459ca10c23e -size 29049 +oid sha256:b259e49d427308ef4fa1c6a39dff305894f50c07251a12610d5c77ec80bad527 +size 28928 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_1,NEXUS_5,1.0,en].png index d57e0b309b..963bb708d9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cda4b7b2b62f30e2d0bf6dea06545a9fa82bf1cf0c0278c5973f8e0e60f49784 -size 22717 +oid sha256:30c33c82a31fb2c2d8f4249fbf3ca1a835ff6364608f668224473fc65704fea4 +size 22609 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_2,NEXUS_5,1.0,en].png index e45f0574f3..19c1b05b87 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:403689283f938ad412caf12282d4a22deb49337a8e4039ece4618c08b1ac7347 -size 54301 +oid sha256:9769070b02ba47b3cda529def37ee0b8a926291e951d0c9ec7a2de055cc27826 +size 54186 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_3,NEXUS_5,1.0,en].png index 38785f077f..6a002be107 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26377928764db3fa8a645a922a010c6450822ded6b55afa69667b15d5dfec4c7 -size 27978 +oid sha256:77a3989d4518a63b5ef1f076f4cd6902017fe6bb9dd32b8972b4a0fc86d5464f +size 27847 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_4,NEXUS_5,1.0,en].png index f7de7aa2c9..c9228eac6d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b47d8274102e757e6374e1956e12b5ac5cbbe6fb9bc4991bc835e8464cc9e44 -size 28748 +oid sha256:2bf891ac65f3fd3f2a1e4f70e9bfd84e454fc547423b45c9c520c1b00d347119 +size 28628 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_5,NEXUS_5,1.0,en].png index 0ecaac6847..60150a7702 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0b2e3d9518ba4a4b19b54ac2d7f5ccd1840d04a0f1fcf252e90c505107b2f36 -size 28164 +oid sha256:fe04e244412cd9aa62228b82e4bcb4c467dbadb9ce5a1018a2fc88ada7f8aa5f +size 28044 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_6,NEXUS_5,1.0,en].png index 8e77061376..cf0421b84d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_RoomDetailsEditView_null_RoomDetailsEditView-Night-0_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2be03a264d63f3e9d403cf0db50b099ce5d973acb17689303a83b9c332887376 -size 24376 +oid sha256:d8621a13a399b87d3a0f0bc68ae401bc3e69b4af45c659cf55fec0530f184407 +size 24272 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png index d5fe3105d2..5610ee9b86 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdaa865f47a54001607d2c796255e0d50ec7f42f3908f9459bc2555af311274b -size 19572 +oid sha256:15c066163a768e20c319fcc8358eaf31ce618e617fc3959b08973dab1584a678 +size 19584 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png index b0b467544d..f79e1802f8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b43c9657a14b5dc8c6a33fb22d7227e99707f4b75286b1d2fa0d493bc1ef179e -size 17386 +oid sha256:6f47cf68a6c9d0ddf05e2e241af48a83348635980b372e60bb8697cebf92990e +size 17388 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png index b00c827576..0e4e7745e8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e722bb9e2d0782727f031ac115a6e06e7335cddf1f73201753c7474a1f42591 -size 19986 +oid sha256:712431fe032e6ee83d0d02a4218242128059e97619f9ff557652efb49dbf35dc +size 19943 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png index 60ce7700f0..8677b1c43c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb6e059c2d95b2efe028f9995e90dccb42938eb56cfcc228d37c01427a1151e8 -size 36788 +oid sha256:36fca441b0648cce75502538df8e4ac077cc52f7bd4743a22f46a772d90d8d52 +size 36789 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png index 591358df1f..2aba1875d3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be13b77a299b2e55d46b51a277c1e1fbb668ee18e54cafbe96f49ef762e39864 +oid sha256:b60818b3142a0e67d650fc0449d51a7f3eb1199845650551ff3661d773146614 size 28151 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png index 06744c416e..0663c2391c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb0c078a9df374fed543ab11a55f7efa651c1d30f99370a48e1bcd5e8477f657 -size 20587 +oid sha256:fe1a6315150c3e372e9fc16cfcaa6f67917c54d9ce529dadd090045a4511c752 +size 20539 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png index 26058a7bfe..6c059ccac1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ccb14dda83f03265d2de3fcd8bfe1d88ded896563e06208887e779a1f1a776e -size 20035 +oid sha256:3071bfc9bdeb9049e28fa97d13d5b669462ceeea00ea0679db999616ff6c9b3d +size 20039 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png index 32030b1237..058cb80966 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7dfad096895e851722bb777260f88ff843de36b9fb28993ad73ac927ec64343d -size 17787 +oid sha256:03037f931e2f922b5a1cd58d2000b9003e297605908a25545138972d025baec8 +size 17772 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png index 9487deb25b..2efbac461a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c91c4d504dee8f6ef3d86974f2e280bacbf749bf4d44690e706e08bf56b12b83 -size 20479 +oid sha256:4c875bc994695e3a3b0fe8b9fce149339ec4f0867ce0af1fce040e206f048256 +size 20453 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png index 4bbfb31331..c95a613f6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:576027f683e1be5c343977e1a2808e2d8b9632db95552b3001bf6cb68eeef70c -size 41501 +oid sha256:7497745968e1c8cc9d46a3655cc8251f4c92c5f1f929aa6538ad32a395ff6ef4 +size 41499 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png index 2827c77e10..469b702dbf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64aed73e7a31bb58ef07eaee008c0fa2b1820319b180a4627e216e84919cf98a -size 32488 +oid sha256:0aa1c3bde0ddea037dd427d9a98b0563d4df9e754bf078eb52805d6a8967b4c7 +size 32493 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png index 7c16b7de84..dd4408bfca 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90b756e00831455e4e47df17587d382ae6986314aceb5bfe54bc9592bcd8b0dc -size 21074 +oid sha256:fd437191f000962ebb7f435c44d004530b265515bbaf5dd8af56938e2da074ff +size 21048 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_5_null_0,NEXUS_5,1.0,en].png index d26299f528..e4ff848c76 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_5_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Day-5_5_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:372ff5eca3c8c9de6bb15d5ae9a55f1d8ec124c7e454d411be6bd28f78d3aed0 -size 24324 +oid sha256:2e7a76c0f4fe32692980089d2be095d9206c8fdec85aa2e7a057829d027b396e +size 24069 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_6_null_0,NEXUS_5,1.0,en].png index f8beb00ae9..2ddb87f0ba 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_6_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettings_null_UserDefinedRoomNotificationSettings-Night-5_6_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3795832f75ba48402f228dbd5be5dc05b10dab00242922fb861c0f77fcd391e4 -size 22853 +oid sha256:e4d30bbb3b982c47a3632d2b7350a8e63e1c3430d4e588297c2ea55b5d5ecb94 +size 22642 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_0,NEXUS_5,1.0,en].png index d48e98ffbf..dcdedbb448 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2d5651dbb53d95939d999538b86415cbc6e35334d418b4d922345b7e5413597 -size 52622 +oid sha256:cc3b73a8d026391bacbe66f774b2464a8455f35170bdd7e9316f06267b8913d7 +size 52410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_1,NEXUS_5,1.0,en].png index 4c88ba2eba..6b2561f178 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e0c0d5cd843bc4b6b8010c845c311280ad03c96416ae311d6795d79e5126fcb -size 51046 +oid sha256:ce0882664a2cbf8d951003943ed36468e1e90b50ed8aca6ae3dfd87671c79899 +size 49421 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_2,NEXUS_5,1.0,en].png index 70abc2078f..10c0da6c3b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:037159d025ba2c8b675b7cdbed7f223cd544471f96950b2f21b3c3e7a81fd69f -size 44133 +oid sha256:f198e19a1db870a5f7c8520f6b80f1d9e1cf74e864d0abeaeb843a9ccfd9b715 +size 37047 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_3,NEXUS_5,1.0,en].png index 86d3e67efb..dcdedbb448 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8695ecfe2e090e382586008e79e767a9f2ca76fd09e92ae88d4a63cf245a0d54 -size 53487 +oid sha256:cc3b73a8d026391bacbe66f774b2464a8455f35170bdd7e9316f06267b8913d7 +size 52410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_4,NEXUS_5,1.0,en].png index 386bdd6eea..b10f766e50 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c890ffdf4d553dcbc4cecabc8acb434f32344a0b5e97c21b99485f113ab8aa6a -size 50466 +oid sha256:5c2f8f387a3d0998e73df5cc580c2199d6591c46aa86a87cc60c5659d9b5dee1 +size 50664 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_5,NEXUS_5,1.0,en].png index 11c8c67636..a25e7ff0fb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61083c142467bb3634710cc5739d55c04b89e2e705193e7f3e4375539b1e2bb5 -size 52149 +oid sha256:d88db5cc763396241af1692e70254e3114aeb090bd9489e16b42ba2cc313e710 +size 47505 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_6,NEXUS_5,1.0,en].png index 11c8c67636..a25e7ff0fb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61083c142467bb3634710cc5739d55c04b89e2e705193e7f3e4375539b1e2bb5 -size 52149 +oid sha256:d88db5cc763396241af1692e70254e3114aeb090bd9489e16b42ba2cc313e710 +size 47505 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_7,NEXUS_5,1.0,en].png index 8f37512aae..dcdedbb448 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4438e1dde9935d4f1bbf651727edcce5b82b534fd4b45360622c063d82971da5 -size 53813 +oid sha256:cc3b73a8d026391bacbe66f774b2464a8455f35170bdd7e9316f06267b8913d7 +size 52410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_8,NEXUS_5,1.0,en].png index 9dcb830def..de7e05d8c2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_3_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d95158f232b6d9339b5f5c62745897b046534018b6a68507ef7a0dbbd692813 -size 52463 +oid sha256:3f694f450626abb04d70854a9c99849b2dc306d791914664a1f9856066eb400e +size 52228 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_0,NEXUS_5,1.0,en].png index d1f3592333..807bbab00e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b8490acab6fef18eda1b673caed8f499356532fe0383b5ea64eae3a98bf4d8c -size 53884 +oid sha256:7d23ee0e0ad834b6bb8f5e971c968a711952df38bcb310a05f201542940fcade +size 53595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_1,NEXUS_5,1.0,en].png index 782e6bf7f6..6e124304fa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78974cfe5b86c3f6788c58250936c0e786f5843ddf24f8e9e3bb832d1a9e8ead -size 53935 +oid sha256:a9a983309ba1ca5b121ba68b9bafa705ed55b01c2592b8e74c0127fc3d6aee21 +size 52169 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_2,NEXUS_5,1.0,en].png index b0a7809d5b..e734739cd2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90065f5871a1a0886d5a51ac3334998e549326e49aa612dbe6c6e399422a453f -size 46716 +oid sha256:74b6720ebdc8ad46b9ee0efcbe0042008567b1565ef43c939064a51212ba6cae +size 39206 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_3,NEXUS_5,1.0,en].png index 18db460f0c..807bbab00e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b365242f676e9fd7325e35b98580761b9c0dac1edf9a7b10e27c52c1604d68ce -size 54820 +oid sha256:7d23ee0e0ad834b6bb8f5e971c968a711952df38bcb310a05f201542940fcade +size 53595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_4,NEXUS_5,1.0,en].png index 68579460b4..467e43fed1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa7d1423e88bd4ff8abd0487525b25dfc30e912c03c626282f67f6b5a3681d56 -size 51948 +oid sha256:a376e6d8424bd49bbeeda9608ba5bdc65e7a0b4fcaf6811725dce2dac9c56837 +size 52134 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_5,NEXUS_5,1.0,en].png index b792ec9bda..4ced14ce3b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edc60ef43aae57282fea83915e71d5a59fc38747631e13ddb9609b5393f01cd4 -size 54074 +oid sha256:cf97bb9e0548a8a733a8dcd092364f3b17efdb9816a3cd44e1d0c96f7b36874c +size 48603 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_6,NEXUS_5,1.0,en].png index b792ec9bda..4ced14ce3b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edc60ef43aae57282fea83915e71d5a59fc38747631e13ddb9609b5393f01cd4 -size 54074 +oid sha256:cf97bb9e0548a8a733a8dcd092364f3b17efdb9816a3cd44e1d0c96f7b36874c +size 48603 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_7,NEXUS_5,1.0,en].png index a1ecae8832..807bbab00e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a7ea54f3b28185d4af4ef042645daffc606bb09092a28e19f3a993aff086f5f -size 55111 +oid sha256:7d23ee0e0ad834b6bb8f5e971c968a711952df38bcb310a05f201542940fcade +size 53595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_8,NEXUS_5,1.0,en].png index 565e094dea..467668fa83 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_2_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6769f788ba5731a7ee9af234827682800cf2031ccfcddca73d4ae0302ed34ddf -size 53633 +oid sha256:24a8a645034c340794e1bf3e30168af8f1e1083a3cece9a3d7dfa3e4077d24ed +size 53365 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_2,NEXUS_5,1.0,en].png index 17690c5868..598c441d0d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8f07e6b40dc7b9416bfe5d5c54643e0d7db9bd833c0bf9356ad423b788a16a7 -size 12610 +oid sha256:c43973e2ea3e2f58eb8c4ae375ed503493bc4d1f739a69fcb6bd0db150b1852d +size 12658 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_5,NEXUS_5,1.0,en].png index 0de6b64bec..f9a82984a1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39b7b8354b98bca791698ad65f4e21619136f446b2fd050c302bdd27ce138a4a -size 13263 +oid sha256:f0f00ce6af3af7f2df1ee1ef9f953efb78347368824ae0b7daef2edbb154ef00 +size 13281 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_7,NEXUS_5,1.0,en].png index fcf8b20fdc..ef69c52551 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64e7ca21b0213413ed45d0640295ceb2764b01b2bce20a01518e55e620925086 -size 22193 +oid sha256:ce2efae39dce136aa480a52ece41871dbdff32cc9358cf973698db25720a8ef1 +size 22205 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..66bf2311d0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-8_8_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c554064b9bc895d5f9bb76c25166bdbe5b217b40ab5ece5d4b79c9d0144226e3 +size 12154 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_2,NEXUS_5,1.0,en].png index 8674ffbf54..d169c59700 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ec6af35e17f96fbbb0ea56f2b290c0056be5c4b88ef6ae04201052ee665d46f -size 12441 +oid sha256:fa160a5d77537b771e0f806821f0c0644c79235f3059ac57fed8ee04080a5098 +size 12503 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_5,NEXUS_5,1.0,en].png index 6d77503a11..a6272371c2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b293b63f5d1945b8a9b8b5c40afb077757e4f4662eb40a9f4245a21b7a05fe3c -size 12971 +oid sha256:33f1607f57f91bf4fb8a10c908af98778a1918e35afbcc94ca047e5a0fafa138 +size 12972 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_7,NEXUS_5,1.0,en].png index c120c09055..e8dd3d5b41 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bafbb6d5893abaefc96abba3cf1115a44e8cfa8af03de343ea11250c17b9445 -size 21319 +oid sha256:ec44c36dea5b03ed4b088d2fcf69a99c7062876c543fa9799c29e94019a3c839 +size 21327 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f7fdf9be71 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-8_9_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6975fc9b40a77082d4797a1493047163759fcfac6d6c59b389a57c5a06dcb5e9 +size 12055 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_9_null,NEXUS_5,1.0,en].png index ea9b2edb09..d95e361124 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_9_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-9_9_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e649c7fc1608661f8910e86200016a5e2dfe3395cf140c3819dcc7c42d28a2a6 -size 29940 +oid sha256:122b0b090fcb1bba3669daa6e56077305ea542d15ae784addf57f30924d9be8d +size 29956 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_10_null,NEXUS_5,1.0,en].png index f3a113fc8f..cb209e52b7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_10_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-9_10_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86bc6476c9cdc9187e3e4411c693124e1b6e2ac28dc85014c05925741d322be0 -size 29790 +oid sha256:69bfa6cbde376e66c4f2f93daac0e51b80dc60c5df557230c8396b74ef8235ed +size 29792 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_1_null,NEXUS_5,1.0,en].png index 28386ad492..b7dc6f82be 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f990c6058d9dab7273cf886da59672f611c47b5507548fb18c66ceea16ccf1a8 -size 12257 +oid sha256:509e303b4e62632032881ec1fc3531ff5113e774316d80a1a76e3b04fd30506b +size 12555 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_2_null,NEXUS_5,1.0,en].png index c234c4fd9a..d4b8e92df4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0b16b915427c8e1e64d2f62c16cece51dc119e2844d50f585c9f245fca27f3c -size 11615 +oid sha256:76cd5aa507bcbcdba1ee3de894d2e79f4caa1831345c84d8458b46d358ac3da2 +size 11891 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_0,NEXUS_5,1.0,en].png index d1e7ae7489..48b4f45709 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e1be8a38303b5d443f8e0911c34497400b1b577b711191c52ad00b67229a7d1 -size 65314 +oid sha256:4c80b1212608cbc6fb00dcc5648e94df163eb02c01b98087a848e1d404af704a +size 65118 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_1,NEXUS_5,1.0,en].png index 9561e126dc..6267da6f44 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c02883f4a671a5c27da1fd5f3f635d7e23ed077aec2d47dc2ac6e0e2ced72f59 -size 86748 +oid sha256:65a353822daf6999f79499bc9e0c7038ed89b8bb28772a7c1ff487f338cf8e9d +size 86560 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_2,NEXUS_5,1.0,en].png index d1e7ae7489..48b4f45709 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e1be8a38303b5d443f8e0911c34497400b1b577b711191c52ad00b67229a7d1 -size 65314 +oid sha256:4c80b1212608cbc6fb00dcc5648e94df163eb02c01b98087a848e1d404af704a +size 65118 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_3,NEXUS_5,1.0,en].png index 90ead2e7fe..4f1c518289 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3057d4d0d83a51dda75f7701698ed8be2f2a99d01e5e43ab639f59210cc3a9a5 -size 65326 +oid sha256:93f7207d43babcf2810063e60c61a913507fadcb5d8597f019136894e01f9335 +size 65094 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_4,NEXUS_5,1.0,en].png index 4fbb84a0e6..4c49e778c2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:952766c0bbe97ba343ebc68966c0148da68bb4dd8c802dd98f937ca8974a314e -size 66408 +oid sha256:f4457ac25047e469886b9fd2d547a87160bda23a0b398ecd4214e5426ebf36bf +size 66167 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_5,NEXUS_5,1.0,en].png index 6389639819..b7fd617cab 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44b50286480b8351ace34656d7396e2dab9694fc5a19fd9226d5008b87b2bb30 -size 66794 +oid sha256:92a1f12cabc596dfa168a1746e1c8d0d1ba728c815fcb471bc1aa0fec9cee494 +size 66554 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_7,NEXUS_5,1.0,en].png index ea9b2edb09..d95e361124 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e649c7fc1608661f8910e86200016a5e2dfe3395cf140c3819dcc7c42d28a2a6 -size 29940 +oid sha256:122b0b090fcb1bba3669daa6e56077305ea542d15ae784addf57f30924d9be8d +size 29956 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_9,NEXUS_5,1.0,en].png index 912c8eb2e8..329925b0bf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-2_2_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a10e3677e9550a0579bcbda29c31303229638a838ced006c678697507340c514 -size 89949 +oid sha256:0d4e0b8856d2a08170bc89e9ce9d8f53575a8e79ceac13625213e60ab9d7718c +size 89753 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_0,NEXUS_5,1.0,en].png index 2feff83fa7..5e0123fed5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1e7bd8361314f95f67930a3795066d0028fd64dbafb79c2d699596d95f6db95 -size 67522 +oid sha256:3aba116389050f62ac9d61ae41599af35323fdf39a01a61a5bcb60a7ac5d9259 +size 67342 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_1,NEXUS_5,1.0,en].png index 10de4f6b65..240ec106f0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ad2f44c3fb1915ad6e226a973cecc3662c02bdf4dd8faf1eb3f056346fa1aca -size 88667 +oid sha256:9826ad9c429c5a6ece2ea2e5fa6fd1e602bb1453ce44ddcb9d952d33ec632729 +size 88498 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_2,NEXUS_5,1.0,en].png index 2feff83fa7..5e0123fed5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1e7bd8361314f95f67930a3795066d0028fd64dbafb79c2d699596d95f6db95 -size 67522 +oid sha256:3aba116389050f62ac9d61ae41599af35323fdf39a01a61a5bcb60a7ac5d9259 +size 67342 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_3,NEXUS_5,1.0,en].png index dacdeac66b..b81afb5e81 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f630b7f9eae799cfea12bebb5df52c60e8a5a3007d0a7d6febd90c945bd67566 -size 67292 +oid sha256:fa09f985e9459b21d0663e3f1ffddbb57c7b47ce6255ad5bc10a285cbf55dbb2 +size 67122 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_4,NEXUS_5,1.0,en].png index 4137dcfae9..f595818dc6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c1fc5a06c2399f36ea14f1135be2a98a836485c980f08523dca2b79edbcee78 -size 69111 +oid sha256:d6b8d451c99fc0ed6e76b6bdfb809a409d975ef636500493f8021c1d2712b3fc +size 68942 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_5,NEXUS_5,1.0,en].png index 3a9f34d9e1..df4b0812c3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b83df3739ab45b9b96461dd56e69befdb7119e3bc68fa141bd6b4086e384a02c -size 69466 +oid sha256:593f2f027fb111f205dd740c8ab193e10c2b8096fbf49181b5c066c4cd58d299 +size 69301 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_7,NEXUS_5,1.0,en].png index f3a113fc8f..cb209e52b7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86bc6476c9cdc9187e3e4411c693124e1b6e2ac28dc85014c05925741d322be0 -size 29790 +oid sha256:69bfa6cbde376e66c4f2f93daac0e51b80dc60c5df557230c8396b74ef8235ed +size 29792 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_9,NEXUS_5,1.0,en].png index 0ebd806966..134a0ebd5e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-2_3_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d7e2aed5d5ec7f81334ba81b621ea489f4a59d40024f8560ed4084725c348fc -size 91560 +oid sha256:72d91b7b1b89f886c23123db3e297d84df46fc919bb60a4f8caa461b14d02f46 +size 91388 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_0,NEXUS_5,1.0,en].png index d5ae1fe7c3..a6ce94832e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aef9057653b917425621448d0ef62975b02b6cae0e572758a2749ac9cc71189 -size 39364 +oid sha256:fafe906b308c604f4ef98dab36b55b84fd235598aea738913301940bf4222a93 +size 41473 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_3,NEXUS_5,1.0,en].png index d5ae1fe7c3..c6db44bf6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aef9057653b917425621448d0ef62975b02b6cae0e572758a2749ac9cc71189 -size 39364 +oid sha256:7f395fed6dfabb7f26a10f851e0fca7ea475722e1def7159f36d05cc9e47e09a +size 47990 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_4,NEXUS_5,1.0,en].png index 471a6b98c6..0abdbfd76d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b159c6b39ad05b3d0dc10ae0da1826bcaae45c94e0150b09c2cf6ddf2353d305 -size 24831 +oid sha256:92ce51c885eb9c5efb80b747ef107e185114401d235bfd261ec06a1bb9f53f85 +size 42518 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_5,NEXUS_5,1.0,en].png index d5ae1fe7c3..a6ce94832e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aef9057653b917425621448d0ef62975b02b6cae0e572758a2749ac9cc71189 -size 39364 +oid sha256:fafe906b308c604f4ef98dab36b55b84fd235598aea738913301940bf4222a93 +size 41473 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_6,NEXUS_5,1.0,en].png index f75b484e1f..8e822c7ef2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb83490002e584be9f268eadc014345740d0026c929e79f2bb4c407fdf2ba926 -size 29777 +oid sha256:3747da81312e3f34da13e6bd530301550c7307856cc7a31aa009be2d60002ecc +size 26338 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a6ce94832e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fafe906b308c604f4ef98dab36b55b84fd235598aea738913301940bf4222a93 +size 41473 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..557ce7e1ce --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_3_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:083440e0fa8699c8fd370bb13351d04c01fa2c78c273f8e2b07bff0b09e4f2f1 +size 31938 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_0,NEXUS_5,1.0,en].png index 35f90db1d9..be5ffbb21e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:870c36e4f7a64e258af75e22845d05dd8c8217d6dc65826b6405537bed9bc848 -size 37439 +oid sha256:94869b0aaccbbc1e82fb96de5f71fa7e9a59ba36317213c0f9a6bb4f9448e2b1 +size 39451 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_3,NEXUS_5,1.0,en].png index 35f90db1d9..cbfa69161d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:870c36e4f7a64e258af75e22845d05dd8c8217d6dc65826b6405537bed9bc848 -size 37439 +oid sha256:0a8411a6c7df7bdc79a526755745fd2b2fed1c32676acd9c9b1135b2391e5abe +size 45326 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_4,NEXUS_5,1.0,en].png index a8dd202ac2..3a7029466e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:837be69c85fb1a7f08ac0780ca34a56fa5d4d1f2134b4255efc68a78b33cab65 -size 23591 +oid sha256:19965ae446d383e69f8f05dc3e3cb1d3956a9962c472d9246b765ae467ef8d56 +size 40318 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_5,NEXUS_5,1.0,en].png index 35f90db1d9..be5ffbb21e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:870c36e4f7a64e258af75e22845d05dd8c8217d6dc65826b6405537bed9bc848 -size 37439 +oid sha256:94869b0aaccbbc1e82fb96de5f71fa7e9a59ba36317213c0f9a6bb4f9448e2b1 +size 39451 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_6,NEXUS_5,1.0,en].png index ffc413c52d..4673753cdc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eda4aab1ded791ad60e417818ad49070c1c53b9c1353da6dd843fae0934459a5 -size 28387 +oid sha256:14cb5e199172c0b325175e913708fb88111d479af06c1ac2c380a8cd68a80021 +size 25090 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..be5ffbb21e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94869b0aaccbbc1e82fb96de5f71fa7e9a59ba36317213c0f9a6bb4f9448e2b1 +size 39451 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f9da40e05d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_4_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59912e13e6cfec021b7c12d689029f159a0b69e91905323f710d47124e67a7ea +size 30051 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_0_null_0,NEXUS_5,1.0,en].png index 972ae5540e..ad273f4619 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2848fd51e5c11e31500ed1f1029d4dd5e3e9514e414c7bcb970baca30ff0d94f -size 60647 +oid sha256:8830c2822cdc08b72cf6b45d5b9becd29b1824c01002336f69dc4b8a432e28e2 +size 60504 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_1_null_0,NEXUS_5,1.0,en].png index 77083c1d7e..944fb938d8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29ec7823e9c8eff8e62baef3fee40c5ce606a0d4618e9c4a7e3dfd9b1ebe2f69 -size 58881 +oid sha256:d0cf3d6480619228bace599debade40ccf67ecdeb545ba1a3115700e58db55e0 +size 58730 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png index df93febb6f..608ac4b27d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:748a2562d8c3e9b5e4def3b8ea3c782868e56f449de1b1ddb4bc36cd84945318 -size 20642 +oid sha256:a5953d21cc18cb10614361f7343fed9375ca851677b4a380e97e86851a082c3b +size 17118 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png index f97e401957..70d8326e1c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a6ce0c3b6fe3ac503ee7019dee234a9f96552e1106fe6858030a7c70c506d4f -size 19869 +oid sha256:d8134edc79b20771f89e8b15eed3b3660e46ab6f3805130252f32d2fb6b26cb6 +size 16745 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png index 4e2e281f59..7814f8545b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5af89d5988d168ee2cbd30e3ba6447f5cd1399d35a2ca04c7348e74cb623f37 -size 22501 +oid sha256:9e6022f750c6e26d822d4ffd1172cd8c34f7afe0188cce2da9f72d3dd8e4f45a +size 18030 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png index d21aa10955..174d9c208d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c49437c9f943cf044ec64a0ecab2b70d1fa811a34aff4baa8ed0ed846a4f5c5 -size 18595 +oid sha256:d5901d9bf468a0ca5fc7c3d6d308898b802b57967afe4d61c814f86e2194ee16 +size 17829 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png index 726d7087b0..a0c98f1f74 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:063cdf0bbdfc846d3040af143f79a0b6eb325beab62cba5657236d028db6bc26 -size 17267 +oid sha256:6aa7aa8f18b1a7d3a68c65e5275a69d49138e51046d913686f111f809cb2235f +size 17054 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png index 1f2604180e..085f22c2d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8118e782f4ec7b67b6aa148f1bb31c9d30f03f72a7abfc73dc8d5094a5a7545c -size 21791 +oid sha256:e0dd5b1c404dd33a70083e7d775bf4c89e8eb1823e72711e8aa6c4947fb49b6e +size 19756 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png index 38c479cfdc..df93febb6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:032ed67865d3ad800371e193ea922589f27cf03697e3c91c8d698957befa8d42 -size 14590 +oid sha256:748a2562d8c3e9b5e4def3b8ea3c782868e56f449de1b1ddb4bc36cd84945318 +size 20642 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png index 8152088666..f97e401957 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54b00e4382ea77eb90e6de983ec1b363b504456cebd1754946759fd2ee1e9b85 -size 14232 +oid sha256:9a6ce0c3b6fe3ac503ee7019dee234a9f96552e1106fe6858030a7c70c506d4f +size 19869 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png index d8b2b5928a..4e2e281f59 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b9cdb7894f9fe410183d73a2febebe576de6967ad3f2183cfda504f081c567b -size 15475 +oid sha256:b5af89d5988d168ee2cbd30e3ba6447f5cd1399d35a2ca04c7348e74cb623f37 +size 22501 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png index cb0e6b6fc9..d21aa10955 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eac20dcc3e285cde5f3d2515d65416162362de196940b3c208886934592070e4 -size 21346 +oid sha256:0c49437c9f943cf044ec64a0ecab2b70d1fa811a34aff4baa8ed0ed846a4f5c5 +size 18595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png index f939fb7b9e..726d7087b0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96d3a1b71b8372ca80b667ae57bf9f87b5e8167684ea62c8f71d90994113929f -size 19530 +oid sha256:063cdf0bbdfc846d3040af143f79a0b6eb325beab62cba5657236d028db6bc26 +size 17267 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png index f64b9f6a2d..1f2604180e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f3a728b5791495710209e5b308dda4b38defded1a44d00bbd69fb1c45877218 -size 25254 +oid sha256:8118e782f4ec7b67b6aa148f1bb31c9d30f03f72a7abfc73dc8d5094a5a7545c +size 21791 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png index d69da80047..38c479cfdc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:820b23e9b9175b48461a4e7e359c2440b8c45cb598f512ea360519b0815621fc -size 18259 +oid sha256:032ed67865d3ad800371e193ea922589f27cf03697e3c91c8d698957befa8d42 +size 14590 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png index 4cf9f8f839..8152088666 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ce4abbf622711cf6a106cb6c74ed729c6e609d329579b7b00ba8c1581b4b953 -size 17463 +oid sha256:54b00e4382ea77eb90e6de983ec1b363b504456cebd1754946759fd2ee1e9b85 +size 14232 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png index a475b2e7d1..d8b2b5928a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0cb196c59e3bbd845f0e110150ac358ca78c9c08c212e184423ead5d841f67c -size 20219 +oid sha256:8b9cdb7894f9fe410183d73a2febebe576de6967ad3f2183cfda504f081c567b +size 15475 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png index a3d0076294..cb0e6b6fc9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2619a9a70eca12d5931ebc21adce48a1aed8383f35908bb09546f44b40f04543 -size 23094 +oid sha256:eac20dcc3e285cde5f3d2515d65416162362de196940b3c208886934592070e4 +size 21346 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png index 81b9668b0f..f939fb7b9e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d7d71d58b250bdec2d9b6e1ed46c5e3ffd98ffeefcaf4267b9979970a89750b -size 22226 +oid sha256:96d3a1b71b8372ca80b667ae57bf9f87b5e8167684ea62c8f71d90994113929f +size 19530 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png index 5cb8c0a0bf..f64b9f6a2d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7bdea3caef3f1be9fac1fbb5511fe2d76b7985576c038f8c1d920615c3d49cd -size 25005 +oid sha256:1f3a728b5791495710209e5b308dda4b38defded1a44d00bbd69fb1c45877218 +size 25254 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_51,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_51,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d69da80047 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_51,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:820b23e9b9175b48461a4e7e359c2440b8c45cb598f512ea360519b0815621fc +size 18259 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_52,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_52,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4cf9f8f839 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_52,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ce4abbf622711cf6a106cb6c74ed729c6e609d329579b7b00ba8c1581b4b953 +size 17463 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_53,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_53,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a475b2e7d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_53,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0cb196c59e3bbd845f0e110150ac358ca78c9c08c212e184423ead5d841f67c +size 20219 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_54,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_54,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a3d0076294 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_54,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2619a9a70eca12d5931ebc21adce48a1aed8383f35908bb09546f44b40f04543 +size 23094 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_55,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_55,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..81b9668b0f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_55,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7d71d58b250bdec2d9b6e1ed46c5e3ffd98ffeefcaf4267b9979970a89750b +size 22226 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_56,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_56,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5cb8c0a0bf --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_56,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7bdea3caef3f1be9fac1fbb5511fe2d76b7985576c038f8c1d920615c3d49cd +size 25005 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceCategory_null_Preferences_PreferenceCategory_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceCategory_null_Preferences_PreferenceCategory_0_null,NEXUS_5,1.0,en].png index 9d135a0b9b..f891a5843a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceCategory_null_Preferences_PreferenceCategory_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceCategory_null_Preferences_PreferenceCategory_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c38a3ec04f2e88faa51b95cebe18a61f6590150bf655a066544e4bcc5c3b00e6 -size 30441 +oid sha256:8d53b0847befd7041345248a6123de5dc8e8a1174ad473aaa9fc77a1e17c100c +size 30417 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextDark_null_Preferences_PreferenceTextDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextDark_null_Preferences_PreferenceTextDark_0_null,NEXUS_5,1.0,en].png index f7c75aed61..d86d6ec555 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextDark_null_Preferences_PreferenceTextDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextDark_null_Preferences_PreferenceTextDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc5c04af9085f12cbf4ffd5beddfe95f683358716bcb5676e235bf61cef0d037 -size 36257 +oid sha256:f03589a34474517da7faf7da9141ce59500be1c7169fe58dd5db0caafdae61ce +size 36339 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextLight_null_Preferences_PreferenceTextLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextLight_null_Preferences_PreferenceTextLight_0_null,NEXUS_5,1.0,en].png index 03840a0a77..9546ffdacf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextLight_null_Preferences_PreferenceTextLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextLight_null_Preferences_PreferenceTextLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30a1b09e10a4f0af425cf946b9cbb1f9c051b2437a1fe46024748b9bb61ea638 -size 37376 +oid sha256:e2b6e813796bbce262c8028785d52fa126c8a47ffab46f9782ab4695ab504bef +size 37350 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeDark_null_Preferences_PreferenceTextWithEndBadgeDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeDark_null_Preferences_PreferenceTextWithEndBadgeDark_0_null,NEXUS_5,1.0,en].png index a8f6b06222..af08aa21f2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeDark_null_Preferences_PreferenceTextWithEndBadgeDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeDark_null_Preferences_PreferenceTextWithEndBadgeDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:719814e14c9adcefdd2bab346c1a510cdafed23f98e5e4dcb8eb7e958355992b -size 38154 +oid sha256:59a025a97a33c8fc8b17e1b666df6c6eb114d5580ed919108d72cd514094e80a +size 38234 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeLight_null_Preferences_PreferenceTextWithEndBadgeLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeLight_null_Preferences_PreferenceTextWithEndBadgeLight_0_null,NEXUS_5,1.0,en].png index 10ed3ed71f..f59111209b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeLight_null_Preferences_PreferenceTextWithEndBadgeLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceTextWithEndBadgeLight_null_Preferences_PreferenceTextWithEndBadgeLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81ebf584035f87cf30edeb3d8e00c154154fa504c83471f6bf55b2e62946d99a -size 39486 +oid sha256:f596886595b77bf358da5c74422249c4e27426b5455b0c16ba2974dc3e4da1e4 +size 39448 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Day_0_null,NEXUS_5,1.0,en].png index 41be7186b0..a9a6095b7e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Day_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Day_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:759a7ccc4c2cc545660cfad732330b19cb4701d305bfdf451ecb353f8de7500a -size 28588 +oid sha256:b22763db946d824e53440a1d65e6aaba8dac57edcb7e53bac1475fbac81b6feb +size 28567 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Night_1_null,NEXUS_5,1.0,en].png index 8ea3601b6d..cf02bbd741 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Night_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.preferences_PreferenceView_null_PreferenceView-Night_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccc75c01551d2bf417ca005f0f2da7b916929d590d99257107362f6abe018653 -size 26556 +oid sha256:9994aec4ebcbad6458a4bbd8cf741ece95916187c283be38ea56fecbfe82f75e +size 26566 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_0,NEXUS_5,1.0,en].png index ed55bd5463..47c301100c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:281766702461ba2844185d1a59c7ebfeed1acc1b216e3708340b963514848b11 -size 71438 +oid sha256:5c10bbc922df738a2b45424142dd476c71c336ac398fa93be98a1c1a421d566a +size 71443 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_0,NEXUS_5,1.0,en].png index 08b6c7c8a3..32005147d7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db09bdb47175a88a79a2166b030dfe682a18a631e3b78a7875e684326c4be9ef -size 68266 +oid sha256:a479cf4d877676d41df4e618571d8d51e44fe42dbb3e49d3db0f31cf0500928a +size 68262 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 781fea60c8..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e640dc434451e995e29594d50e0494f8f46792a6a76d498347abb9f48c7b40ea -size 36937 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0e233e8f64 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:244a6f7b697dcbff32ad6010145c2c97f60df2c3b9455560148f4c8265ba36f1 +size 77859 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 9e51298cb7..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37c78d99633c381963224c091524236243273231a9698a632fec31e7131df3c7 -size 35085 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ff2ddc533c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5da6a3d6d4e7657a760054acb68449050ff16c34431fde68f2a718b0c7bd047f +size 75080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsSeptember_null_IconsSeptember-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsSeptember_null_IconsSeptember-Day_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 50f1985c0d..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsSeptember_null_IconsSeptember-Day_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:750d377a57496379e29530e0e984a992a8a88b687a448862699e57cfcfb4d1d9 -size 45043 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsSeptember_null_IconsSeptember-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsSeptember_null_IconsSeptember-Night_1_null,NEXUS_5,1.0,en].png deleted file mode 100644 index e60e74e4fb..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsSeptember_null_IconsSeptember-Night_1_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89fcbc1c72c1020d58627c1782bfa7ef0e4844f0ea819b999be90c8b5d52e8ea -size 42998 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_DropdownMenuItem_null_Menus_DropdownMenuItem_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_DropdownMenuItem_null_Menus_DropdownMenuItem_0_null,NEXUS_5,1.0,en].png index 1bbc577143..ce489e30e4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_DropdownMenuItem_null_Menus_DropdownMenuItem_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_DropdownMenuItem_null_Menus_DropdownMenuItem_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e9aaaad9ee2138d37f93e4d4adfbe235ca42c1cd5fd1eaa5fa55bbe55879379 -size 25687 +oid sha256:127101dd98df815f133e7f1ec1a21673d5552011279007d8964d0694fc529a9d +size 25714 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Day-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Day-1_2_null,NEXUS_5,1.0,en].png index 7e75e60938..0bf007326e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Day-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Day-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c33717dcff9a7ba5f05ae7f84b1327c4e9a1834d58f40692c255a70d22d3860b -size 15144 +oid sha256:8669ba9062242c83bba1a040f1996289b545121e47866377d3f0199355045f44 +size 15184 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Night-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Night-1_3_null,NEXUS_5,1.0,en].png index 321d1a07eb..9bd9804b74 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Night-1_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_AvatarActionBottomSheet_null_AvatarActionBottomSheet-Night-1_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a93a7a7668f542a7984f0b6df5571acd202308b6d03500e0e2bedbddb544c30a -size 13312 +oid sha256:60582e5a930ca64ccae2dda6a3c9dcba5ca82df34c3aede655040feba92043b3 +size 13271 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Day-13_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Day-13_13_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 2f3dc97e8c..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Day-13_13_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e6d75686b1463d11d89e5130b160c3c446f26612e8da44981cc6994687001d80 -size 7093 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Night-13_14_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Night-13_14_null,NEXUS_5,1.0,en].png deleted file mode 100644 index ff6633e96a..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Night-13_14_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8c96b9ce6a8d9136c56789e9e6af25b4259143f6416305c523f4e5224bc8fa8 -size 5898 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Day-12_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Day-12_12_null,NEXUS_5,1.0,en].png deleted file mode 100644 index f0c42b1810..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Day-12_12_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0e8a1efaf22dd86e6e27904d72a820cfb0b5d1d38ffeb5745bfa6ca3b0a1c85 -size 6003 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Night-12_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Night-12_13_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 27368109ad..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Night-12_13_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f2db2983459eae8b7538c51d125836807b29334b01fbca495c3c9630fb510c2 -size 5969 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-14_14_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-12_12_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-14_14_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-12_12_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-14_15_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-12_13_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-14_15_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-12_13_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-15_15_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-13_13_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-15_15_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-13_13_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-15_16_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-13_14_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-15_16_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-13_14_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-16_16_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-14_14_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-16_16_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-14_14_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-16_17_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-14_15_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-16_17_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-14_15_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-17_17_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-15_15_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-17_17_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-15_15_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-17_18_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-15_16_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-17_18_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-15_16_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Day-16_16_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Day-16_16_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3059b1e5ad --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Day-16_16_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b6f6caef8d902d4e0b93721f212dcd3f20cde267e1782813f0c55498bca5c8e +size 6805 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Night-16_17_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Night-16_17_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..279ced000a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Night-16_17_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6def3059e37f5c7d6d8930df229a895f5d37b95f46a138fa4ef790bbf76b80b6 +size 6744 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-18_18_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-17_17_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-18_18_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-17_17_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-18_19_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-17_18_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-18_19_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-17_18_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_18_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_18_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..87bfdadf60 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_18_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a70e858c4935324773d885cdc80268550af9ded8bfa2c7e5e6b916f2d4b85f4b +size 18110 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_19_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_19_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2b93b20917 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_19_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c0a05c8f8245af9d46da170ce3062f20c10b77f7b64be1432747a14b98c5c0e +size 16198 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png index f5ba129fd3..e9254642a2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:991416dcd21ebf8e2d57baac7cb42506596a1a346dd7d643b09c84c473e053b7 -size 28016 +oid sha256:9a76e1dcbc4461b6e0a1cca836a097846262a3d302befbec4486ed7ee837a8da +size 28214 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png index 8bd3ded996..304d6dc23e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8b8c60473ce1b9e7451e763c960a9242bb83ca1d3769ee921db830ff6e57e7c -size 27094 +oid sha256:25be7fa172232c508c58369f60e82fc7ec1d7362d9f701c1e698ec8d8498b482 +size 27449 diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index ca8370ae23..69608a0a1a 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -31,6 +31,9 @@ style: active: false UnusedPrivateMember: active: true + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 5 UnusedParameter: active: true UnnecessaryInnerClass: diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 4fa301b914..e611db953c 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -10,7 +10,8 @@ "name": ":features:rageshake:api", "includeRegex": [ "crash_detection_.*", - "rageshake_detection_.*" + "rageshake_detection_.*", + "settings_rageshake.*" ] }, { @@ -93,6 +94,12 @@ "screen_waitlist_.*" ] }, + { + "name": ":features:leaveroom:api", + "includeRegex": [ + "leave_room_alert_.*" + ] + }, { "name": ":features:roomlist:impl", "includeRegex": [ @@ -107,16 +114,19 @@ "screen_room_details_.*", "screen_room_member_list_.*", "screen_dm_details_.*", - "screen_room_notification_settings_.*" + "screen_room_notification_settings_.*", + "screen_notification_settings_edit_failed_updating_default_mode" ] }, { "name": ":features:messages:impl", "includeRegex": [ + "room_timeline_.*", "screen_room_.*", "screen\\.room\\..*", "screen_dm_details_.*", - "room_timeline_state_changes" + "emoji_picker_category_.*", + ".*report_content_.*" ], "excludeRegex": [ "screen_room_details_.*", @@ -130,6 +140,12 @@ "screen_analytics_prompt.*" ] }, + { + "name": ":features:analytics:api", + "includeRegex": [ + "screen_analytics_settings_.*" + ] + }, { "name": ":features:ftue:impl", "includeRegex": [ @@ -157,7 +173,8 @@ "includeRegex": [ "screen_advanced_settings_.*", "screen\\.advanced_settings\\..*", - "screen_edit_profile_.*" + "screen_edit_profile_.*", + "screen_notification_settings_.*" ] }, {