diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1df2119bf9..724060aa40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-{0}-{1}', matrix.variant, github.sha) || format('build-{0}-{1}', matrix.variant, github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 8551cb44c0..f05db3c791 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest name: Danger main check steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger diff --git a/.github/workflows/gradle-wrapper-update.yml b/.github/workflows/gradle-wrapper-update.yml index 33a12d3c54..351057fc89 100644 --- a/.github/workflows/gradle-wrapper-update.yml +++ b/.github/workflows/gradle-wrapper-update.yml @@ -8,7 +8,7 @@ jobs: update-gradle-wrapper: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 # Skip in forks diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 271c5399f6..4746aa3885 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -11,5 +11,5 @@ jobs: runs-on: ubuntu-latest # No concurrency required, this is a prerequisite to other actions and should run every time. steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index 74fb1cfc83..8b78a3c447 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -24,7 +24,7 @@ jobs: group: ${{ format('maestro-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -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.4.1 + - uses: mobile-dev-inc/action-maestro-cloud@v1.5.0 with: api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} # Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android): diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 85c9ed1422..179b001131 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository == 'vector-im/element-x-android' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 65f3cbb53c..124afbc98f 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -55,7 +55,7 @@ jobs: name: Dependency analysis runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 9c0aac7aef..417acc9341 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -17,7 +17,7 @@ jobs: name: Search for forbidden patterns runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run code quality check suite run: ./tools/check/check_code_quality.sh @@ -29,7 +29,7 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-develop-{0}', github.sha) || format('check-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -52,12 +52,6 @@ jobs: name: linting-report path: | */build/reports/**/*.* - - name: 🔊 Publish results to Sonar - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} - if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }} - run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES - name: Prepare Danger if: always() run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e535a1467..cc8fc9055c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: group: ${{ github.ref == 'refs/head/main' && format('build-release-main-{0}', github.sha) }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 42846c2cd5..b511835a60 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -1,4 +1,4 @@ -name: Code Quality Checks +name: Sonar on: workflow_dispatch: @@ -10,18 +10,18 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options="-Xmx2g" -Dkotlin.incremental=false - CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon --warn + CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 4 --no-daemon --warn jobs: sonar: - name: Project Check Suite + name: Sonar Quality Checks runs-on: ubuntu-latest # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('sonar-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('sonar-develop-{0}', github.sha) || format('sonar-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -41,9 +41,3 @@ jobs: ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }} run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES - - name: Prepare Danger - if: always() - run: | - npm install --save-dev @babel/core - npm install --save-dev @babel/plugin-transform-flow-strip-types - yarn add danger-plugin-lint-report --dev diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml index 3d63f8b542..f3acb4675b 100644 --- a/.github/workflows/sync-localazy.yml +++ b/.github/workflows/sync-localazy.yml @@ -11,7 +11,7 @@ jobs: # Skip in forks if: github.repository == 'vector-im/element-x-android' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.9 uses: actions/setup-python@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 460e57b4e3..f662e5352d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,16 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} cancel-in-progress: true steps: + # Increase swapfile size to prevent screenshot tests getting terminated + # https://github.com/actions/runner-images/discussions/7188#discussioncomment-6750749 + - name: 💽 Increase swapfile size + run: | + sudo swapoff -a + sudo fallocate -l 8G /mnt/swapfile + sudo chmod 600 /mnt/swapfile + sudo mkswap /mnt/swapfile + sudo swapon /mnt/swapfile + sudo swapon --show - name: ⏬ Checkout with LFS uses: nschloe/action-cached-lfs-checkout@v1.2.2 with: diff --git a/.maestro/tests/roomList/timeline/messages/poll.yaml b/.maestro/tests/roomList/timeline/messages/poll.yaml new file mode 100644 index 0000000000..65495dda60 --- /dev/null +++ b/.maestro/tests/roomList/timeline/messages/poll.yaml @@ -0,0 +1,13 @@ +appId: ${APP_ID} +--- +- takeScreenshot: build/maestro/530-Timeline +- tapOn: "Add attachment" +- tapOn: "Poll" +- tapOn: "What is the poll about?" +- inputText: "I am a poll" +- tapOn: "Option 1" +- inputText: "Answer 1" +- tapOn: "Option 2" +- inputText: "Answer 2" +- tapOn: "Create" +- takeScreenshot: build/maestro/531-Timeline diff --git a/.maestro/tests/roomList/timeline/timeline.yaml b/.maestro/tests/roomList/timeline/timeline.yaml index bec566985d..1acb10a9aa 100644 --- a/.maestro/tests/roomList/timeline/timeline.yaml +++ b/.maestro/tests/roomList/timeline/timeline.yaml @@ -5,5 +5,6 @@ appId: ${APP_ID} - takeScreenshot: build/maestro/500-Timeline - runFlow: messages/text.yaml - runFlow: messages/location.yaml +- runFlow: messages/poll.yaml - back - runFlow: ../../assertions/assertHomeDisplayed.yaml diff --git a/CHANGES.md b/CHANGES.md index c9e3ad2d33..4cca4c7212 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,26 @@ +Changes in Element X v0.1.6 (2023-09-04) +======================================== + +Features ✨ +---------- + - Enable the Polls feature. Allows to create, view, vote and end polls. ([#1196](https://github.com/vector-im/element-x-android/issues/1196)) +- Create poll. ([#1143](https://github.com/vector-im/element-x-android/issues/1143)) + +Bugfixes 🐛 +---------- +- Ensure notification for Event from encrypted room get decrypted content. ([#1178](https://github.com/vector-im/element-x-android/issues/1178)) + - Make sure Snackbars are only displayed once. ([#928](https://github.com/vector-im/element-x-android/issues/928)) + - Fix the orientation of sent images. ([#1135](https://github.com/vector-im/element-x-android/issues/1135)) + - Bug reporter crashes when 'send logs' is disabled. ([#1168](https://github.com/vector-im/element-x-android/issues/1168)) + - Add missing link to the terms on the analytics setting screen. ([#1177](https://github.com/vector-im/element-x-android/issues/1177)) + - Re-enable `SyncService.withEncryptionSync` to improve decryption of notifications. ([#1198](https://github.com/vector-im/element-x-android/issues/1198)) + - Crash with `aspectRatio` modifier when `Float.NaN` was used as input. ([#1995](https://github.com/vector-im/element-x-android/issues/1995)) + +Other changes +------------- + - Remove unnecessary year in copyright mention. ([#1187](https://github.com/vector-im/element-x-android/issues/1187)) + + Changes in Element X v0.1.5 (2023-08-28) ======================================== diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c5cb01b95..2821fcbd04 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -218,7 +218,7 @@ dependencies { implementation(libs.network.okhttp.logging) implementation(libs.serialization.json) - implementation(libs.vanniktech.emoji) + implementation(libs.matrix.emojibase.bindings) implementation(libs.dagger) kapt(libs.dagger.compiler) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ffd0265584..7d1c45b1b1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ { val bugReporter = appBindings.bugReporter() Timber.plant(tracingService.createTimberTree()) val tracingConfiguration = if (BuildConfig.DEBUG) { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val store = SharedPrefTracingConfigurationStore(prefs) + val builder = TargetLogLevelMapBuilder(store) TracingConfiguration( - filterConfiguration = TracingFilterConfigurations.debug, + filterConfiguration = TracingFilterConfigurations.custom(builder.getCurrentMap()), writesToLogcat = true, writesToFilesConfiguration = WriteToFilesConfiguration.Disabled ) @@ -51,6 +58,8 @@ class TracingInitializer : Initializer { } bugReporter.cleanLogDirectoryIfNeeded() tracingService.setupTracing(tracingConfiguration) + // Also set env variable for rust back trace + Os.setenv("RUST_BACKTRACE", "1", true) } override fun dependencies(): List>> = mutableListOf() diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml index 37fe0011dc..973e5911ae 100644 --- a/app/src/main/res/xml/backup_rules.xml +++ b/app/src/main/res/xml/backup_rules.xml @@ -15,15 +15,8 @@ --> - + diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml index a6ecda4638..9b4bbfff1c 100644 --- a/app/src/main/res/xml/data_extraction_rules.xml +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -1,5 +1,5 @@ - + - diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index cffd318fb1..88f0741ebe 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -48,8 +48,6 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) - implementation(projects.libraries.permissions.api) - implementation(projects.libraries.permissions.noop) implementation(libs.coil) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index 17f3a44eb8..6c22644658 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -32,6 +32,7 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.api.LoginEntryPoint import io.element.android.features.onboarding.api.OnBoardingEntryPoint +import io.element.android.features.preferences.api.ConfigureTracingEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.di.AppScope @@ -43,6 +44,7 @@ class NotLoggedInFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val onBoardingEntryPoint: OnBoardingEntryPoint, + private val configureTracingEntryPoint: ConfigureTracingEntryPoint, private val loginEntryPoint: LoginEntryPoint, private val notLoggedInImageLoaderFactory: NotLoggedInImageLoaderFactory, ) : BackstackNode( @@ -70,6 +72,9 @@ class NotLoggedInFlowNode @AssistedInject constructor( data class LoginFlow( val isAccountCreation: Boolean, ) : NavTarget + + @Parcelize + data object ConfigureTracing : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -83,6 +88,10 @@ class NotLoggedInFlowNode @AssistedInject constructor( override fun onSignIn() { backstack.push(NavTarget.LoginFlow(isAccountCreation = false)) } + + override fun onOpenDeveloperSettings() { + backstack.push(NavTarget.ConfigureTracing) + } } onBoardingEntryPoint .nodeBuilder(this, buildContext) @@ -94,6 +103,9 @@ class NotLoggedInFlowNode @AssistedInject constructor( .params(LoginEntryPoint.Params(isAccountCreation = navTarget.isAccountCreation)) .build() } + NavTarget.ConfigureTracing -> { + configureTracingEntryPoint.createNode(this, buildContext) + } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 6d386a17e5..7683b7278f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -16,44 +16,26 @@ package io.element.android.appnav.loggedin -import android.Manifest -import android.os.Build 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 -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomlist.RoomListService -import io.element.android.libraries.permissions.api.PermissionsPresenter -import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.push.api.PushService -import kotlinx.coroutines.delay import javax.inject.Inject -private const val DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS = 1500L - class LoggedInPresenter @Inject constructor( private val matrixClient: MatrixClient, - private val permissionsPresenterFactory: PermissionsPresenter.Factory, private val networkMonitor: NetworkMonitor, private val pushService: PushService, ) : Presenter { - private val postNotificationPermissionsPresenter by lazy { - // Ask for POST_NOTIFICATION PERMISSION on Android 13+ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - permissionsPresenterFactory.create(Manifest.permission.POST_NOTIFICATIONS) - } else { - NoopPermissionsPresenter() - } - } - @Composable override fun present(): LoggedInState { LaunchedEffect(Unit) { @@ -64,25 +46,15 @@ class LoggedInPresenter @Inject constructor( pushService.registerWith(matrixClient, pushProvider, distributor) } - val roomListState by matrixClient.roomListService.state.collectAsState() + val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() val networkStatus by networkMonitor.connectivity.collectAsState() - val permissionsState = postNotificationPermissionsPresenter.present() - var showSyncSpinner by remember { - mutableStateOf(false) - } - LaunchedEffect(roomListState, networkStatus) { - showSyncSpinner = when { - networkStatus == NetworkStatus.Offline -> false - roomListState == RoomListService.State.Running -> false - else -> { - delay(DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS) - true - } + val showSyncSpinner by remember { + derivedStateOf { + networkStatus == NetworkStatus.Online && syncIndicator == RoomListService.SyncIndicator.Show } } return LoggedInState( showSyncSpinner = showSyncSpinner, - permissionsState = permissionsState, ) } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt index bb06952a50..4196277698 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt @@ -16,9 +16,6 @@ package io.element.android.appnav.loggedin -import io.element.android.libraries.permissions.api.PermissionsState - data class LoggedInState( val showSyncSpinner: Boolean, - val permissionsState: PermissionsState, ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt index 3cfb03f123..0e8fdef8d8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt @@ -17,7 +17,6 @@ package io.element.android.appnav.loggedin import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState open class LoggedInStateProvider : PreviewParameterProvider { override val values: Sequence @@ -32,5 +31,4 @@ fun aLoggedInState( showSyncSpinner: Boolean = true, ) = LoggedInState( showSyncSpinner = showSyncSpinner, - permissionsState = createDummyPostNotificationPermissionsState(), ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt index 0ade93a795..37e6e9591d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt @@ -23,21 +23,16 @@ import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.libraries.androidutils.system.openAppSettingsPage import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.permissions.api.PermissionsView @Composable fun LoggedInView( state: LoggedInState, modifier: Modifier = Modifier ) { - val context = LocalContext.current - Box( modifier = modifier .fillMaxSize() @@ -49,10 +44,6 @@ fun LoggedInView( .align(Alignment.TopCenter), isVisible = state.showSyncSpinner, ) - PermissionsView( - state = state.permissionsState, - openSystemSettings = context::openAppSettingsPage - ) } } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt index dad9365921..703e59c7f1 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt @@ -31,10 +31,16 @@ import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolde import io.element.android.services.apperror.api.AppErrorState import io.element.android.services.apperror.api.AppErrorStateService import io.element.android.services.apperror.impl.DefaultAppErrorStateService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class RootPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = createPresenter() diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 4abc89e7ee..f0a9642f4f 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -26,16 +26,21 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService -import io.element.android.libraries.permissions.api.PermissionsPresenter -import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class LoggedInPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = createPresenter() @@ -43,7 +48,7 @@ class LoggedInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.permissionsState.permission).isEmpty() + assertThat(initialState.showSyncSpinner).isFalse() } } @@ -68,11 +73,6 @@ class LoggedInPresenterTest { ): LoggedInPresenter { return LoggedInPresenter( matrixClient = FakeMatrixClient(roomListService = roomListService), - permissionsPresenterFactory = object : PermissionsPresenter.Factory { - override fun create(permission: String): PermissionsPresenter { - return NoopPermissionsPresenter() - } - }, networkMonitor = FakeNetworkMonitor(networkStatus), pushService = object : PushService { override fun notificationStyleChanged() { diff --git a/build.gradle.kts b/build.gradle.kts index b48ef70ce7..5c958fc397 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,7 +62,7 @@ allprojects { config.from(files("$rootDir/tools/detekt/detekt.yml")) } dependencies { - detektPlugins("io.nlopez.compose.rules:detekt:0.2.1") + detektPlugins("io.nlopez.compose.rules:detekt:0.2.2") } // KtLint @@ -143,22 +143,6 @@ sonar { } } -allprojects { - val projectDir = projectDir.toString() - sonar { - properties { - // Note: folders `kotlin` are not supported (yet), I asked on their side: https://community.sonarsource.com/t/82824 - // As a workaround provide the path in `sonar.sources` property. - if (File("$projectDir/src/main/kotlin").exists()) { - property("sonar.sources", "src/main/kotlin") - } - if (File("$projectDir/src/test/kotlin").exists()) { - property("sonar.tests", "src/test/kotlin") - } - } - } -} - allprojects { tasks.withType { maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) @@ -261,6 +245,8 @@ koverMerged { includes += "*Presenter" excludes += "*Fake*Presenter" excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*" + // Some options can't be tested at the moment + excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*" } bound { minValue = 85 diff --git a/changelog.d/+sdk_bump.feature b/changelog.d/+sdk_bump.feature new file mode 100644 index 0000000000..4f92120f84 --- /dev/null +++ b/changelog.d/+sdk_bump.feature @@ -0,0 +1 @@ +Bump Rust SDK to `v0.1.49` \ No newline at end of file diff --git a/changelog.d/1143.feature b/changelog.d/1143.feature deleted file mode 100644 index 84a86f4f25..0000000000 --- a/changelog.d/1143.feature +++ /dev/null @@ -1 +0,0 @@ -Create poll. diff --git a/changelog.d/1168.bugfix b/changelog.d/1168.bugfix deleted file mode 100644 index f7f959ac0a..0000000000 --- a/changelog.d/1168.bugfix +++ /dev/null @@ -1 +0,0 @@ -Bug reporter crashes when 'send logs' is disabled. diff --git a/changelog.d/1172.feature b/changelog.d/1172.feature new file mode 100644 index 0000000000..ea03101f0c --- /dev/null +++ b/changelog.d/1172.feature @@ -0,0 +1,2 @@ +[Rich text editor] Integrate rich text editor library. Note that markdown is now not supported and further formatting support will be introduced through the rich text editor. + diff --git a/changelog.d/1173.bugfix b/changelog.d/1173.bugfix new file mode 100644 index 0000000000..354aaaa3c4 --- /dev/null +++ b/changelog.d/1173.bugfix @@ -0,0 +1 @@ +Reply action: harmonize conditions in bottom sheet and swipe to reply. diff --git a/changelog.d/1177.bugfix b/changelog.d/1177.bugfix deleted file mode 100644 index edbf2e9006..0000000000 --- a/changelog.d/1177.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add missing link to the terms on the analytics setting screen. diff --git a/changelog.d/1187.misc b/changelog.d/1187.misc deleted file mode 100644 index 301e3a6fc4..0000000000 --- a/changelog.d/1187.misc +++ /dev/null @@ -1 +0,0 @@ -Remove unnecessary year in copyright mention. diff --git a/changelog.d/1191.misc b/changelog.d/1191.misc new file mode 100644 index 0000000000..0b91374f77 --- /dev/null +++ b/changelog.d/1191.misc @@ -0,0 +1 @@ +Exclude some groups related to analytics to be included. diff --git a/changelog.d/1217.feature b/changelog.d/1217.feature new file mode 100644 index 0000000000..24941e4ed3 --- /dev/null +++ b/changelog.d/1217.feature @@ -0,0 +1 @@ +Implement Bloom effect modifier. diff --git a/changelog.d/1222.bugfix b/changelog.d/1222.bugfix new file mode 100644 index 0000000000..325cf6fc4d --- /dev/null +++ b/changelog.d/1222.bugfix @@ -0,0 +1 @@ +Fix system bar color after login on light theme. diff --git a/changelog.d/1224.feature b/changelog.d/1224.feature new file mode 100644 index 0000000000..c921977df4 --- /dev/null +++ b/changelog.d/1224.feature @@ -0,0 +1 @@ +Set color on display name and default avatar in the timeline. diff --git a/changelog.d/1241.bugfix b/changelog.d/1241.bugfix new file mode 100644 index 0000000000..d2339b3158 --- /dev/null +++ b/changelog.d/1241.bugfix @@ -0,0 +1 @@ +Enable polls in release build. diff --git a/changelog.d/1244.misc b/changelog.d/1244.misc new file mode 100644 index 0000000000..ab0bbf5178 --- /dev/null +++ b/changelog.d/1244.misc @@ -0,0 +1 @@ +Use the new SyncIndicator API. diff --git a/changelog.d/1251.misc b/changelog.d/1251.misc new file mode 100644 index 0000000000..ba3b37b8ff --- /dev/null +++ b/changelog.d/1251.misc @@ -0,0 +1 @@ +Improve RoomSummary mapping by using RoomInfo. diff --git a/changelog.d/1261.feature b/changelog.d/1261.feature new file mode 100644 index 0000000000..bf7dd2399d --- /dev/null +++ b/changelog.d/1261.feature @@ -0,0 +1 @@ +[Rich text editor] Add formatting menu (accessible via the '+' button) diff --git a/changelog.d/1269.misc b/changelog.d/1269.misc new file mode 100644 index 0000000000..1b3205917c --- /dev/null +++ b/changelog.d/1269.misc @@ -0,0 +1 @@ +Ensure Posthog data are sent to "https://posthog.element.io" diff --git a/changelog.d/612.bugfix b/changelog.d/612.bugfix new file mode 100644 index 0000000000..390329afa1 --- /dev/null +++ b/changelog.d/612.bugfix @@ -0,0 +1 @@ +Make links in room topic clickable diff --git a/changelog.d/897.feature b/changelog.d/897.feature new file mode 100644 index 0000000000..f705f8dee8 --- /dev/null +++ b/changelog.d/897.feature @@ -0,0 +1 @@ +Add a notification permission screen to the initial flow. diff --git a/changelog.d/928.bugfix b/changelog.d/928.bugfix deleted file mode 100644 index 98a4cd34e0..0000000000 --- a/changelog.d/928.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make sure Snackbars are only displayed once. diff --git a/docs/continuous_integration.md b/docs/continuous_integration.md new file mode 100644 index 0000000000..4ff0ebb983 --- /dev/null +++ b/docs/continuous_integration.md @@ -0,0 +1,69 @@ +# Continuous integration strategy + + + +* [Introduction](#introduction) +* [CI tools](#ci-tools) +* [Rules](#rules) +* [What is the CI checking](#what-is-the-ci-checking) +* [What is the CI reporting](#what-is-the-ci-reporting) +* [Current choices](#current-choices) + * [R8 task](#r8-task) + * [Android test (connected test)](#android-test-connected-test) + + + +## Introduction + +This document gives some information about how we take advantage of the continuous integration (CI). + +## CI tools + +We use GitHub Actions to configure and perform the CI. + +## Rules + +We want: + +1. The CI to detect as soon as possible any issue in the code +2. The CI to be fast - it's run on all the Pull Requests, and developers do not like to wait too long +3. The CI to be reliable - it should not fail randomly +4. The CI to generate artifacts which can be used by the team and the community +5. The CI to generate useful logs and reports, not too verbose, not too short +6. The developer to be able to run the CI locally - to help with this we have [a script](../tools/check/check_code_quality.sh) the can be run locally and which does more checks that just building and deploying the app. +7. The CI to be used as a common environment for the team: generate the screenshots image for the screenshot test, build the release build (unsigned) +8. The CI to run repeated tasks, like building the nightly builds, integrating data from external tools (translations, etc.) +9. The CI to upgrade our dependencies (Renovate) +10. The CI to do some issue triaging + +## What is the CI checking + +The CI checks that: + +1. The code is compiling, without any warnings, for all the app build types and variants and for the minimal app +2. The tests are passing +3. The code quality is good (detekt, ktlint, lint) +4. The code is running and smoke tests are passing (maestro) +5. The PullRequest itself is good (with danger) +6. Files that must be added with git-lfs are added with git-lfs + +## What is the CI reporting + +The CI reports: + +1. Code coverage reports +2. Sonar reports + +## Current choices + +### R8 task + +The CI does not run R8 because it's too slow, and it breaks rule 2. + +The drawback is that the nightly build can fail, as well as the release build. + +Since the nightly build is failing, the team can detect the failure quite fast and react to it. + +### Android test (connected test) + +We limit the number of connected tests (tests under folder `androidTest`), because it often break rule 2 and 3. diff --git a/docs/install_from_github_release.md b/docs/install_from_github_release.md new file mode 100644 index 0000000000..de395f737a --- /dev/null +++ b/docs/install_from_github_release.md @@ -0,0 +1,64 @@ +# Installing Element X Android from a Github Release + +This document explains how to install Element X Android from a Github Release. + + + +* [Requirements](#requirements) +* [Steps](#steps) +* [I already have the application on my phone](#i-already-have-the-application-on-my-phone) + + + +## Requirements + +The Github release will contain an Android App Bundle (with `aab` extension) file, unlike in the Element Android project where releases directly provide the APKs. So there are some steps to perform to generate and sign App Bundle APKs. An APK suitable for the targeted device will then be generated. + +The easiest way to do that is to use the debug signature that is shared between the developers and stored in the Element X Android project. So we recommend to clone the project first, to be able to use the debug signature it contains. But note that you can use any other signature. You don't need to install Android Studio, you will only need a shell terminal. + +You can clone the project by running: +```bash +git clone git@github.com:vector-im/element-x-android.git +``` +or +```bash +git clone https://github.com/vector-im/element-x-android.git +``` + +You will also need to install [bundletool](https://developer.android.com/studio/command-line/bundletool). On MacOS, you can run the following command: + +```bash +brew install bundletool +``` + +## Steps + +1. Open the GitHub release that you want to install from https://github.com/vector-im/element-x-android/releases +2. Download the asset `app-release-signed.aab` +3. Navigate to the folder where you cloned the project and run the following command: +```bash +bundletool build-apks --bundle= --output=./tmp/elementx.apks \ + --ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \ + --overwrite +``` +For instance: +```bash +bundletool build-apks --bundle=./tmp/Element/0.1.5/app-release-signed.aab --output=./tmp/elementx.apks \ + --ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \ + --overwrite +``` +4. Run an Android emulator, or connect a real device to your computer +5. Install the APKs on the device: +```bash +bundletool install-apks --apks=./tmp/elementx.apks +``` + +That's it, the application should be installed on your device, you can start it from the launcher icon. + +## I already have the application on my phone + +If the application was already installed on your phone, there are several cases: + +- it was installed from the PlayStore, you will have to uninstall it first because the signature will not match. +- it was installed from a previous GitHub release, this is like an application upgrade, so no need to uninstall the existing app. +- it was installed from a more recent GitHub release, you will have to uninstall it first. diff --git a/fastlane/metadata/android/en-US/changelogs/40001060.txt b/fastlane/metadata/android/en-US/changelogs/40001060.txt new file mode 100644 index 0000000000..ff4f86ce7e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40001060.txt @@ -0,0 +1,2 @@ +Main changes in this version: bugfixes. +Full changelog: https://github.com/vector-im/element-x-android/releases diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts index 3ce2bab507..3deb202ebc 100644 --- a/features/analytics/impl/build.gradle.kts +++ b/features/analytics/impl/build.gradle.kts @@ -51,4 +51,5 @@ dependencies { testImplementation(libs.test.mockk) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) + testImplementation(projects.tests.testutils) } diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt index b9cb4d95ff..aad9973282 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -23,11 +23,18 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class AnalyticsOptInPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - enable`() = runTest { val analyticsService = FakeAnalyticsService(isEnabled = false) diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt index 29c4579d8f..3a2812b72e 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -23,10 +23,17 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class AnalyticsPreferencesPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state available`() = runTest { val presenter = DefaultAnalyticsPreferencesPresenter( diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 0ef46a57ec..3ea54ca4ea 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { testImplementation(projects.libraries.mediapickers.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.usersearch.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt index 48ad56caf4..79969eeac7 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt @@ -23,6 +23,7 @@ import io.element.android.features.createroom.impl.userlist.aListOfSelectedUsers import io.element.android.features.createroom.impl.userlist.aUserListState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.ui.components.aMatrixUserList +import io.element.android.libraries.usersearch.api.UserSearchResult import kotlinx.collections.immutable.toImmutableList open class AddPeopleUserListStateProvider : PreviewParameterProvider { @@ -36,7 +37,11 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider + UserSearchResult(matrixUser, index % 2 == 0) + } + .toImmutableList()), selectedUsers = aListOfSelectedUsers(), isSearchActive = true, selectionMode = SelectionMode.Multiple, diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt index 43cf6cff4d..af68f0dd57 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt @@ -24,12 +24,18 @@ import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.libraries.usersearch.test.FakeUserRepository +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test class AddPeoplePresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private lateinit var presenter: AddPeoplePresenter @Before diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt index 864d85423d..bc5bafa843 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -47,6 +48,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -58,6 +60,10 @@ private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery" @RunWith(RobolectricTestRunner::class) class ConfigureRoomPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private lateinit var presenter: ConfigureRoomPresenter private lateinit var userListDataStore: UserListDataStore private lateinit var createRoomDataStore: CreateRoomDataStore diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index 0f88280c84..1809df135b 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -35,13 +35,19 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test class CreateRoomRootPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private lateinit var userRepository: FakeUserRepository private lateinit var presenter: CreateRoomRootPresenter private lateinit var fakeUserListPresenter: FakeUserListPresenter diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt index 3561387dcf..12e9f2fc90 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt @@ -25,12 +25,18 @@ import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.usersearch.api.UserSearchResult import io.element.android.libraries.usersearch.test.FakeUserRepository +import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class DefaultUserListPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val userRepository = FakeUserRepository() @Test diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index 8b12767b20..42fe8dade5 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -43,6 +43,10 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.features.analytics.api) implementation(projects.services.analytics.api) + implementation(projects.libraries.permissions.api) + implementation(projects.libraries.permissions.noop) + implementation(projects.services.toolbox.api) + implementation(projects.services.toolbox.test) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -51,6 +55,9 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) + testImplementation(projects.libraries.permissions.impl) + testImplementation(projects.libraries.permissions.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 2b515c18a6..ab6bf94a69 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -35,6 +35,7 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.impl.migration.MigrationScreenNode +import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode import io.element.android.features.ftue.impl.state.DefaultFtueState import io.element.android.features.ftue.impl.state.FtueStep import io.element.android.features.ftue.impl.welcome.WelcomeNode @@ -79,6 +80,9 @@ class FtueFlowNode @AssistedInject constructor( @Parcelize data object WelcomeScreen : NavTarget + @Parcelize + data object NotificationsOptIn : NavTarget + @Parcelize data object AnalyticsOptIn : NavTarget } @@ -124,6 +128,14 @@ class FtueFlowNode @AssistedInject constructor( } createNode(buildContext, listOf(callback)) } + NavTarget.NotificationsOptIn -> { + val callback = object : NotificationsOptInNode.Callback { + override fun onNotificationsOptInFinished() { + lifecycleScope.launch { moveToNextStep() } + } + } + createNode(buildContext, listOf(callback)) + } NavTarget.AnalyticsOptIn -> { analyticsEntryPoint.createNode(this, buildContext) } @@ -138,6 +150,9 @@ class FtueFlowNode @AssistedInject constructor( FtueStep.WelcomeScreen -> { backstack.newRoot(NavTarget.WelcomeScreen) } + FtueStep.NotificationsOptIn -> { + backstack.newRoot(NavTarget.NotificationsOptIn) + } FtueStep.AnalyticsOptIn -> { backstack.replace(NavTarget.AnalyticsOptIn) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt new file mode 100644 index 0000000000..55b6748c72 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.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.features.ftue.impl.notifications + +sealed interface NotificationsOptInEvents { + data object ContinueClicked : NotificationsOptInEvents + data object NotNowClicked : NotificationsOptInEvents +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt new file mode 100644 index 0000000000..00fbb10b1f --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt @@ -0,0 +1,57 @@ +/* + * 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.ftue.impl.notifications + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class NotificationsOptInNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenterFactory: NotificationsOptInPresenter.Factory, +) : Node(buildContext, plugins = plugins) { + + interface Callback: NodeInputs { + fun onNotificationsOptInFinished() + } + + private val callback = inputs() + + private val presenter: NotificationsOptInPresenter by lazy { + presenterFactory.create(callback) + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + NotificationsOptInView( + state = state, + onBack = { callback.onNotificationsOptInFinished() }, + modifier = modifier + ) + } +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt new file mode 100644 index 0000000000..f3bffca590 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt @@ -0,0 +1,97 @@ +/* + * 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.ftue.impl.notifications + +import android.Manifest +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.permissions.api.PermissionStateProvider +import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsPresenter +import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class NotificationsOptInPresenter @AssistedInject constructor( + private val permissionsPresenterFactory: PermissionsPresenter.Factory, + @Assisted private val callback: NotificationsOptInNode.Callback, + private val appCoroutineScope: CoroutineScope, + private val permissionStateProvider: PermissionStateProvider, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, +) : Presenter { + + @AssistedFactory + interface Factory { + fun create(callback: NotificationsOptInNode.Callback): NotificationsOptInPresenter + } + + private val postNotificationPermissionsPresenter by lazy { + // Ask for POST_NOTIFICATION PERMISSION on Android 13+ + if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + permissionsPresenterFactory.create(Manifest.permission.POST_NOTIFICATIONS) + } else { + NoopPermissionsPresenter() + } + } + + @Composable + override fun present(): NotificationsOptInState { + val notificationsPermissionsState = postNotificationPermissionsPresenter.present() + + fun handleEvents(event: NotificationsOptInEvents) { + when (event) { + NotificationsOptInEvents.ContinueClicked -> { + if (notificationsPermissionsState.permissionGranted) { + callback.onNotificationsOptInFinished() + } else { + notificationsPermissionsState.eventSink(PermissionsEvents.OpenSystemDialog) + } + } + NotificationsOptInEvents.NotNowClicked -> { + if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + appCoroutineScope.setPermissionDenied() + } + callback.onNotificationsOptInFinished() + } + } + } + + LaunchedEffect(notificationsPermissionsState) { + if (notificationsPermissionsState.permissionGranted + || notificationsPermissionsState.permissionAlreadyDenied) { + callback.onNotificationsOptInFinished() + } + } + + return NotificationsOptInState( + notificationsPermissionState = notificationsPermissionsState, + eventSink = ::handleEvents + ) + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + private fun CoroutineScope.setPermissionDenied() = launch { + permissionStateProvider.setPermissionDenied(Manifest.permission.POST_NOTIFICATIONS, true) + } +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt new file mode 100644 index 0000000000..a64fb7ad4a --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.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.ftue.impl.notifications + +import io.element.android.libraries.permissions.api.PermissionsState + +data class NotificationsOptInState( + val notificationsPermissionState: PermissionsState, + val eventSink: (NotificationsOptInEvents) -> Unit +) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt new file mode 100644 index 0000000000..230e125c1b --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt @@ -0,0 +1,33 @@ +/* + * 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.ftue.impl.notifications + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.permissions.api.aPermissionsState + +open class NotificationsOptInStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aNotificationsOptInState(), + // Add other states here + ) +} + +fun aNotificationsOptInState() = NotificationsOptInState( + notificationsPermissionState = aPermissionsState(), + eventSink = {} +) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt new file mode 100644 index 0000000000..d22d6ab6ff --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt @@ -0,0 +1,197 @@ +/* + * 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.ftue.impl.notifications + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Notifications +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.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.ftue.impl.R +import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +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.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun NotificationsOptInView( + state: NotificationsOptInState, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { + BackHandler(onBack = onBack) + + HeaderFooterPage( + modifier = modifier + .systemBarsPadding() + .fillMaxSize(), + header = { NotificationsOptInHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) }, + footer = { NotificationsOptInFooter(state) }, + ) { + NotificationsOptInContent(modifier = Modifier.fillMaxWidth()) + } +} + +@Composable +private fun NotificationsOptInHeader( + modifier: Modifier = Modifier, +) { + IconTitleSubtitleMolecule( + modifier = modifier, + title = stringResource(R.string.screen_notification_optin_title), + subTitle = stringResource(R.string.screen_notification_optin_subtitle), + iconImageVector = Icons.Default.Notifications, + ) +} + +@Composable +private fun NotificationsOptInFooter(state: NotificationsOptInState) { + ButtonColumnMolecule { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_ok), + onClick = { + state.eventSink(NotificationsOptInEvents.ContinueClicked) + } + ) + TextButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_not_now), + onClick = { + state.eventSink(NotificationsOptInEvents.NotNowClicked) + } + ) + } +} + +@Composable +private fun NotificationsOptInContent( + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column( + verticalArrangement = Arrangement.spacedBy( + 16.dp, + alignment = Alignment.CenterVertically + ) + ) { + NotificationRow( + avatarLetter = "M", + avatarColorsId = "5", + firstRowPercent = 1f, + secondRowPercent = 0.4f + ) + + NotificationRow( + avatarLetter = "A", + avatarColorsId = "1", + firstRowPercent = 1f, + secondRowPercent = 1f + ) + + NotificationRow( + avatarLetter = "T", + avatarColorsId = "4", + firstRowPercent = 0.65f, + secondRowPercent = 0f + ) + } + } +} + +@Composable +private fun NotificationRow( + avatarLetter: String, + avatarColorsId: String, + firstRowPercent: Float, + secondRowPercent: Float, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier, + color = ElementTheme.colors.bgCanvasDisabled, + shape = RoundedCornerShape(14.dp), + shadowElevation = 2.dp, + ) { + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Avatar( + avatarData = AvatarData(id = avatarColorsId, name = avatarLetter, size = AvatarSize.NotificationsOptIn), + ) + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Box( + modifier = Modifier + .clip(CircleShape) + .fillMaxWidth(firstRowPercent) + .height(10.dp) + .background(ElementTheme.colors.borderInteractiveSecondary) + ) + if (secondRowPercent > 0f) { + Box( + modifier = Modifier + .clip(CircleShape) + .fillMaxWidth(secondRowPercent) + .height(10.dp) + .background(ElementTheme.colors.borderInteractiveSecondary) + ) + } + } + } + } +} + +@DayNightPreviews +@Composable +internal fun NotificationsOptInViewPreview( + @PreviewParameter(NotificationsOptInStateProvider::class) state: NotificationsOptInState +) { + ElementPreview { + NotificationsOptInView( + onBack = {}, + state = state, + ) + } +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt index 108072cba9..3247d7faf8 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt @@ -16,6 +16,8 @@ package io.element.android.features.ftue.impl.state +import android.Manifest +import android.os.Build import androidx.annotation.VisibleForTesting import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.ftue.api.state.FtueState @@ -23,7 +25,9 @@ import io.element.android.features.ftue.impl.migration.MigrationScreenStore import io.element.android.features.ftue.impl.welcome.state.WelcomeScreenState import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.permissions.api.PermissionStateProvider import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first @@ -34,10 +38,12 @@ import javax.inject.Inject @ContributesBinding(SessionScope::class) class DefaultFtueState @Inject constructor( + private val sdkVersionProvider: BuildVersionSdkIntProvider, private val coroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, private val welcomeScreenState: WelcomeScreenState, private val migrationScreenStore: MigrationScreenStore, + private val permissionStateProvider: PermissionStateProvider, private val matrixClient: MatrixClient, ) : FtueState { @@ -47,6 +53,9 @@ class DefaultFtueState @Inject constructor( welcomeScreenState.reset() analyticsService.reset() migrationScreenStore.reset() + if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + permissionStateProvider.resetPermission(Manifest.permission.POST_NOTIFICATIONS) + } } init { @@ -63,7 +72,10 @@ class DefaultFtueState @Inject constructor( FtueStep.MigrationScreen -> if (shouldDisplayWelcomeScreen()) FtueStep.WelcomeScreen else getNextStep( FtueStep.WelcomeScreen ) - FtueStep.WelcomeScreen -> if (needsAnalyticsOptIn()) FtueStep.AnalyticsOptIn else getNextStep( + FtueStep.WelcomeScreen -> if (shouldAskNotificationPermissions()) FtueStep.NotificationsOptIn else getNextStep( + FtueStep.NotificationsOptIn + ) + FtueStep.NotificationsOptIn -> if (needsAnalyticsOptIn()) FtueStep.AnalyticsOptIn else getNextStep( FtueStep.AnalyticsOptIn ) FtueStep.AnalyticsOptIn -> null @@ -73,6 +85,7 @@ class DefaultFtueState @Inject constructor( return listOf( shouldDisplayMigrationScreen(), shouldDisplayWelcomeScreen(), + shouldAskNotificationPermissions(), needsAnalyticsOptIn() ).any { it } } @@ -90,6 +103,15 @@ class DefaultFtueState @Inject constructor( return welcomeScreenState.isWelcomeScreenNeeded() } + private fun shouldAskNotificationPermissions(): Boolean { + return if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + val permission = Manifest.permission.POST_NOTIFICATIONS + val isPermissionDenied = runBlocking { permissionStateProvider.isPermissionDenied(permission).first() } + val isPermissionGranted = permissionStateProvider.isPermissionGranted(permission) + !isPermissionGranted && !isPermissionDenied + } else false + } + fun setWelcomeScreenShown() { welcomeScreenState.setWelcomeScreenShown() updateState() @@ -104,5 +126,6 @@ class DefaultFtueState @Inject constructor( sealed interface FtueStep { data object MigrationScreen : FtueStep data object WelcomeScreen : FtueStep + data object NotificationsOptIn : FtueStep data object AnalyticsOptIn : FtueStep } diff --git a/features/ftue/impl/src/main/res/values-cs/translations.xml b/features/ftue/impl/src/main/res/values-cs/translations.xml index f1734c9c75..724f3793cd 100644 --- a/features/ftue/impl/src/main/res/values-cs/translations.xml +++ b/features/ftue/impl/src/main/res/values-cs/translations.xml @@ -1,6 +1,6 @@ - "Toto je jednorázový proces, děkujeme za čekání." + "Jedná se o jednorázový proces, prosíme o strpení." "Nastavení vašeho účtu" "Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku." "Historie zpráv šifrovaných místností nebude v této aktualizaci k dispozici." diff --git a/features/ftue/impl/src/main/res/values-ro/translations.xml b/features/ftue/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..f932256318 --- /dev/null +++ b/features/ftue/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,11 @@ + + + "Acesta este un proces care se desfășoară o singură dată, vă mulțumim pentru așteptare." + "Contul dumneavoastră se configurează" + "Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an." + "Istoricul mesajelor pentru camerele criptate nu va fi disponibil în această actualizare." + "Ne-ar plăcea să auzim de la dumneavoastră, spuneți-ne ce părere aveți prin intermediul paginii de setări." + "Să începem!" + "Iată ce trebuie să știți:" + "Bun venit la%1$s!" + diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index aee8470751..b398ba37d0 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -2,6 +2,8 @@ "This is a one time process, thanks for waiting." "Setting up your account." + "You can change your settings later." + "Allow notifications and never miss a message" "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms won’t be available in this update." "We’d love to hear from you, let us know what you think via the settings page." diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt index f93f761994..1388eb8fc1 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt @@ -16,6 +16,7 @@ package io.element.android.features.ftue.impl +import android.os.Build import com.google.common.truth.Truth.assertThat import io.element.android.features.ftue.impl.migration.InMemoryMigrationScreenStore import io.element.android.features.ftue.impl.migration.MigrationScreenStore @@ -25,8 +26,10 @@ import io.element.android.features.ftue.impl.welcome.state.FakeWelcomeState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.permissions.impl.FakePermissionStateProvider import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel @@ -51,13 +54,21 @@ class DefaultFtueStateTests { val welcomeState = FakeWelcomeState() val analyticsService = FakeAnalyticsService() val migrationScreenStore = InMemoryMigrationScreenStore() + val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true) val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) - val state = createState(coroutineScope, welcomeState, analyticsService, migrationScreenStore) + val state = createState( + coroutineScope = coroutineScope, + welcomeState = welcomeState, + analyticsService = analyticsService, + migrationScreenStore = migrationScreenStore, + permissionStateProvider = permissionStateProvider + ) welcomeState.setWelcomeScreenShown() analyticsService.setDidAskUserConsent() migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) + permissionStateProvider.setPermissionGranted() state.updateState() assertThat(state.shouldDisplayFlow.value).isFalse() @@ -71,9 +82,16 @@ class DefaultFtueStateTests { val welcomeState = FakeWelcomeState() val analyticsService = FakeAnalyticsService() val migrationScreenStore = InMemoryMigrationScreenStore() + val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) - val state = createState(coroutineScope, welcomeState, analyticsService, migrationScreenStore) + val state = createState( + coroutineScope = coroutineScope, + welcomeState = welcomeState, + analyticsService = analyticsService, + migrationScreenStore = migrationScreenStore, + permissionStateProvider = permissionStateProvider + ) val steps = mutableListOf() // First step, migration screen @@ -84,7 +102,11 @@ class DefaultFtueStateTests { steps.add(state.getNextStep(steps.lastOrNull())) welcomeState.setWelcomeScreenShown() - // Third step, analytics opt in + // Third step, notifications opt in + steps.add(state.getNextStep(steps.lastOrNull())) + permissionStateProvider.setPermissionGranted() + + // Fourth step, analytics opt in steps.add(state.getNextStep(steps.lastOrNull())) analyticsService.setDidAskUserConsent() @@ -94,6 +116,7 @@ class DefaultFtueStateTests { assertThat(steps).containsExactly( FtueStep.MigrationScreen, FtueStep.WelcomeScreen, + FtueStep.NotificationsOptIn, FtueStep.AnalyticsOptIn, null, // Final state ) @@ -107,11 +130,40 @@ class DefaultFtueStateTests { val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) val analyticsService = FakeAnalyticsService() val migrationScreenStore = InMemoryMigrationScreenStore() + val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) val state = createState( coroutineScope = coroutineScope, analyticsService = analyticsService, migrationScreenStore = migrationScreenStore, + permissionStateProvider = permissionStateProvider, + ) + + // Skip first 3 steps + migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) + state.setWelcomeScreenShown() + permissionStateProvider.setPermissionGranted() + + assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn) + + analyticsService.setDidAskUserConsent() + assertThat(state.getNextStep(FtueStep.WelcomeScreen)).isNull() + + // Cleanup + coroutineScope.cancel() + } + + @Test + fun `if version is older than 13 we don't display the notification opt in screen`() = runTest { + val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) + val analyticsService = FakeAnalyticsService() + val migrationScreenStore = InMemoryMigrationScreenStore() + + val state = createState( + sdkIntVersion = Build.VERSION_CODES.M, + coroutineScope = coroutineScope, + analyticsService = analyticsService, + migrationScreenStore = migrationScreenStore, ) migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) @@ -132,12 +184,16 @@ class DefaultFtueStateTests { welcomeState: FakeWelcomeState = FakeWelcomeState(), analyticsService: AnalyticsService = FakeAnalyticsService(), migrationScreenStore: MigrationScreenStore = InMemoryMigrationScreenStore(), + permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false), matrixClient: MatrixClient = FakeMatrixClient(), + sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU, // First version where notification permission is required ) = DefaultFtueState( + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion), coroutineScope = coroutineScope, analyticsService = analyticsService, welcomeScreenState = welcomeState, migrationScreenStore = migrationScreenStore, + permissionStateProvider = permissionStateProvider, matrixClient = matrixClient, ) } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt index 6e19879b86..1a9a33130d 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt @@ -25,10 +25,17 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class MigrationScreenPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial`() = runTest { val presenter = createPresenter() diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt new file mode 100644 index 0000000000..74a867a0fc --- /dev/null +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt @@ -0,0 +1,148 @@ +/* + * 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.ftue.impl.notifications + +import android.os.Build +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth +import io.element.android.libraries.permissions.api.PermissionStateProvider +import io.element.android.libraries.permissions.api.PermissionsPresenter +import io.element.android.libraries.permissions.impl.FakePermissionStateProvider +import io.element.android.libraries.permissions.test.FakePermissionsPresenter +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider +import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class NotificationsOptInPresenterTests { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + + private var isFinished = false + + @Test + fun `initial state`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.notificationsPermissionState.showDialog).isFalse() + } + } + + @Test + fun `show dialog on continue clicked`() = runTest { + val permissionPresenter = FakePermissionsPresenter() + val presenter = createPresenter(permissionPresenter) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(NotificationsOptInEvents.ContinueClicked) + Truth.assertThat(awaitItem().notificationsPermissionState.showDialog).isTrue() + } + } + + @Test + fun `finish flow on continue clicked with permission already granted`() = runTest { + val permissionPresenter = FakePermissionsPresenter().apply { + setPermissionGranted() + } + val presenter = createPresenter(permissionPresenter) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(NotificationsOptInEvents.ContinueClicked) + Truth.assertThat(isFinished).isTrue() + } + } + + @Test + fun `finish flow on not now clicked`() = runTest { + val permissionPresenter = FakePermissionsPresenter() + val presenter = createPresenter( + permissionsPresenter = permissionPresenter, + sdkIntVersion = Build.VERSION_CODES.M + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(NotificationsOptInEvents.NotNowClicked) + Truth.assertThat(isFinished).isTrue() + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `set permission denied on not now clicked in API 33`() = runTest(StandardTestDispatcher()) { + val permissionPresenter = FakePermissionsPresenter() + val permissionStateProvider = FakePermissionStateProvider() + val presenter = createPresenter( + permissionsPresenter = permissionPresenter, + permissionStateProvider = permissionStateProvider, + sdkIntVersion = Build.VERSION_CODES.TIRAMISU + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(NotificationsOptInEvents.NotNowClicked) + + // Allow background coroutines to run + runCurrent() + + val isPermissionDenied = runBlocking { + permissionStateProvider.isPermissionDenied("notifications").first() + } + Truth.assertThat(isPermissionDenied).isTrue() + } + } + + private fun TestScope.createPresenter( + permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), + permissionStateProvider: PermissionStateProvider = FakePermissionStateProvider(), + sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU, + ) = NotificationsOptInPresenter( + permissionsPresenterFactory = object : PermissionsPresenter.Factory { + override fun create(permission: String): PermissionsPresenter { + return permissionsPresenter + } + }, + callback = object : NotificationsOptInNode.Callback { + override fun onNotificationsOptInFinished() { + isFinished = true + } + }, + appCoroutineScope = this, + permissionStateProvider = permissionStateProvider, + buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion), + ) +} diff --git a/features/invitelist/impl/build.gradle.kts b/features/invitelist/impl/build.gradle.kts index cd008472b5..d8fb25585f 100644 --- a/features/invitelist/impl/build.gradle.kts +++ b/features/invitelist/impl/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.features.invitelist.test) testImplementation(projects.services.analytics.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index adb2042b62..9ba26b5e2d 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -44,10 +44,15 @@ import io.element.android.libraries.push.api.notifications.NotificationDrawerMan import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class InviteListPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() @Test fun `present - starts empty, adds invites when received`() = runTest { diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt index 3075d3c686..7bbab71a8d 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt @@ -29,14 +29,20 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MembershipCha import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class LeaveRoomPresenterImplTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state hides all dialogs`() = runTest { val presenter = createPresenter() diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index 325003b110..dd265ee7f0 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -57,4 +57,5 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) testImplementation(projects.features.messages.test) + testImplementation(projects.tests.testutils) } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt index 2ea7dc20e1..94af55c01c 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt @@ -26,10 +26,10 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import im.vector.app.features.analytics.plan.Composer import io.element.android.features.location.impl.common.MapDefaults +import io.element.android.features.location.impl.common.actions.LocationActions import io.element.android.features.location.impl.common.permissions.PermissionsEvents import io.element.android.features.location.impl.common.permissions.PermissionsPresenter import io.element.android.features.location.impl.common.permissions.PermissionsState -import io.element.android.features.location.impl.common.actions.LocationActions import io.element.android.features.messages.api.MessageComposerContext import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta @@ -119,9 +119,8 @@ class SendLocationPresenter @Inject constructor( Composer( inThread = messageComposerContext.composerMode.inThread, isEditing = messageComposerContext.composerMode.isEditing, - isLocation = true, isReply = messageComposerContext.composerMode.isReply, - locationType = Composer.LocationType.PinDrop, + messageType = Composer.MessageType.LocationPin, ) ) } @@ -138,9 +137,8 @@ class SendLocationPresenter @Inject constructor( Composer( inThread = messageComposerContext.composerMode.inThread, isEditing = messageComposerContext.composerMode.isEditing, - isLocation = true, isReply = messageComposerContext.composerMode.isReply, - locationType = Composer.LocationType.MyLocation, + messageType = Composer.MessageType.LocationUser, ) ) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt index 21766a2cf0..af31f1bfeb 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt @@ -34,12 +34,18 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.SendLocationInvocation import io.element.android.libraries.textcomposer.MessageComposerMode import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class SendLocationPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val permissionsPresenterFake = PermissionsPresenterFake() private val fakeMatrixRoom = FakeMatrixRoom() private val fakeAnalyticsService = FakeAnalyticsService() @@ -302,9 +308,8 @@ class SendLocationPresenterTest { Composer( inThread = false, isEditing = false, - isLocation = true, isReply = false, - locationType = Composer.LocationType.MyLocation, + messageType = Composer.MessageType.LocationUser, ) ) } @@ -359,9 +364,8 @@ class SendLocationPresenterTest { Composer( inThread = false, isEditing = false, - isLocation = true, isReply = false, - locationType = Composer.LocationType.PinDrop, + messageType = Composer.MessageType.LocationPin, ) ) } @@ -406,9 +410,8 @@ class SendLocationPresenterTest { Composer( inThread = false, isEditing = true, - isLocation = true, isReply = false, - locationType = Composer.LocationType.PinDrop, + messageType = Composer.MessageType.LocationPin, ) ) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt index 12ccdc16a5..ebb146ca57 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt @@ -27,12 +27,18 @@ import io.element.android.features.location.impl.common.permissions.PermissionsP import io.element.android.features.location.impl.common.permissions.PermissionsPresenterFake import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ShowLocationPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val permissionsPresenterFake = PermissionsPresenterFake() private val fakeLocationActions = FakeLocationActions() private val fakeBuildMeta = aBuildMeta(applicationName = "app name") diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml index 0c58c45d03..e780269a39 100644 --- a/features/login/impl/src/main/res/values-ro/translations.xml +++ b/features/login/impl/src/main/res/values-ro/translations.xml @@ -5,10 +5,11 @@ "Introduceţi un termen de căutare sau o adresă de domeniu." "Căutați o companie, o comunitate sau un server privat." "Găsiți un furnizor de cont" - "Aici vor trăi conversațiile - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile." + "Aici vor trăi conversațiile dumneavoastră - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile." "Sunteți pe cale să vă conectați la %s" - "Aici vor trăi conversațiile - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile." + "Aici vor trăi conversațiile dumneavoastră - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile." "Sunteți pe cale să creați un cont pe %s" + "Matrix.org este un server mare și gratuit din rețeaua publică Matrix pentru comunicații sigure și descentralizate, administrat de Fundația Matrix.org." "Altul" "Utilizați un alt furnizor de cont, cum ar fi propriul server privat sau un cont de serviciu." "Schimbați furnizorul contului" 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 242ec4bb2c..3ecb8a52d0 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -9,6 +9,7 @@ "Вы собираетесь войти в %s" "Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем." "Вы собираетесь создать учетную запись на %s" + "Matrix.org — это большой бесплатный сервер в общедоступной сети Matrix для безопасной децентрализованной связи, управляемый Matrix.org Foundation." "Другое" "Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись." "Сменить поставщика учетной записи" diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index 009ab31dcd..4a2fcc397c 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -26,10 +26,17 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ChangeServerPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = ChangeServerPresenter( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt index 32d1c6918a..60cc1285e3 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt @@ -27,11 +27,18 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class OidcPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = OidcPresenter( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt index f807355cb1..6fe3248ec3 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt @@ -24,10 +24,17 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.changeserver.ChangeServerPresenter import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ChangeAccountProviderPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val changeServerPresenter = ChangeServerPresenter( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index 95cd9bf053..0ec396694b 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -31,11 +31,18 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.waitForPredicate import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ConfirmAccountProviderPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial test`() = runTest { val presenter = createConfirmAccountProviderPresenter() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index 421eb869b0..1848940966 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -31,10 +31,17 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class LoginPasswordPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val authenticationService = FakeAuthenticationService() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index ae6ae4d344..89d1a22b0c 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -30,11 +30,18 @@ import io.element.android.features.login.impl.resolver.network.WellKnownSlidingS import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class SearchAccountProviderPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val fakeWellknownRequest = FakeWellknownRequest() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt index 507e8cec8b..dadcbe186a 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt @@ -30,10 +30,17 @@ import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class WaitListPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val authenticationService = FakeAuthenticationService().apply { diff --git a/features/logout/impl/build.gradle.kts b/features/logout/impl/build.gradle.kts index 464695e169..52a9f76b41 100644 --- a/features/logout/impl/build.gradle.kts +++ b/features/logout/impl/build.gradle.kts @@ -48,4 +48,5 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) } diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt index 29df521cb1..9527012e28 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt @@ -25,10 +25,17 @@ import io.element.android.features.logout.api.LogoutPreferenceState import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class LogoutPreferencePresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = DefaultLogoutPreferencePresenter( diff --git a/features/messages/api/build.gradle.kts b/features/messages/api/build.gradle.kts index 756014e97d..9e890265ec 100644 --- a/features/messages/api/build.gradle.kts +++ b/features/messages/api/build.gradle.kts @@ -25,5 +25,5 @@ android { dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) - api(projects.libraries.textcomposer) + api(projects.libraries.textcomposer.impl) } diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 71030c3ca0..00d65eba6a 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) - implementation(projects.libraries.textcomposer) + implementation(projects.libraries.textcomposer.impl) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.eventformatter.api) @@ -61,7 +61,7 @@ dependencies { implementation(libs.accompanist.systemui) implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.zoomableimage) - implementation(libs.vanniktech.emoji) + implementation(libs.matrix.emojibase.bindings) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -76,6 +76,7 @@ dependencies { testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediapickers.test) + testImplementation(projects.libraries.textcomposer.test) testImplementation(libs.test.mockk) ksp(libs.showkase.processor) 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 8a374471e3..0831afb699 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 @@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.features.analytics.plan.PollEnd import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction @@ -59,7 +60,6 @@ import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -75,6 +75,7 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.room.canRedactAsState import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -93,6 +94,7 @@ class MessagesPresenter @AssistedInject constructor( private val messageSummaryFormatter: MessageSummaryFormatter, private val dispatchers: CoroutineDispatchers, private val clipboardHelper: ClipboardHelper, + private val analyticsService: AnalyticsService, @Assisted private val navigator: MessagesNavigator, ) : Presenter { @@ -176,7 +178,7 @@ class MessagesPresenter @AssistedInject constructor( snackbarMessage = snackbarMessage, showReinvitePrompt = showReinvitePrompt, inviteProgress = inviteProgress.value, - eventSink = ::handleEvents + eventSink = { handleEvents(it) } ) } @@ -202,6 +204,7 @@ class MessagesPresenter @AssistedInject constructor( TimelineItemAction.Developer -> handleShowDebugInfoAction(targetEvent) TimelineItemAction.Forward -> handleForwardAction(targetEvent) TimelineItemAction.ReportContent -> handleReportAction(targetEvent) + TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent) } } @@ -214,7 +217,8 @@ class MessagesPresenter @AssistedInject constructor( } private fun CoroutineScope.reinviteOtherUser(inviteProgress: MutableState>) = launch(dispatchers.io) { - suspend { + inviteProgress.value = Async.Loading() + runCatching { room.updateMembers() val memberList = when (val memberState = room.membersStateFlow.value) { @@ -227,7 +231,14 @@ class MessagesPresenter @AssistedInject constructor( room.inviteUserById(member.userId).onFailure { t -> Timber.e(t, "Failed to reinvite DM partner") }.getOrThrow() - }.runCatchingUpdatingState(inviteProgress) + }.fold( + onSuccess = { + inviteProgress.value = Async.Success(Unit) + }, + onFailure = { + inviteProgress.value = Async.Failure(it) + } + ) } private suspend fun handleActionRedact(event: TimelineItem.Event) { @@ -242,7 +253,9 @@ class MessagesPresenter @AssistedInject constructor( private fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) { val composerMode = MessageComposerMode.Edit( targetEvent.eventId, - (targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty(), + (targetEvent.content as? TimelineItemTextBasedContent)?.let { + it.htmlBody ?: it.body + }.orEmpty(), targetEvent.transactionId, ) composerState.eventSink( @@ -310,6 +323,13 @@ class MessagesPresenter @AssistedInject constructor( navigator.onReportContentClicked(event.eventId, event.senderId) } + private suspend fun handleEndPollAction(event: TimelineItem.Event) { + event.eventId?.let { + room.endPoll(it, "The poll with event id: $it has ended.") + analyticsService.capture(PollEnd()) + } + } + private suspend fun handleCopyContents(event: TimelineItem.Event) { val content = when (event.content) { is TimelineItemTextBasedContent -> event.content.body 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 9b3f5073a1..6ca799dc84 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 @@ -30,6 +30,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.persistentSetOf open class MessagesStateProvider : PreviewParameterProvider { @@ -54,7 +55,9 @@ fun aMessagesState() = MessagesState( userHasPermissionToSendMessage = true, userHasPermissionToRedact = false, composerState = aMessageComposerState().copy( - text = "Hello", + richTextEditorState = RichTextEditorState("Hello", fake = true).apply { + requestFocus() + }, isFullScreen = false, mode = MessageComposerMode.Normal("Hello"), ), @@ -67,7 +70,7 @@ fun aMessagesState() = MessagesState( ), actionListState = anActionListState(), customReactionState = CustomReactionState( - selectedEventId = null, + target = CustomReactionState.Target.None, eventSink = {}, selectedEmoji = persistentSetOf(), ), 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 92b15f4fc0..ab11ca05d7 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 @@ -123,7 +123,13 @@ fun MessagesView( fun onMessageLongClicked(event: TimelineItem.Event) { Timber.v("OnMessageLongClicked= ${event.id}") localView.hideKeyboard() - state.actionListState.eventSink(ActionListEvents.ComputeForMessage(event, state.userHasPermissionToRedact)) + state.actionListState.eventSink( + ActionListEvents.ComputeForMessage( + event = event, + canRedact = state.userHasPermissionToRedact, + canSendMessage = state.userHasPermissionToSendMessage, + ) + ) } fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) { @@ -141,7 +147,7 @@ fun MessagesView( } fun onMoreReactionsClicked(event: TimelineItem.Event) { - state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event)) + state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) } Scaffold( @@ -194,18 +200,17 @@ fun MessagesView( state = state.actionListState, onActionSelected = ::onActionSelected, onCustomReactionClicked = { event -> - state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event)) + if (event.eventId == null) return@ActionListView + state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) }, onEmojiReactionClicked = ::onEmojiReactionClicked, ) CustomReactionBottomSheet( state = state.customReactionState, - onEmojiSelected = { emoji -> - state.customReactionState.selectedEventId?.let { eventId -> - state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, eventId)) - state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) - } + onEmojiSelected = { eventId, emoji -> + state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, eventId)) + state.customReactionState.eventSink(CustomReactionEvents.DismissCustomReactionSheet) } ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt index c5e6618736..7c8fad6c7c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt @@ -20,5 +20,9 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem sealed interface ActionListEvents { data object Clear : ActionListEvents - data class ComputeForMessage(val event: TimelineItem.Event, val canRedact: Boolean) : ActionListEvents + data class ComputeForMessage( + val event: TimelineItem.Event, + val canRedact: Boolean, + val canSendMessage: Boolean, + ) : ActionListEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index e654365bcd..b179261c72 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -62,6 +62,7 @@ class ActionListPresenter @Inject constructor( is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage( timelineItem = event.event, userCanRedact = event.canRedact, + userCanSendMessage = event.canSendMessage, target = target, ) } @@ -70,13 +71,14 @@ class ActionListPresenter @Inject constructor( return ActionListState( target = target.value, displayEmojiReactions = displayEmojiReactions, - eventSink = ::handleEvents + eventSink = { handleEvents(it) } ) } private fun CoroutineScope.computeForMessage( timelineItem: TimelineItem.Event, userCanRedact: Boolean, + userCanSendMessage: Boolean, target: MutableState ) = launch { target.value = ActionListState.Target.Loading(timelineItem) @@ -99,6 +101,17 @@ class ActionListPresenter @Inject constructor( } is TimelineItemPollContent -> { buildList { + val isMineOrCanRedact = timelineItem.isMine || userCanRedact + + // TODO Poll: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()` + // when touching this + // if (timelineItem.isRemote) { + // // Can only reply or forward messages already uploaded to the server + // add(TimelineItemAction.Reply) + // } + if (!timelineItem.content.isEnded && timelineItem.isRemote && isMineOrCanRedact) { + add(TimelineItemAction.EndPoll) + } if (timelineItem.content.canBeCopied()) { add(TimelineItemAction.Copy) } @@ -108,7 +121,7 @@ class ActionListPresenter @Inject constructor( if (!timelineItem.isMine) { add(TimelineItemAction.ReportContent) } - if (timelineItem.isMine || userCanRedact) { + if (isMineOrCanRedact) { add(TimelineItemAction.Redact) } } @@ -116,7 +129,9 @@ class ActionListPresenter @Inject constructor( else -> buildList { if (timelineItem.isRemote) { // Can only reply or forward messages already uploaded to the server - add(TimelineItemAction.Reply) + if (userCanSendMessage) { + add(TimelineItemAction.Reply) + } add(TimelineItemAction.Forward) } if (timelineItem.isMine && timelineItem.isTextMessage) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index 09213d64b3..bb9c851288 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemLocationContent +import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVideoContent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -83,6 +84,15 @@ open class ActionListStateProvider : PreviewParameterProvider { ), displayEmojiReactions = false, ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemPollContent()).copy( + reactionsState = reactionsState + ), + actions = aTimelineItemPollActionList(), + ), + displayEmojiReactions = false, + ), ) } } @@ -104,3 +114,13 @@ fun aTimelineItemActionList(): ImmutableList { TimelineItemAction.Developer, ) } +fun aTimelineItemPollActionList(): ImmutableList { + return persistentListOf( + TimelineItemAction.EndPoll, + TimelineItemAction.Reply, + TimelineItemAction.Copy, + TimelineItemAction.Developer, + TimelineItemAction.ReportContent, + TimelineItemAction.Redact, + ) +} 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 b6141218eb..7a8a1fa1db 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 @@ -35,4 +35,5 @@ sealed class TimelineItemAction( data object Edit : TimelineItemAction(CommonStrings.action_edit, VectorIcons.Edit) data object Developer : TimelineItemAction(CommonStrings.action_view_source, VectorIcons.DeveloperMode) data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, VectorIcons.ReportContent, destructive = true) + data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, VectorIcons.PollEnd) } 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 43805bb5c0..38ef458bd9 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 @@ -26,6 +26,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AttachFile import androidx.compose.material.icons.filled.BarChart import androidx.compose.material.icons.filled.Collections +import androidx.compose.material.icons.filled.FormatColorText import androidx.compose.material.icons.filled.LocationOn import androidx.compose.material.icons.filled.PhotoCamera import androidx.compose.material.icons.filled.Videocam @@ -145,6 +146,11 @@ internal fun AttachmentSourcePickerMenu( text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) }, ) } + ListItem( + modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) }, + icon = { Icon(Icons.Default.FormatColorText, null) }, + text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) }, + ) } } 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 d99eb3c158..92b180f326 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,16 +17,15 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Immutable +import io.element.android.libraries.textcomposer.Message import io.element.android.libraries.textcomposer.MessageComposerMode @Immutable sealed interface MessageComposerEvents { data object ToggleFullScreenState : MessageComposerEvents - data class FocusChanged(val hasFocus: Boolean) : MessageComposerEvents - data class SendMessage(val message: String) : MessageComposerEvents + data class SendMessage(val message: Message) : MessageComposerEvents data object CloseSpecialMode : MessageComposerEvents data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvents - data class UpdateText(val text: String) : MessageComposerEvents data object AddAttachment : MessageComposerEvents data object DismissAttachmentMenu : MessageComposerEvents sealed interface PickAttachmentSource : MessageComposerEvents { @@ -37,5 +36,7 @@ sealed interface MessageComposerEvents { data object Location : PickAttachmentSource data object Poll : PickAttachmentSource } + data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvents data object CancelSendAttachment : MessageComposerEvents + data class Error(val error: Throwable) : 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 cc735dc008..215826dade 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 @@ -44,8 +44,10 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.textcomposer.Message import io.element.android.libraries.textcomposer.MessageComposerMode import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -67,6 +69,7 @@ class MessageComposerPresenter @Inject constructor( private val snackbarDispatcher: SnackbarDispatcher, private val analyticsService: AnalyticsService, private val messageComposerContext: MessageComposerContextImpl, + private val richTextEditorStateFactory: RichTextEditorStateFactory, ) : Presenter { @SuppressLint("UnsafeOptInUsageError") @@ -103,19 +106,16 @@ class MessageComposerPresenter @Inject constructor( val isFullScreen = rememberSaveable { mutableStateOf(false) } - val hasFocus = remember { - mutableStateOf(false) - } - val text: MutableState = rememberSaveable { - mutableStateOf("") - } + val richTextEditorState = richTextEditorStateFactory.create() val ongoingSendAttachmentJob = remember { mutableStateOf(null) } var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) } + var showTextFormatting: Boolean by remember { mutableStateOf(false) } LaunchedEffect(messageComposerContext.composerMode) { when (val modeValue = messageComposerContext.composerMode) { - is MessageComposerMode.Edit -> text.value = modeValue.defaultContent + is MessageComposerMode.Edit -> + richTextEditorState.setHtml(modeValue.defaultContent) else -> Unit } } @@ -136,18 +136,15 @@ class MessageComposerPresenter @Inject constructor( when (event) { MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value - is MessageComposerEvents.FocusChanged -> hasFocus.value = event.hasFocus - - is MessageComposerEvents.UpdateText -> text.value = event.text MessageComposerEvents.CloseSpecialMode -> { - text.value = "" + richTextEditorState.setHtml("") messageComposerContext.composerMode = MessageComposerMode.Normal("") } is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage( - text = event.message, + message = event.message, updateComposerMode = { messageComposerContext.composerMode = it }, - textState = text + richTextEditorState = richTextEditorState, ) is MessageComposerEvents.SetMode -> { messageComposerContext.composerMode = event.composerMode @@ -156,7 +153,7 @@ class MessageComposerPresenter @Inject constructor( inThread = messageComposerContext.composerMode.inThread, isEditing = messageComposerContext.composerMode.isEditing, isReply = messageComposerContext.composerMode.isReply, - isLocation = false, + messageType = Composer.MessageType.Text, ) ) } @@ -194,43 +191,51 @@ class MessageComposerPresenter @Inject constructor( ongoingSendAttachmentJob.value == null } } + is MessageComposerEvents.ToggleTextFormatting -> { + showAttachmentSourcePicker = false + showTextFormatting = event.enabled + } + is MessageComposerEvents.Error -> { + analyticsService.trackError(event.error) + } } } return MessageComposerState( - text = text.value, + richTextEditorState = richTextEditorState, isFullScreen = isFullScreen.value, - hasFocus = hasFocus.value, mode = messageComposerContext.composerMode, showAttachmentSourcePicker = showAttachmentSourcePicker, + showTextFormatting = showTextFormatting, canShareLocation = canShareLocation.value, canCreatePoll = canCreatePoll.value, attachmentsState = attachmentsState.value, - eventSink = ::handleEvents + eventSink = { handleEvents(it) } ) } private fun CoroutineScope.sendMessage( - text: String, + message: Message, updateComposerMode: (newComposerMode: MessageComposerMode) -> Unit, - textState: MutableState + richTextEditorState: RichTextEditorState, ) = launch { val capturedMode = messageComposerContext.composerMode // Reset composer right away - textState.value = "" + richTextEditorState.setHtml("") updateComposerMode(MessageComposerMode.Normal("")) when (capturedMode) { - is MessageComposerMode.Normal -> room.sendMessage(text) + is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html) is MessageComposerMode.Edit -> { val eventId = capturedMode.eventId val transactionId = capturedMode.transactionId - room.editMessage(eventId, transactionId, text) + room.editMessage(eventId, transactionId, message.markdown, message.html) } is MessageComposerMode.Quote -> TODO() is MessageComposerMode.Reply -> room.replyMessage( capturedMode.eventId, - text + message.markdown, + message.html, ) } } 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 dbbc62ca47..65fac53fdc 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,21 +19,23 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.ImmutableList @Immutable data class MessageComposerState( - val text: String?, + val richTextEditorState: RichTextEditorState, val isFullScreen: Boolean, - val hasFocus: Boolean, val mode: MessageComposerMode, val showAttachmentSourcePicker: Boolean, + val showTextFormatting: Boolean, val canShareLocation: Boolean, val canCreatePoll: Boolean, val attachmentsState: AttachmentsState, - val eventSink: (MessageComposerEvents) -> Unit + val eventSink: (MessageComposerEvents) -> Unit, ) { - val isSendButtonVisible: Boolean = text.isNullOrEmpty().not() + val canSendMessage: Boolean = richTextEditorState.messageHtml.isNotEmpty() + val hasFocus: Boolean = richTextEditorState.hasFocus } @Immutable 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 2217b574b4..d86969fc19 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 @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.wysiwyg.compose.RichTextEditorState open class MessageComposerStateProvider : PreviewParameterProvider { override val values: Sequence @@ -27,19 +28,20 @@ open class MessageComposerStateProvider : PreviewParameterProvider { private val timeline = room.timeline @@ -61,7 +65,7 @@ class TimelinePresenter @Inject constructor( mutableStateOf(null) } - val lastReadReceiptIndex = rememberSaveable { mutableStateOf(Int.MAX_VALUE) } + val lastReadReceiptIndex = rememberSaveable { mutableIntStateOf(Int.MAX_VALUE) } val lastReadReceiptId = rememberSaveable { mutableStateOf(null) } val timelineItems by timelineItemsFactory.collectItemsAsState() @@ -92,7 +96,7 @@ class TimelinePresenter @Inject constructor( pollStartId = event.pollStartId, answers = listOf(event.answerId), ) - // TODO Polls: Send poll vote analytic + analyticsService.capture(PollVote()) } } } @@ -115,11 +119,11 @@ class TimelinePresenter @Inject constructor( return TimelineState( highlightedEventId = highlightedEventId.value, - canReply = userHasPermissionToSendMessage, + userHasPermissionToSendMessage = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, hasNewItems = hasNewItems.value, - eventSink = ::handleEvents + eventSink = { handleEvents(it) } ) } 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 ab5874d39c..1c7ff1b87c 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,7 +26,7 @@ import kotlinx.collections.immutable.ImmutableList data class TimelineState( val timelineItems: ImmutableList, val highlightedEventId: EventId?, - val canReply: Boolean, + val userHasPermissionToSendMessage: Boolean, val paginationState: MatrixTimeline.PaginationState, val hasNewItems: Boolean, val eventSink: (TimelineEvents) -> Unit 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 6172da0469..8a4d45e40c 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 @@ -44,7 +44,7 @@ fun aTimelineState(timelineItems: ImmutableList = persistentListOf timelineItems = timelineItems, paginationState = MatrixTimeline.PaginationState(isBackPaginating = false, hasMoreToLoadBackwards = true), highlightedEventId = null, - canReply = true, + userHasPermissionToSendMessage = true, hasNewItems = false, eventSink = {}, ) 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 ff90e8d29a..e48eb4f72b 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 @@ -63,6 +63,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent +import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.FloatingActionButton @@ -119,7 +120,7 @@ fun TimelineView( TimelineItemRow( timelineItem = timelineItem, highlightedItem = state.highlightedEventId?.value, - canReply = state.canReply, + userHasPermissionToSendMessage = state.userHasPermissionToSendMessage, onClick = onMessageClicked, onLongClick = onMessageLongClicked, onUserDataClick = onUserDataClicked, @@ -156,7 +157,7 @@ fun TimelineView( fun TimelineItemRow( timelineItem: TimelineItem, highlightedItem: String?, - canReply: Boolean, + userHasPermissionToSendMessage: Boolean, onUserDataClick: (UserId) -> Unit, onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, @@ -189,7 +190,7 @@ fun TimelineItemRow( TimelineItemEventRow( event = timelineItem, isHighlighted = highlightedItem == timelineItem.identifier(), - canReply = canReply, + canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(), onClick = { onClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, onUserDataClick = onUserDataClick, @@ -228,7 +229,7 @@ fun TimelineItemRow( TimelineItemRow( timelineItem = subGroupEvent, highlightedItem = highlightedItem, - canReply = false, + userHasPermissionToSendMessage = false, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, 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 10671ee00f..7e21ff649d 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 @@ -75,6 +75,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent +import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.components.EqualWidthColumn import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -327,6 +328,7 @@ private fun MessageSenderInformation( ) { val avatarStrokeColor = MaterialTheme.colorScheme.background val avatarSize = senderAvatar.size.dp + val avatarColors = AvatarColorsProvider.provide(senderAvatar.id, ElementTheme.isLightTheme) Box( modifier = modifier ) { @@ -350,7 +352,7 @@ private fun MessageSenderInformation( text = sender, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary, + color = avatarColors.foreground, style = ElementTheme.typography.fontBodyMdMedium, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt index d817ec0cd4..3fe739a592 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt @@ -22,34 +22,35 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import com.vanniktech.emoji.Emoji -import io.element.android.features.messages.impl.timeline.components.EmojiPicker +import io.element.android.emojibasebindings.Emoji import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.hide +import io.element.android.libraries.matrix.api.core.EventId @OptIn(ExperimentalMaterial3Api::class) @Composable fun CustomReactionBottomSheet( state: CustomReactionState, - onEmojiSelected: (Emoji) -> Unit, + onEmojiSelected: (EventId, Emoji) -> Unit, modifier: Modifier = Modifier, ) { val sheetState = rememberModalBottomSheetState() val coroutineScope = rememberCoroutineScope() + val target = state.target as? CustomReactionState.Target.Success fun onDismiss() { - state.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) + state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) } fun onEmojiSelectedDismiss(emoji: Emoji) { + if (target?.event?.eventId == null) return sheetState.hide(coroutineScope) { - state.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) - onEmojiSelected(emoji) + state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + onEmojiSelected(target.event.eventId, emoji) } } - val isVisible = state.selectedEventId != null - if (isVisible) { + if (target?.emojibaseStore != null && target.event.eventId != null) { ModalBottomSheet( onDismissRequest = ::onDismiss, sheetState = sheetState, @@ -57,8 +58,9 @@ fun CustomReactionBottomSheet( ) { EmojiPicker( onEmojiSelected = ::onEmojiSelectedDismiss, - modifier = Modifier.fillMaxSize(), + emojibaseStore = target.emojibaseStore, selectedEmojis = state.selectedEmoji, + modifier = Modifier.fillMaxSize(), ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt index a0d69df372..2458686a83 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt @@ -19,5 +19,6 @@ package io.element.android.features.messages.impl.timeline.components.customreac import io.element.android.features.messages.impl.timeline.model.TimelineItem sealed interface CustomReactionEvents { - data class UpdateSelectedEvent(val event: TimelineItem.Event?) : CustomReactionEvents + data class ShowCustomReactionSheet(val event: TimelineItem.Event) : CustomReactionEvents + object DismissCustomReactionSheet : CustomReactionEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt index f094f2dbc6..8bbd6cbff7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt @@ -17,28 +17,53 @@ package io.element.android.features.messages.impl.timeline.components.customreaction import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.MutableState 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 androidx.compose.runtime.rememberCoroutineScope import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch +import io.element.android.features.messages.impl.timeline.model.TimelineItem import kotlinx.collections.immutable.toImmutableSet import javax.inject.Inject -class CustomReactionPresenter @Inject constructor() : Presenter { +class CustomReactionPresenter @Inject constructor( + private val emojibaseProvider: EmojibaseProvider +) : Presenter { @Composable override fun present(): CustomReactionState { - var selectedEvent by remember { mutableStateOf(null) } + val target: MutableState = remember { + mutableStateOf(CustomReactionState.Target.None) + } - fun handleEvents(event: CustomReactionEvents) { - when (event) { - is CustomReactionEvents.UpdateSelectedEvent -> selectedEvent = event.event + val localCoroutineScope = rememberCoroutineScope() + fun handleShowCustomReactionSheet(event: TimelineItem.Event) { + target.value = CustomReactionState.Target.Loading(event) + localCoroutineScope.launch { + target.value = CustomReactionState.Target.Success( + event = event, + emojibaseStore = emojibaseProvider.emojibaseStore + ) } } - val selectedEmoji = selectedEvent?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet() - return CustomReactionState(selectedEventId = selectedEvent?.eventId, selectedEmoji = selectedEmoji, eventSink = ::handleEvents) + fun handleDismissCustomReactionSheet() { + target.value = CustomReactionState.Target.None + } + + fun handleEvents(event: CustomReactionEvents) { + when (event) { + is CustomReactionEvents.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event) + is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet() + } + } + val event = (target.value as? CustomReactionState.Target.Success)?.event + val selectedEmoji = event?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet() + return CustomReactionState( + target = target.value, + selectedEmoji = selectedEmoji, + eventSink = { handleEvents(it) } + ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt index 9de1642dff..f6f7d2b0f9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt @@ -16,11 +16,23 @@ package io.element.android.features.messages.impl.timeline.components.customreaction -import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.features.messages.impl.timeline.model.TimelineItem import kotlinx.collections.immutable.ImmutableSet data class CustomReactionState( - val selectedEventId: EventId?, + val target: Target, val selectedEmoji: ImmutableSet, val eventSink: (CustomReactionEvents) -> Unit, -) +) { + sealed interface Target { + + data object None : Target + data class Loading(val event: TimelineItem.Event) : Target + data class Success( + val event: TimelineItem.Event, + val emojibaseStore: EmojibaseStore, + ) : Target + } +} + diff --git a/app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt similarity index 59% rename from app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt index dd1e7455c6..a68d6f0d4e 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.element.android.x.initializer +package io.element.android.features.messages.impl.timeline.components.customreaction -import androidx.startup.Initializer -import com.vanniktech.emoji.EmojiManager -import com.vanniktech.emoji.google.GoogleEmojiProvider +import android.content.Context +import io.element.android.emojibasebindings.EmojibaseDatasource +import io.element.android.emojibasebindings.EmojibaseStore -class EmojiInitializer : Initializer { - override fun create(context: android.content.Context) { - EmojiManager.install(GoogleEmojiProvider()) +class DefaultEmojibaseProvider(val context: Context): EmojibaseProvider { + + override val emojibaseStore: EmojibaseStore by lazy { + EmojibaseDatasource().load(context) } - override fun dependencies(): MutableList>> = mutableListOf() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt similarity index 83% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt index 45fd5bf186..1012d61020 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.messages.impl.timeline.components +package io.element.android.features.messages.impl.timeline.components.customreaction import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -41,11 +41,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.vanniktech.emoji.Emoji -import com.vanniktech.emoji.google.GoogleEmojiProvider +import io.element.android.emojibasebindings.Emoji +import io.element.android.emojibasebindings.EmojibaseCategory +import io.element.android.emojibasebindings.EmojibaseDatasource +import io.element.android.emojibasebindings.EmojibaseStore import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Icon @@ -59,24 +63,23 @@ import kotlinx.coroutines.launch @Composable fun EmojiPicker( onEmojiSelected: (Emoji) -> Unit, + emojibaseStore: EmojibaseStore, selectedEmojis: ImmutableSet, modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() - - val emojiProvider = remember { GoogleEmojiProvider() } - val categories = remember { emojiProvider.categories } - val pagerState = rememberPagerState(pageCount = { emojiProvider.categories.size }) + val categories = remember { emojibaseStore.categories } + val pagerState = rememberPagerState(pageCount = { EmojibaseCategory.values().size }) Column(modifier) { TabRow( selectedTabIndex = pagerState.currentPage, ) { - categories.forEachIndexed { index, category -> + EmojibaseCategory.values().forEachIndexed { index, category -> Tab( text = { Icon( - resourceId = emojiProvider.getIcon(category), - contentDescription = category.categoryNames["en"] + imageVector = category.icon, + contentDescription = stringResource(id = category.title) ) }, selected = pagerState.currentPage == index, @@ -91,14 +94,16 @@ fun EmojiPicker( state = pagerState, modifier = Modifier.fillMaxWidth(), ) { index -> - val category = categories[index] + val category = EmojibaseCategory.values()[index] + val emojis = categories[category] ?: listOf() LazyVerticalGrid( modifier = Modifier.fillMaxSize(), columns = GridCells.Adaptive(minSize = 40.dp), contentPadding = PaddingValues(vertical = 10.dp, horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - items(category.emojis, key = { it.unicode }) { item -> + + items(emojis, key = { it.unicode }) { item -> val backgroundColor = if (selectedEmojis.contains(item.unicode)) { ElementTheme.colors.bgActionPrimaryRest } else { @@ -144,7 +149,8 @@ internal fun EmojiPickerDarkPreview() { private fun ContentToPreview() { EmojiPicker( onEmojiSelected = {}, + emojibaseStore = EmojibaseDatasource().load(LocalContext.current), + selectedEmojis = persistentSetOf("😀", "😄", "😃"), modifier = Modifier.fillMaxWidth(), - selectedEmojis = persistentSetOf("😀", "😄", "😃") ) } 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 new file mode 100644 index 0000000000..fb111cce97 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt @@ -0,0 +1,58 @@ +/* + * 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.customreaction + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.EmojiEvents +import androidx.compose.material.icons.outlined.EmojiFlags +import androidx.compose.material.icons.outlined.EmojiFoodBeverage +import androidx.compose.material.icons.outlined.EmojiNature +import androidx.compose.material.icons.outlined.EmojiObjects +import androidx.compose.material.icons.outlined.EmojiPeople +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 + +@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.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 + } + diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt new file mode 100644 index 0000000000..6a4f48a806 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.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.features.messages.impl.timeline.components.customreaction + +import io.element.android.emojibasebindings.EmojibaseStore + +interface EmojibaseProvider { + val emojibaseStore: EmojibaseStore +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt index 65101183d9..e5851c993b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt @@ -18,10 +18,14 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.designsystem.text.toDp 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.CommonStrings @@ -69,3 +73,10 @@ fun ExtraPadding.getStr(fontSize: TextUnit): String { // A space and some unbreakable spaces return " " + "\u00A0".repeat(nbOfSpaces) } + +@Composable +fun ExtraPadding.getDpSize(): Dp { + if (nbChars == 0) return 0.dp + val timestampFontSize = ElementTheme.typography.fontBodyXsRegular.fontSize // 11.sp + return nbChars * timestampFontSize.toDp() / 3 +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index c6b0218bba..b2dcc477a0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -18,9 +18,6 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -28,7 +25,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.element.android.features.messages.impl.timeline.components.html.HtmlDocument import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent @@ -51,18 +47,14 @@ fun TimelineItemTextView( CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textPrimary) { val htmlDocument = content.htmlDocument if (htmlDocument != null) { - // For now we ignore the extra padding for html content, so add some spacing - // below the content (as previous behavior) - Column(modifier = modifier) { - HtmlDocument( - document = htmlDocument, - modifier = Modifier, - onTextClicked = onTextClicked, - onTextLongClicked = onTextLongClicked, - interactionSource = interactionSource - ) - Spacer(Modifier.height(16.dp)) - } + HtmlDocument( + document = htmlDocument, + extraPadding = extraPadding, + modifier = modifier, + onTextClicked = onTextClicked, + onTextLongClicked = onTextLongClicked, + interactionSource = interactionSource + ) } else { Box(modifier) { val textWithPadding = remember(content.body) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt index 9c2798a638..9df5399727 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt @@ -25,8 +25,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent @@ -53,13 +55,18 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import io.element.android.features.messages.impl.timeline.components.event.ExtraPadding +import io.element.android.features.messages.impl.timeline.components.event.getDpSize +import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.theme.LinkColor import kotlinx.collections.immutable.persistentMapOf import org.jsoup.nodes.Document @@ -72,18 +79,28 @@ private const val CHIP_ID = "chip" @Composable fun HtmlDocument( document: Document, + extraPadding: ExtraPadding, interactionSource: MutableInteractionSource, modifier: Modifier = Modifier, onTextClicked: () -> Unit = {}, onTextLongClicked: () -> Unit = {}, ) { - HtmlBody( - body = document.body(), - interactionSource = interactionSource, + FlowRow( modifier = modifier, - onTextClicked = onTextClicked, - onTextLongClicked = onTextLongClicked, - ) + ) { + HtmlBody( + body = document.body(), + interactionSource = interactionSource, + onTextClicked = onTextClicked, + onTextLongClicked = onTextLongClicked, + ) + Spacer( + modifier = Modifier.size( + width = extraPadding.getDpSize(), + height = ElementTheme.typography.fontBodyXsRegular.fontSize.toDp() * 1.25f + ) + ) + } } @Composable @@ -603,5 +620,9 @@ internal fun HtmlDocumentDarkPreview(@PreviewParameter(DocumentProvider::class) @Composable private fun ContentToPreview(document: Document) { - HtmlDocument(document, remember { MutableInteractionSource() }) + HtmlDocument( + document = document, + extraPadding = noExtraPadding, + interactionSource = remember { MutableInteractionSource() } + ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt index 456ac5f548..e75e49c1e3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt @@ -61,7 +61,7 @@ class ReactionSummaryPresenter @Inject constructor( } return ReactionSummaryState( target = targetWithAvatars.value, - eventSink = ::handleEvents + eventSink = { handleEvents(it) } ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt index 237dc5683d..c9ebd9be8c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt @@ -66,7 +66,7 @@ class RetrySendMenuPresenter @Inject constructor( return RetrySendMenuState( selectedEvent = selectedEvent, - eventSink = ::handleEvent, + eventSink = { handleEvent(it) }, ) } } 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 9da31ee6a7..040969e092 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 @@ -132,10 +132,12 @@ class TimelineItemContentMessageFactory @Inject constructor( } private fun aspectRatioOf(width: Long?, height: Long?): Float? { - return if (height != null && width != null) { + val result = if (height != null && width != null) { width.toFloat() / height.toFloat() } else { null } + + return result?.takeIf { it.isFinite() } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt index 02837bd6b4..ef31d6249c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt @@ -34,6 +34,18 @@ fun TimelineItemEventContent.canBeCopied(): Boolean = else -> false } +/** + * Determine if the event content can be replied to. + * Note: it should match the logic in [io.element.android.features.messages.impl.actionlist.ActionListPresenter]. + */ +fun TimelineItemEventContent.canBeRepliedTo(): Boolean = + when (this) { + is TimelineItemRedactedContent, + is TimelineItemStateContent, + is TimelineItemPollContent -> false + else -> true + } + /** * Return true if user can react (i.e. send a reaction) on the event content. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt index a21f262071..7ad79f19e8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt @@ -18,6 +18,10 @@ package io.element.android.features.messages.impl.timeline.model.event import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.location.api.Location +import io.element.android.features.poll.api.PollAnswerItem +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.poll.PollAnswer +import io.element.android.libraries.matrix.api.poll.PollKind open class TimelineItemLocationContentProvider : PreviewParameterProvider { override val values: Sequence @@ -36,3 +40,32 @@ fun aTimelineItemLocationContent(description: String? = null) = TimelineItemLoca ), description = description, ) + +fun aTimelineItemPollContent( + isEnded: Boolean = false, +) = TimelineItemPollContent( + eventId = EventId("\$anEventId"), + question = "Some question?", + answerItems = listOf( + PollAnswerItem( + answer = PollAnswer("id_1", "Answer1"), + isSelected = false, + isEnabled = false, + isWinner = false, + isDisclosed = false, + votesCount = 0, + percentage = 0.0f, + ), + PollAnswerItem( + answer = PollAnswer("id_2", "Answer2"), + isSelected = false, + isEnabled = false, + isWinner = false, + isDisclosed = false, + votesCount = 0, + percentage = 0.0f, + ), + ), + pollKind = PollKind.Disclosed, + isEnded = isEnded, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt index ec6ee16675..10fca53261 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt @@ -22,4 +22,6 @@ sealed interface TimelineItemTextBasedContent : TimelineItemEventContent { val body: String val htmlDocument: Document? val isEdited: Boolean + val htmlBody: String? + get() = htmlDocument?.body()?.html() } diff --git a/features/messages/impl/src/main/res/values-cs/translations.xml b/features/messages/impl/src/main/res/values-cs/translations.xml index 7ca02ef21c..1543101f6d 100644 --- a/features/messages/impl/src/main/res/values-cs/translations.xml +++ b/features/messages/impl/src/main/res/values-cs/translations.xml @@ -5,11 +5,6 @@ "%1$d změny místnosti" "%1$d změn místnosti" - - "%1$d další" - "%1$d další" - "%1$d dalších" - "Fotoaparát" "Vyfotit" "Natočit video" 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 5f9f0223aa..e88ddc43cb 100644 --- a/features/messages/impl/src/main/res/values-fr/translations.xml +++ b/features/messages/impl/src/main/res/values-fr/translations.xml @@ -4,10 +4,6 @@ "%1$d changement dans la conversation" "%1$d changements dans la conversation" - - "%1$d de plus" - "%1$d de plus" - "Appareil photo" "Prendre une photo" "Enregistrer une vidéo" 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 54774ca172..c351eb29cb 100644 --- a/features/messages/impl/src/main/res/values-ro/translations.xml +++ b/features/messages/impl/src/main/res/values-ro/translations.xml @@ -11,13 +11,33 @@ "Atașament" "Bibliotecă foto și video" "Locație" + "Sondaj" + "Formatarea textului" + "Istoricul mesajelor este momentan indisponibil în această cameră" "Nu am putut găsi detaliile utilizatorului" "Doriți să îi invitați înapoi?" "Sunteți singur în această cameră" "Mesaj copiat" "Nu aveți permisiunea de a posta în această cameră" + "Permiteți setări personalizate" + "Activarea acestei opțiuni va anula setările implicite." + "Anunțați-mă în acestă cameră pentru" + "Îl puteți schimba în %1$s." + "Setări generale" + "Setare implicită" + "Stergeți setarea personalizată" + "A apărut o eroare la încărcarea setărilor pentry notificari." + "Nu s-a reușit restaurarea modului implicit, vă rugăm să încercați din nou." + "Nu s-a reușit setarea modului, vă rugăm să încercați din nou." + "Toate mesajele" + "Numai mențiuni și cuvinte cheie" + "În această cameră, anunțați-mă pentru" + "Afișați mai puțin" + "Afișați mai mult" "Trimiteți din nou" "Mesajul dvs. nu a putut fi trimis" + "Adăugați emoji" + "Afișați mai puțin" "Procesarea datelor media a eșuat, vă rugăm să încercați din nou." "Ștergeți" 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 33147f8ea2..8c96074f1b 100644 --- a/features/messages/impl/src/main/res/values-ru/translations.xml +++ b/features/messages/impl/src/main/res/values-ru/translations.xml @@ -5,11 +5,6 @@ "%1$d изменения в комнате" "%1$d изменений в комнате" - - "И ещё %1$d" - "И ещё %1$d" - "И ещё %1$d" - "Камера" "Сделать фото" "Записать видео" 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 03a32f847f..2390e1e25e 100644 --- a/features/messages/impl/src/main/res/values-sk/translations.xml +++ b/features/messages/impl/src/main/res/values-sk/translations.xml @@ -5,11 +5,6 @@ "%1$d zmeny miestnosti" "%1$d zmien miestnosti" - - "%1$d ďalší" - "%1$d ďalšie" - "%1$d ďalších" - "Kamera" "Odfotiť" "Nahrať video" 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 88f97cd2bf..ac4725896e 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 @@ -3,9 +3,6 @@ "%1$d 個聊天室變更" - - "還有 %1$d 個" - "照相機" "拍照" "錄影" diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 105cb1fc7b..81cd4933e4 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -4,9 +4,6 @@ "%1$d room change" "%1$d room changes" - - "%1$d more" - "Camera" "Take photo" "Record a video" @@ -14,6 +11,7 @@ "Photo & Video Library" "Location" "Poll" + "Text Formatting" "Message history is currently unavailable in this room" "Could not retrieve user details" "Would you like to invite them back?" 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 2c542e0054..2a601fbb90 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 @@ -21,6 +21,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.PollEnd import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.InviteDialogAction @@ -30,7 +31,6 @@ import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction 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.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter @@ -41,6 +41,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.media.FakeLocalMediaFactory +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.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper @@ -50,6 +52,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.SnackbarDispatcher +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -69,16 +72,24 @@ import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.testCoroutineDispatchers +import io.element.android.tests.testutils.waitForPredicate import io.mockk.mockk import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test +import kotlin.time.Duration.Companion.milliseconds class MessagesPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val mockMediaUrl: Uri = mockk("localMediaUri") @Test @@ -315,6 +326,7 @@ class MessagesPresenterTest { initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, aMessageEvent())) assertThat(matrixRoom.redactEventEventIdParam).isEqualTo(AN_EVENT_ID) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) + skipItems(1) // back paginating } } @@ -371,14 +383,14 @@ class MessagesPresenterTest { // Initially the composer doesn't have focus, so we don't show the alert assertThat(initialState.showReinvitePrompt).isFalse() // When the input field is focused we show the alert - initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) - val focusedState = consumeItemsUntilPredicate { state -> + initialState.composerState.richTextEditorState.requestFocus() + val focusedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { state -> state.showReinvitePrompt }.last() assertThat(focusedState.showReinvitePrompt).isTrue() // If it's dismissed then we stop showing the alert initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) - val dismissedState = consumeItemsUntilPredicate { state -> + val dismissedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { state -> !state.showReinvitePrompt }.last() assertThat(dismissedState.showReinvitePrompt).isFalse() @@ -395,7 +407,7 @@ class MessagesPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.showReinvitePrompt).isFalse() - initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) + initialState.composerState.richTextEditorState.requestFocus() val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isFalse() } @@ -411,7 +423,7 @@ class MessagesPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.showReinvitePrompt).isFalse() - initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) + initialState.composerState.richTextEditorState.requestFocus() val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isFalse() } @@ -462,7 +474,9 @@ class MessagesPresenterTest { val initialState = consumeItemsUntilTimeout().last() initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) skipItems(1) - val loadingState = awaitItem() + val loadingState = consumeItemsUntilPredicate { state -> + state.inviteProgress.isLoading() + }.last() assertThat(loadingState.inviteProgress.isLoading()).isTrue() val newState = awaitItem() assertThat(newState.inviteProgress.isSuccess()).isTrue() @@ -559,32 +573,59 @@ class MessagesPresenterTest { } } + @Test + fun `present - handle poll end`() = runTest { + val room = FakeMatrixRoom() + val analyticsService = FakeAnalyticsService() + val presenter = createMessagePresenter( + matrixRoom = room, + analyticsService = analyticsService, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent())) + waitForPredicate { room.endPollInvocations.size == 1 } + cancelAndIgnoreRemainingEvents() + assertThat(room.endPollInvocations.size).isEqualTo(1) + assertThat(room.endPollInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID) + assertThat(room.endPollInvocations.first().text).isEqualTo("The poll with event id: \$anEventId has ended.") + assertThat(analyticsService.capturedEvents.size).isEqualTo(1) + assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollEnd()) + } + } + private fun TestScope.createMessagePresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), matrixRoom: MatrixRoom = FakeMatrixRoom(), navigator: FakeMessagesNavigator = FakeMessagesNavigator(), clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), + analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ): MessagesPresenter { val messageComposerPresenter = MessageComposerPresenter( appCoroutineScope = this, room = matrixRoom, mediaPickerProvider = FakePickerProvider(), - featureFlagService = FakeFeatureFlagService(), + featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.NotificationSettings.key to true)), localMediaFactory = FakeLocalMediaFactory(mockMediaUrl), mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom), snackbarDispatcher = SnackbarDispatcher(), - analyticsService = FakeAnalyticsService(), + analyticsService = analyticsService, messageComposerContext = MessageComposerContextImpl(), + richTextEditorStateFactory = TestRichTextEditorStateFactory(), + ) val timelinePresenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), room = matrixRoom, dispatchers = coroutineDispatchers, - appScope = this + appScope = this, + analyticsService = analyticsService, ) val buildMeta = aBuildMeta() val actionListPresenter = ActionListPresenter(buildMeta = buildMeta) - val customReactionPresenter = CustomReactionPresenter() + val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom) return MessagesPresenter( @@ -600,6 +641,7 @@ class MessagesPresenterTest { messageSummaryFormatter = FakeMessageSummaryFormatter(), navigator = navigator, clipboardHelper = clipboardHelper, + analyticsService = analyticsService, dispatchers = coroutineDispatchers, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index 111b3a370d..5e68906216 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -26,22 +26,25 @@ import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.aTimelineItemEvent -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent +import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent -import io.element.android.features.poll.api.PollAnswerItem -import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.poll.PollAnswer -import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ActionListPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) @@ -61,7 +64,7 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -86,7 +89,7 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -114,7 +117,7 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -135,6 +138,37 @@ class ActionListPresenterTest { } } + @Test + fun `present - compute for others message cannot sent message`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = true) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + isMine = false, + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false) + ) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false)) + // val loadingState = awaitItem() + // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + messageEvent, + persistentListOf( + TimelineItemAction.Forward, + TimelineItemAction.Copy, + TimelineItemAction.Developer, + TimelineItemAction.ReportContent, + ) + ) + ) + initialState.eventSink.invoke(ActionListEvents.Clear) + assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) + } + } + @Test fun `present - compute for others message and can redact`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) @@ -146,7 +180,7 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -177,7 +211,7 @@ class ActionListPresenterTest { isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -210,7 +244,7 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemImageContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -241,7 +275,7 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemStateEventContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -270,7 +304,7 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemStateEventContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -298,7 +332,7 @@ class ActionListPresenterTest { isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -335,10 +369,10 @@ class ActionListPresenterTest { content = TimelineItemRedactedContent, ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true)) awaitItem().run { assertThat(target).isEqualTo(ActionListState.Target.None) assertThat(displayEmojiReactions).isFalse() @@ -359,7 +393,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -384,35 +418,35 @@ class ActionListPresenterTest { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, - content = TimelineItemPollContent( - eventId = EventId("\$anEventId"), - question = "Some question?", - answerItems = listOf( - PollAnswerItem( - answer = PollAnswer("id_1", "Answer1"), - isSelected = false, - isEnabled = false, - isWinner = false, - isDisclosed = false, - votesCount = 0, - percentage = 0.0f, - ), - PollAnswerItem( - answer = PollAnswer("id_2", "Answer2"), - isSelected = false, - isEnabled = false, - isWinner = false, - isDisclosed = false, - votesCount = 0, - percentage = 0.0f, - ), - ), - pollKind = PollKind.Disclosed, - isEnded = false, + content = aTimelineItemPollContent(), + ) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + messageEvent, + persistentListOf( + TimelineItemAction.EndPoll, + TimelineItemAction.Redact, + ) ) ) + assertThat(successState.displayEmojiReactions).isTrue() + } + } - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + @Test + fun `present - compute for ended poll message`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = false) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + isMine = true, + content = aTimelineItemPollContent(isEnded = true), + ) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt index 3608d9e80e..5a66fe13b8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt @@ -34,13 +34,19 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class AttachmentsPreviewPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val mediaPreProcessor = FakeMediaPreProcessor() private val mockMediaUrl: Uri = mockk("localMediaUri") diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt index 983b251df7..7b6f48403f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt @@ -30,13 +30,20 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ForwardMessagesPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + + @Test fun `present - initial state`() = runTest { val presenter = aPresenter() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt index 3c31fa49d3..97ee81bcb6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt @@ -33,15 +33,22 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.matrix.test.media.FakeMediaLoader import io.element.android.libraries.matrix.test.media.aMediaSource +import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test private val TESTED_MEDIA_INFO = aFileInfo() class MediaViewerPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + + private val mockMediaUri: Uri = mockk("localMediaUri") private val localMediaFactory = FakeLocalMediaFactory(mockMediaUri) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/report/ReportMessagePresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/report/ReportMessagePresenterTests.kt index e6b0dee0c9..97b0227def 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/report/ReportMessagePresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/report/ReportMessagePresenterTests.kt @@ -28,11 +28,17 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ReportMessagePresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `presenter - initial state`() = runTest { val presenter = aPresenter() 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 799db7f274..d1d4a54073 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 @@ -53,17 +53,24 @@ import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.libraries.textcomposer.Message import io.element.android.libraries.textcomposer.MessageComposerMode import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test import java.io.File class MessageComposerPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val pickerProvider = FakePickerProvider().apply { givenResult(mockk()) // Uri is not available in JVM, so the only way to have a non-null Uri is using Mockk } @@ -74,6 +81,7 @@ class MessageComposerPresenterTest { private val snackbarDispatcher = SnackbarDispatcher() private val mockMediaUrl: Uri = mockk("localMediaUri") private val localMediaFactory = FakeLocalMediaFactory(mockMediaUrl) + private val analyticsService = FakeAnalyticsService() @Test fun `present - initial state`() = runTest { @@ -84,12 +92,12 @@ class MessageComposerPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.isFullScreen).isFalse() - assertThat(initialState.text).isEqualTo("") + assertThat(initialState.richTextEditorState.messageHtml).isEqualTo("") assertThat(initialState.mode).isEqualTo(MessageComposerMode.Normal("")) assertThat(initialState.showAttachmentSourcePicker).isFalse() assertThat(initialState.canShareLocation).isTrue() assertThat(initialState.attachmentsState).isEqualTo(AttachmentsState.None) - assertThat(initialState.isSendButtonVisible).isFalse() + assertThat(initialState.canSendMessage).isFalse() } } @@ -118,14 +126,14 @@ class MessageComposerPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE)) + initialState.richTextEditorState.setHtml(A_MESSAGE) val withMessageState = awaitItem() - assertThat(withMessageState.text).isEqualTo(A_MESSAGE) - assertThat(withMessageState.isSendButtonVisible).isTrue() - withMessageState.eventSink.invoke(MessageComposerEvents.UpdateText("")) + assertThat(withMessageState.richTextEditorState.messageHtml).isEqualTo(A_MESSAGE) + assertThat(withMessageState.canSendMessage).isTrue() + withMessageState.richTextEditorState.setHtml("") val withEmptyMessageState = awaitItem() - assertThat(withEmptyMessageState.text).isEqualTo("") - assertThat(withEmptyMessageState.isSendButtonVisible).isFalse() + assertThat(withEmptyMessageState.richTextEditorState.messageHtml).isEqualTo("") + assertThat(withEmptyMessageState.canSendMessage).isFalse() } } @@ -142,8 +150,8 @@ class MessageComposerPresenterTest { state = awaitItem() assertThat(state.mode).isEqualTo(mode) state = awaitItem() - assertThat(state.text).isEqualTo(A_MESSAGE) - assertThat(state.isSendButtonVisible).isTrue() + assertThat(state.richTextEditorState.messageHtml).isEqualTo(A_MESSAGE) + assertThat(state.canSendMessage).isTrue() backToNormalMode(state, skipCount = 1) } } @@ -160,8 +168,8 @@ class MessageComposerPresenterTest { state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) - assertThat(state.text).isEqualTo("") - assertThat(state.isSendButtonVisible).isFalse() + assertThat(state.richTextEditorState.messageHtml).isEqualTo("") + assertThat(state.canSendMessage).isFalse() backToNormalMode(state) } } @@ -178,8 +186,8 @@ class MessageComposerPresenterTest { state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) - assertThat(state.text).isEqualTo("") - assertThat(state.isSendButtonVisible).isFalse() + assertThat(state.richTextEditorState.messageHtml).isEqualTo("") + assertThat(state.canSendMessage).isFalse() backToNormalMode(state) } } @@ -192,14 +200,14 @@ class MessageComposerPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE)) + initialState.richTextEditorState.setHtml(A_MESSAGE) val withMessageState = awaitItem() - assertThat(withMessageState.text).isEqualTo(A_MESSAGE) - assertThat(withMessageState.isSendButtonVisible).isTrue() - withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(A_MESSAGE)) + assertThat(withMessageState.richTextEditorState.messageHtml).isEqualTo(A_MESSAGE) + assertThat(withMessageState.canSendMessage).isTrue() + withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage())) val messageSentState = awaitItem() - assertThat(messageSentState.text).isEqualTo("") - assertThat(messageSentState.isSendButtonVisible).isFalse() + assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("") + assertThat(messageSentState.canSendMessage).isFalse() } } @@ -215,23 +223,23 @@ class MessageComposerPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.text).isEqualTo("") + assertThat(initialState.richTextEditorState.messageHtml).isEqualTo("") val mode = anEditMode() initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) skipItems(1) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) - assertThat(withMessageState.text).isEqualTo(A_MESSAGE) - assertThat(withMessageState.isSendButtonVisible).isTrue() - withMessageState.eventSink.invoke(MessageComposerEvents.UpdateText(ANOTHER_MESSAGE)) + assertThat(withMessageState.richTextEditorState.messageHtml).isEqualTo(A_MESSAGE) + assertThat(withMessageState.canSendMessage).isTrue() + withMessageState.richTextEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() - assertThat(withEditedMessageState.text).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(ANOTHER_MESSAGE)) + assertThat(withEditedMessageState.richTextEditorState.messageHtml).isEqualTo(ANOTHER_MESSAGE) + withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(ANOTHER_MESSAGE.toMessage())) skipItems(1) val messageSentState = awaitItem() - assertThat(messageSentState.text).isEqualTo("") - assertThat(messageSentState.isSendButtonVisible).isFalse() - assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE) + assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("") + assertThat(messageSentState.canSendMessage).isFalse() + assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE) } } @@ -247,23 +255,23 @@ class MessageComposerPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.text).isEqualTo("") + assertThat(initialState.richTextEditorState.messageHtml).isEqualTo("") val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID) initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) skipItems(1) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) - assertThat(withMessageState.text).isEqualTo(A_MESSAGE) - assertThat(withMessageState.isSendButtonVisible).isTrue() - withMessageState.eventSink.invoke(MessageComposerEvents.UpdateText(ANOTHER_MESSAGE)) + assertThat(withMessageState.richTextEditorState.messageHtml).isEqualTo(A_MESSAGE) + assertThat(withMessageState.canSendMessage).isTrue() + withMessageState.richTextEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() - assertThat(withEditedMessageState.text).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(ANOTHER_MESSAGE)) + assertThat(withEditedMessageState.richTextEditorState.messageHtml).isEqualTo(ANOTHER_MESSAGE) + withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(ANOTHER_MESSAGE.toMessage())) skipItems(1) val messageSentState = awaitItem() - assertThat(messageSentState.text).isEqualTo("") - assertThat(messageSentState.isSendButtonVisible).isFalse() - assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE) + assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("") + assertThat(messageSentState.canSendMessage).isFalse() + assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE) } } @@ -279,23 +287,23 @@ class MessageComposerPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.text).isEqualTo("") + assertThat(initialState.richTextEditorState.messageHtml).isEqualTo("") val mode = aReplyMode() initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) val state = awaitItem() assertThat(state.mode).isEqualTo(mode) - assertThat(state.text).isEqualTo("") - assertThat(state.isSendButtonVisible).isFalse() - initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_REPLY)) + assertThat(state.richTextEditorState.messageHtml).isEqualTo("") + assertThat(state.canSendMessage).isFalse() + state.richTextEditorState.setHtml(A_REPLY) val withMessageState = awaitItem() - assertThat(withMessageState.text).isEqualTo(A_REPLY) - assertThat(withMessageState.isSendButtonVisible).isTrue() - withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(A_REPLY)) + assertThat(withMessageState.richTextEditorState.messageHtml).isEqualTo(A_REPLY) + assertThat(withMessageState.canSendMessage).isTrue() + withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(A_REPLY.toMessage())) skipItems(1) val messageSentState = awaitItem() - assertThat(messageSentState.text).isEqualTo("") - assertThat(messageSentState.isSendButtonVisible).isFalse() - assertThat(fakeMatrixRoom.replyMessageParameter).isEqualTo(A_REPLY) + assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("") + assertThat(messageSentState.canSendMessage).isFalse() + assertThat(fakeMatrixRoom.replyMessageParameter).isEqualTo(A_REPLY to A_REPLY) } } @@ -517,13 +525,50 @@ class MessageComposerPresenterTest { } } + @Test + fun `present - errors are tracked`() = runTest { + val testException = Exception("Test error") + val presenter = createPresenter(this) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + initialState.eventSink(MessageComposerEvents.Error(testException)) + assertThat(analyticsService.trackedErrors).containsExactly(testException) + } + } + + @Test + fun `present - ToggleTextFormatting toggles text formatting`() = runTest { + val presenter = createPresenter(this) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.showTextFormatting).isFalse() + initialState.eventSink(MessageComposerEvents.AddAttachment) + val composerOptions = awaitItem() + assertThat(composerOptions.showAttachmentSourcePicker).isTrue() + composerOptions.eventSink(MessageComposerEvents.ToggleTextFormatting(true)) + awaitItem() // composer options closed + val showTextFormatting = awaitItem() + assertThat(showTextFormatting.showAttachmentSourcePicker).isFalse() + assertThat(showTextFormatting.showTextFormatting).isTrue() + showTextFormatting.eventSink(MessageComposerEvents.ToggleTextFormatting(false)) + val finished = awaitItem() + assertThat(finished.showTextFormatting).isFalse() + } + } + private suspend fun ReceiveTurbine.backToNormalMode(state: MessageComposerState, skipCount: Int = 0) { state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode) skipItems(skipCount) val normalState = awaitItem() assertThat(normalState.mode).isEqualTo(MessageComposerMode.Normal("")) - assertThat(normalState.text).isEqualTo("") - assertThat(normalState.isSendButtonVisible).isFalse() + assertThat(normalState.richTextEditorState.messageHtml).isEqualTo("") + assertThat(normalState.canSendMessage).isFalse() } private fun createPresenter( @@ -541,8 +586,9 @@ class MessageComposerPresenterTest { localMediaFactory, MediaSender(mediaPreProcessor, room), snackbarDispatcher, - FakeAnalyticsService(), + analyticsService, MessageComposerContextImpl(), + TestRichTextEditorStateFactory(), ) } @@ -554,3 +600,8 @@ fun anEditMode( fun aReplyMode() = MessageComposerMode.Reply(A_USER_NAME, null, AN_EVENT_ID, A_MESSAGE) fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE) + +private fun String.toMessage() = Message( + html = this, + markdown = this, +) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/TestRichTextEditorStateFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/TestRichTextEditorStateFactory.kt new file mode 100644 index 0000000000..762d144cd6 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/TestRichTextEditorStateFactory.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.textcomposer + +import androidx.compose.runtime.Composable +import io.element.android.features.messages.impl.messagecomposer.RichTextEditorStateFactory +import io.element.android.wysiwyg.compose.RichTextEditorState +import io.element.android.wysiwyg.compose.rememberRichTextEditorState + +class TestRichTextEditorStateFactory : RichTextEditorStateFactory { + @Composable + override fun create(): RichTextEditorState { + return rememberRichTextEditorState("", fake = true) + } +} 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 860f8def37..eb5a7b72e1 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 @@ -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 im.vector.app.features.analytics.plan.PollVote import io.element.android.features.messages.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter @@ -37,15 +38,23 @@ import io.element.android.libraries.matrix.test.room.aMessageContent import io.element.android.libraries.matrix.test.room.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.libraries.matrix.ui.components.aMatrixUserList +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitWithLatch import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test import java.util.Date class TimelinePresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = createTimelinePresenter() @@ -253,7 +262,11 @@ class TimelinePresenterTest { @Test fun `present - PollAnswerSelected event calls into rust room api and analytics`() = runTest { val room = FakeMatrixRoom() - val presenter = createTimelinePresenter(room) + val analyticsService = FakeAnalyticsService() + val presenter = createTimelinePresenter( + room = room, + analyticsService = analyticsService, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -264,7 +277,8 @@ class TimelinePresenterTest { assertThat(room.sendPollResponseInvocations.size).isEqualTo(1) assertThat(room.sendPollResponseInvocations.first().answers).isEqualTo(listOf("anAnswerId")) assertThat(room.sendPollResponseInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID) - // TODO Polls: Test poll vote analytic + assertThat(analyticsService.capturedEvents.size).isEqualTo(1) + assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollVote()) } private fun TestScope.createTimelinePresenter( @@ -275,18 +289,21 @@ class TimelinePresenterTest { timelineItemsFactory = timelineItemsFactory, room = FakeMatrixRoom(matrixTimeline = timeline), dispatchers = testCoroutineDispatchers(), - appScope = this + appScope = this, + analyticsService = FakeAnalyticsService(), ) } private fun TestScope.createTimelinePresenter( room: MatrixRoom, + analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ): TimelinePresenter { return TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), room = room, dispatchers = testCoroutineDispatchers(), - appScope = this + appScope = this, + analyticsService = analyticsService, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt index 84628cedae..9f9b7358d8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt @@ -24,27 +24,40 @@ 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.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter +import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class CustomReactionPresenterTests { - private val presenter = CustomReactionPresenter() + @Rule + @JvmField + val warmUpRule = WarmUpRule() + + private val presenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) @Test fun `present - handle selecting and de-selecting an event`() = runTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + + val event = aTimelineItemEvent(eventId = AN_EVENT_ID) val initialState = awaitItem() - assertThat(initialState.selectedEventId).isNull() + assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) - initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID))) - assertThat(awaitItem().selectedEventId).isEqualTo(AN_EVENT_ID) + initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) - initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) - assertThat(awaitItem().selectedEventId).isNull() + assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) + + val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId + assertThat(eventId).isEqualTo(AN_EVENT_ID) + + initialState.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.None) } } @@ -53,13 +66,19 @@ class CustomReactionPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitItem() - assertThat(initialState.selectedEventId).isNull() val reactions = aTimelineItemReactions(count = 1, isHighlighted = true) + val event = aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions) + val initialState = awaitItem() + assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) + val key = reactions.reactions.first().key - initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions))) + initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) + + assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) + val stateWithSelectedEmojis = awaitItem() - assertThat(stateWithSelectedEmojis.selectedEventId).isEqualTo(AN_EVENT_ID) + val eventId = (stateWithSelectedEmojis.target as? CustomReactionState.Target.Success)?.event?.eventId + assertThat(eventId).isEqualTo(AN_EVENT_ID) assertThat(stateWithSelectedEmojis.selectedEmoji).contains(key) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt new file mode 100644 index 0000000000..7bf993e2a4 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.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.messages.timeline.components.customreaction + +import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.features.messages.impl.timeline.components.customreaction.EmojibaseProvider + +class FakeEmojibaseProvider: EmojibaseProvider { + override val emojibaseStore: EmojibaseStore + get() = EmojibaseStore(mapOf()) +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt index 0170878cb5..c2a415ea44 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt @@ -30,10 +30,17 @@ 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.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class ReactionSummaryPresenterTests { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val aggregatedReaction = anAggregatedReaction(userId = A_USER_ID, key = "👍", isHighlighted = true) private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME) private val summaryEvent = ReactionSummaryEvents.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt index 4f4f0a0ee4..5a23eba6da 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt @@ -25,11 +25,17 @@ import io.element.android.features.messages.impl.timeline.components.retrysendme import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.libraries.matrix.test.A_TRANSACTION_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class RetrySendMenuPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private val room = FakeMatrixRoom() private val presenter = RetrySendMenuPresenter(room) diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt index 855bf067dd..7c03be6a41 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt @@ -18,6 +18,9 @@ package io.element.android.features.networkmonitor.api.ui import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.spring import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -27,34 +30,44 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width -import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.WifiOff import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.text.toDp +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings +/** + * A view that displays a connectivity indicator when the device is offline, adding a default + * padding to make sure the status bar is not overlapped. + */ @Composable fun ConnectivityIndicatorView( isOnline: Boolean, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline } val isStatusBarPaddingVisible = remember { MutableTransitionState(isOnline) }.apply { targetState = isOnline } @@ -78,6 +91,46 @@ fun ConnectivityIndicatorView( } } +/** + * A view that displays a connectivity indicator when the device is offline, passing the padding + * needed to make sure the status bar is not overlapped to its content views. + */ +@Composable +fun ConnectivityIndicatorContainer( + isOnline: Boolean, + modifier: Modifier = Modifier, + content: @Composable (topPadding: Dp) -> Unit, +) { + val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline } + + val statusBarTopPadding = if (LocalInspectionMode.current) { + // Needed to get valid UI previews + 24.dp + } else { + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 6.dp + } + val target = remember(isOnline) { if (isOnline) 0.dp else statusBarTopPadding } + val animationStateOffset by animateDpAsState( + targetValue = target, + animationSpec = spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = 1.dp, + ), + label = "insets-animation", + ) + + content(animationStateOffset) + + // Display the network indicator with an animation + AnimatedVisibility( + visibleState = isIndicatorVisible, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + ) { + Indicator(modifier) + } +} + @Composable private fun Indicator(modifier: Modifier = Modifier) { Row( diff --git a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt index 7be45ce236..d183b05386 100644 --- a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt +++ b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt @@ -33,5 +33,6 @@ interface OnBoardingEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onSignUp() fun onSignIn() + fun onOpenDeveloperSettings() } } diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index 0f97a0ffeb..9994eacf81 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -48,4 +48,5 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt index d86623cae2..21322657c1 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt @@ -46,6 +46,10 @@ class OnBoardingNode @AssistedInject constructor( plugins().forEach { it.onSignUp() } } + private fun onOpenDeveloperSettings() { + plugins().forEach { it.onOpenDeveloperSettings() } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -54,6 +58,8 @@ class OnBoardingNode @AssistedInject constructor( modifier = modifier, onSignIn = ::onSignIn, onCreateAccount = ::onSignUp, + onSignInWithQrCode = { /* Not supported yet */ }, + onOpenDeveloperSettings = ::onOpenDeveloperSettings, ) } } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt index 48a360e6c9..b26752fdbe 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt @@ -18,6 +18,8 @@ package io.element.android.features.onboarding.impl import androidx.compose.runtime.Composable import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.meta.BuildType import javax.inject.Inject /** @@ -25,10 +27,12 @@ import javax.inject.Inject * When this presenter get more code in it, please remove the ignore rule in the kover configuration. */ class OnBoardingPresenter @Inject constructor( + private val buildMeta: BuildMeta, ) : Presenter { @Composable override fun present(): OnBoardingState { return OnBoardingState( + isDebugBuild = buildMeta.buildType != BuildType.RELEASE, canLoginWithQrCode = OnBoardingConfig.canLoginWithQrCode, canCreateAccount = OnBoardingConfig.canCreateAccount, ) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt index 88215c0c1e..5bd7718033 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingState.kt @@ -17,6 +17,7 @@ package io.element.android.features.onboarding.impl data class OnBoardingState( + val isDebugBuild: Boolean, val canLoginWithQrCode: Boolean, val canCreateAccount: Boolean, ) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt index 1c60a56018..926d2a2303 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingStateProvider.kt @@ -25,13 +25,16 @@ open class OnBoardingStateProvider : PreviewParameterProvider { anOnBoardingState(canLoginWithQrCode = true), anOnBoardingState(canCreateAccount = true), anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true), + anOnBoardingState(isDebugBuild = true), ) } fun anOnBoardingState( + isDebugBuild: Boolean = false, canLoginWithQrCode: Boolean = false, canCreateAccount: Boolean = false ) = OnBoardingState( + isDebugBuild = isDebugBuild, canLoginWithQrCode = canLoginWithQrCode, canCreateAccount = canCreateAccount ) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt index 1adfe6bd93..424c24839a 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt @@ -25,7 +25,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.QrCode +import androidx.compose.material.icons.filled.Settings import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier @@ -41,6 +43,8 @@ import io.element.android.libraries.designsystem.atomic.pages.OnBoardingPage import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.Button +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.OutlinedButton import io.element.android.libraries.designsystem.theme.components.Text @@ -57,15 +61,19 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun OnBoardingView( state: OnBoardingState, + onSignInWithQrCode: () -> Unit, + onSignIn: () -> Unit, + onCreateAccount: () -> Unit, + onOpenDeveloperSettings: () -> Unit, modifier: Modifier = Modifier, - onSignInWithQrCode: () -> Unit = {}, - onSignIn: () -> Unit = {}, - onCreateAccount: () -> Unit = {}, ) { OnBoardingPage( modifier = modifier, content = { - OnBoardingContent() + OnBoardingContent( + state = state, + onOpenDeveloperSettings = onOpenDeveloperSettings + ) }, footer = { OnBoardingButtons( @@ -79,7 +87,11 @@ fun OnBoardingView( } @Composable -private fun OnBoardingContent(modifier: Modifier = Modifier) { +private fun OnBoardingContent( + state: OnBoardingState, + onOpenDeveloperSettings: () -> Unit, + modifier: Modifier = Modifier +) { Box( modifier = modifier.fillMaxSize(), ) { @@ -122,6 +134,17 @@ private fun OnBoardingContent(modifier: Modifier = Modifier) { ) } } + if (state.isDebugBuild) { + IconButton( + modifier = Modifier.align(Alignment.TopEnd), + onClick = onOpenDeveloperSettings, + ) { + Icon( + imageVector = Icons.Filled.Settings, + contentDescription = stringResource(CommonStrings.common_settings) + ) + } + } } } @@ -172,5 +195,11 @@ private fun OnBoardingButtons( internal fun OnBoardingScreenPreview( @PreviewParameter(OnBoardingStateProvider::class) state: OnBoardingState ) = ElementPreview { - OnBoardingView(state) + OnBoardingView( + state = state, + onSignInWithQrCode = {}, + onSignIn = {}, + onCreateAccount = {}, + onOpenDeveloperSettings = {} + ) } diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt index d336e5b466..538fba98d5 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -20,19 +20,40 @@ 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.core.meta.BuildType +import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class OnBoardingPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { - val presenter = OnBoardingPresenter() + val presenter = OnBoardingPresenter(aBuildMeta()) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() + assertThat(initialState.isDebugBuild).isTrue() assertThat(initialState.canLoginWithQrCode).isFalse() assertThat(initialState.canCreateAccount).isFalse() } } + + @Test + fun `present - initial state release`() = runTest { + val presenter = OnBoardingPresenter(aBuildMeta(buildType = BuildType.RELEASE)) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isDebugBuild).isFalse() + } + } } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index 438c14456c..7e03d7a46e 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -24,13 +24,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectableGroup -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Poll 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.libraries.designsystem.VectorIcons import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.Icon @@ -62,7 +61,7 @@ fun PollContentView( .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - PollTitle(title = question) + PollTitle(title = question, isPollEnded = isPollEnded) PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected) @@ -76,17 +75,26 @@ fun PollContentView( @Composable internal fun PollTitle( title: String, + isPollEnded: Boolean, modifier: Modifier = Modifier ) { Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - Icon( - modifier = Modifier.size(22.dp), - imageVector = Icons.Outlined.Poll, - contentDescription = null - ) + if (isPollEnded) { + Icon( + resourceId = VectorIcons.PollEnd, + contentDescription = null, + modifier = Modifier.size(22.dp) + ) + } else { + Icon( + resourceId = VectorIcons.Poll, + contentDescription = null, + modifier = Modifier.size(22.dp) + ) + } Text( text = title, style = ElementTheme.typography.fontBodyLgMedium @@ -170,7 +178,7 @@ internal fun PollContentEndedPreview() = ElementPreview { question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(isEnded = true), pollKind = PollKind.Disclosed, - isPollEnded = false, + isPollEnded = true, onAnswerSelected = { _, _ -> }, ) } diff --git a/features/poll/impl/build.gradle.kts b/features/poll/impl/build.gradle.kts index 4e8d36966a..5ff9025aae 100644 --- a/features/poll/impl/build.gradle.kts +++ b/features/poll/impl/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.services.analytics.api) + implementation(projects.features.messages.api) implementation(projects.libraries.uiStrings) testImplementation(libs.test.junit) @@ -48,6 +49,8 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) + testImplementation(projects.features.messages.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt index 387f57a597..506c39c177 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt @@ -24,15 +24,17 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.RoomScope +import io.element.android.services.analytics.api.AnalyticsService @ContributesNode(RoomScope::class) class CreatePollNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, presenterFactory: CreatePollPresenter.Factory, - // analyticsService: AnalyticsService, // TODO Polls: add analytics + analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { private val presenter = presenterFactory.create(backNavigator = ::navigateUp) @@ -40,8 +42,7 @@ class CreatePollNode @AssistedInject constructor( init { lifecycle.subscribe( onResume = { - // TODO Polls: add analytics - // analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.PollView)) + analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.CreatePollView)) } ) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt index b44afae9d1..44cc54a100 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt @@ -29,9 +29,13 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.features.analytics.plan.Composer +import im.vector.app.features.analytics.plan.PollCreation +import io.element.android.features.messages.api.MessageComposerContext import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch @@ -44,9 +48,9 @@ private const val MAX_SELECTIONS = 1 class CreatePollPresenter @AssistedInject constructor( private val room: MatrixRoom, - // private val analyticsService: AnalyticsService, // TODO Polls: add analytics + private val analyticsService: AnalyticsService, + private val messageComposerContext: MessageComposerContext, @Assisted private val navigateUp: () -> Unit, - // private val messageComposerContext: MessageComposerContext, // TODO Polls: add analytics ) : Presenter { @AssistedFactory @@ -78,7 +82,21 @@ class CreatePollPresenter @AssistedInject constructor( maxSelections = MAX_SELECTIONS, pollKind = pollKind, ) - // analyticsService.capture(PollCreate()) // TODO Polls: add analytics + analyticsService.capture( + Composer( + inThread = messageComposerContext.composerMode.inThread, + isEditing = messageComposerContext.composerMode.isEditing, + isReply = messageComposerContext.composerMode.isReply, + messageType = Composer.MessageType.Poll, + ) + ) + analyticsService.capture( + PollCreation( + action = PollCreation.Action.Create, + isUndisclosed = pollKind == PollKind.Undisclosed, + numberOfAnswers = answers.size, + ) + ) navigateUp() } else { Timber.d("Cannot create poll") @@ -153,7 +171,7 @@ private val pollKindSaver: Saver, Boolean> = Saver( }, restore = { mutableStateOf( - when(it) { + when (it) { true -> PollKind.Undisclosed else -> PollKind.Disclosed } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt index 3e3ec4eb16..8e3de07574 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt @@ -25,12 +25,19 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.poll.impl.R @@ -64,10 +71,14 @@ fun CreatePollView( val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) } BackHandler(onBack = navBack) if (state.showConfirmation) ConfirmationDialog( - content = stringResource(id = R.string.screen_create_poll_confirmation), + content = stringResource(id = R.string.screen_create_poll_discard_confirmation), onSubmitClicked = { state.eventSink(CreatePollEvents.NavBack) }, onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } ) + val questionFocusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + questionFocusRequester.requestFocus() + } Scaffold( modifier = modifier, topBar = { @@ -113,10 +124,13 @@ fun CreatePollView( onValueChange = { state.eventSink(CreatePollEvents.SetQuestion(it)) }, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .focusRequester(questionFocusRequester) + .fillMaxWidth(), placeholder = { Text(text = stringResource(id = R.string.screen_create_poll_question_hint)) }, + keyboardOptions = keyboardOptions, ) } ) @@ -133,6 +147,7 @@ fun CreatePollView( placeholder = { Text(text = stringResource(id = R.string.screen_create_poll_answer_hint, index + 1)) }, + keyboardOptions = keyboardOptions, ) }, trailingContent = ListItemContent.Custom { @@ -185,3 +200,8 @@ internal fun CreatePollViewPreview( state = state, ) } + +private val keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences, + imeAction = ImeAction.Next, +) diff --git a/features/poll/impl/src/main/res/values-cs/translations.xml b/features/poll/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..d199df993f --- /dev/null +++ b/features/poll/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,10 @@ + + + "Přidat volbu" + "Zobrazit výsledky až po skončení hlasování" + "Anonymní hlasování" + "Volba %1$d" + "Otázka nebo téma" + "Čeho se hlasování týká?" + "Vytvořit hlasování" + diff --git a/features/poll/impl/src/main/res/values-ro/translations.xml b/features/poll/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..b552a68024 --- /dev/null +++ b/features/poll/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,12 @@ + + + "Adăugați o opțiune" + "Afișați rezultatele numai după încheierea sondajului" + "Sondaj anonim" + "Opțiune %1$d" + "Sunteți sigur că doriți să renunțați la acest sondaj?" + "Renunțați la sondaj" + "Întrebare sau subiect" + "Despre ce este sondajul?" + "Creați un sondaj" + diff --git a/features/poll/impl/src/main/res/values-ru/translations.xml b/features/poll/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..f1a518b0e7 --- /dev/null +++ b/features/poll/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,10 @@ + + + "Добавить опцию" + "Показывать результаты только после окончания опроса" + "Анонимный опрос" + "Настройка %1$d" + "Вопрос или тема" + "Тема опроса?" + "Создать опрос" + diff --git a/features/poll/impl/src/main/res/values/localazy.xml b/features/poll/impl/src/main/res/values/localazy.xml index 846b247108..4c80a4ce45 100644 --- a/features/poll/impl/src/main/res/values/localazy.xml +++ b/features/poll/impl/src/main/res/values/localazy.xml @@ -4,7 +4,8 @@ "Show results only after poll ends" "Anonymous Poll" "Option %1$d" - "Are you sure you would like to go back?" + "Are you sure you want to discard this poll?" + "Discard Poll" "Question or topic" "What is the poll about?" "Create Poll" diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index 9dacc6062d..a58fb5476b 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -20,22 +20,34 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth +import im.vector.app.features.analytics.plan.Composer +import im.vector.app.features.analytics.plan.PollCreation +import io.element.android.features.messages.test.MessageComposerContextFake import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.test.room.CreatePollInvocation import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class CreatePollPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private var navUpInvocationsCount = 0 private val fakeMatrixRoom = FakeMatrixRoom() - // private val fakeAnalyticsService = FakeAnalyticsService() // TODO Polls: add analytics + private val fakeAnalyticsService = FakeAnalyticsService() + private val messageComposerContextFake = MessageComposerContextFake() private val presenter = CreatePollPresenter( room = fakeMatrixRoom, - // analyticsService = fakeAnalyticsService, // TODO Polls: add analytics + analyticsService = fakeAnalyticsService, + messageComposerContext = messageComposerContextFake, navigateUp = { navUpInvocationsCount++ }, ) @@ -98,6 +110,22 @@ class CreatePollPresenterTest { pollKind = PollKind.Disclosed ) ) + Truth.assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2) + Truth.assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo( + Composer( + inThread = false, + isEditing = false, + isReply = false, + messageType = Composer.MessageType.Poll, + ) + ) + Truth.assertThat(fakeAnalyticsService.capturedEvents[1]).isEqualTo( + PollCreation( + action = PollCreation.Action.Create, + isUndisclosed = false, + numberOfAnswers = 2, + ) + ) } } diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/ConfigureTracingEntryPoint.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/ConfigureTracingEntryPoint.kt new file mode 100644 index 0000000000..803c0c9232 --- /dev/null +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/ConfigureTracingEntryPoint.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.features.preferences.api + +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint + +interface ConfigureTracingEntryPoint : SimpleFeatureEntryPoint diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultConfigureTracingEntryPoint.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultConfigureTracingEntryPoint.kt new file mode 100644 index 0000000000..372acbd1f4 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultConfigureTracingEntryPoint.kt @@ -0,0 +1,33 @@ +/* + * 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.preferences.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.preferences.api.ConfigureTracingEntryPoint +import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultConfigureTracingEntryPoint @Inject constructor() : ConfigureTracingEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 0998318abf..4f38a26d93 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -35,6 +35,7 @@ import io.element.android.features.preferences.impl.analytics.AnalyticsSettingsN import io.element.android.features.preferences.impl.developer.DeveloperSettingsNode import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode +import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.root.PreferencesRootNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -63,6 +64,9 @@ class PreferencesFlowNode @AssistedInject constructor( @Parcelize data object DeveloperSettings : NavTarget + @Parcelize + data object ConfigureTracing : NavTarget + @Parcelize data object AnalyticsSettings : NavTarget @@ -108,7 +112,15 @@ class PreferencesFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(callback)) } NavTarget.DeveloperSettings -> { - createNode(buildContext) + val callback = object : DeveloperSettingsNode.Callback { + override fun openConfigureTracing() { + backstack.push(NavTarget.ConfigureTracing) + } + } + createNode(buildContext, listOf(callback)) + } + NavTarget.ConfigureTracing -> { + createNode(buildContext) } NavTarget.About -> { createNode(buildContext) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt index d5af758dec..96339e7bb4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt @@ -24,6 +24,7 @@ import com.airbnb.android.showkase.models.Showkase import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -37,6 +38,14 @@ class DeveloperSettingsNode @AssistedInject constructor( private val presenter: DeveloperSettingsPresenter, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun openConfigureTracing() + } + + private fun onOpenConfigureTracing() { + plugins().forEach { it.openConfigureTracing() } + } + @Composable override fun View(modifier: Modifier) { val activity = LocalContext.current as Activity @@ -50,6 +59,7 @@ class DeveloperSettingsNode @AssistedInject constructor( state = state, modifier = modifier, onOpenShowkase = ::openShowkase, + onOpenConfigureTracing = ::onOpenConfigureTracing, onBackPressed = ::navigateUp ) } 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 010e17bd35..ebcd44b4db 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 @@ -83,7 +83,8 @@ class DeveloperSettingsPresenter @Inject constructor( features, enabledFeatures, event.feature, - event.isEnabled + event.isEnabled, + triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) } ) DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) } @@ -122,7 +123,8 @@ class DeveloperSettingsPresenter @Inject constructor( features: SnapshotStateMap, enabledFeatures: SnapshotStateMap, featureUiModel: FeatureUiModel, - enabled: Boolean + enabled: Boolean, + @Suppress("UNUSED_PARAMETER") triggerClearCache: () -> Unit, ) = launch { val feature = features[featureUiModel.key] ?: return@launch if (featureFlagService.setFeatureEnabled(feature, enabled)) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index 23ea4faf86..684587220b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun DeveloperSettingsView( state: DeveloperSettingsState, onOpenShowkase: () -> Unit, + onOpenConfigureTracing: () -> Unit, onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { @@ -47,6 +48,12 @@ fun DeveloperSettingsView( PreferenceCategory(title = "Feature flags") { FeatureListContent(state) } + PreferenceCategory(title = "Rust SDK") { + PreferenceText( + title = "Configure tracing", + onClick = onOpenConfigureTracing, + ) + } PreferenceCategory(title = "Showkase") { PreferenceText( title = "Open Showkase browser", @@ -109,6 +116,7 @@ private fun ContentToPreview(state: DeveloperSettingsState) { DeveloperSettingsView( state = state, onOpenShowkase = {}, + onOpenConfigureTracing = {}, onBackPressed = {} ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingEvents.kt new file mode 100644 index 0000000000..2d73ea0726 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingEvents.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.preferences.impl.developer.tracing + +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target + +sealed interface ConfigureTracingEvents { + data class UpdateFilter(val target: Target, val logLevel: LogLevel) : ConfigureTracingEvents + data object ResetFilters : ConfigureTracingEvents +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingNode.kt new file mode 100644 index 0000000000..7c58058798 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingNode.kt @@ -0,0 +1,45 @@ +/* + * 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.preferences.impl.developer.tracing + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class ConfigureTracingNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: ConfigureTracingPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ConfigureTracingView( + state = state, + onBackPressed = ::navigateUp, + modifier = modifier + ) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt new file mode 100644 index 0000000000..b0d2243c70 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt @@ -0,0 +1,54 @@ +/* + * 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.preferences.impl.developer.tracing + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import io.element.android.libraries.architecture.Presenter +import kotlinx.collections.immutable.toImmutableMap +import javax.inject.Inject + +class ConfigureTracingPresenter @Inject constructor( + private val tracingConfigurationStore: TracingConfigurationStore, + private val targetLogLevelMapBuilder: TargetLogLevelMapBuilder, +) : Presenter { + + @Composable + override fun present(): ConfigureTracingState { + val modifiedMap = remember { mutableStateOf(targetLogLevelMapBuilder.getCurrentMap()) } + + fun handleEvents(event: ConfigureTracingEvents) { + when (event) { + is ConfigureTracingEvents.UpdateFilter -> { + modifiedMap.value = modifiedMap.value.toMutableMap() + .apply { this[event.target] = event.logLevel } + tracingConfigurationStore.storeLogLevel(event.target, event.logLevel) + } + ConfigureTracingEvents.ResetFilters -> { + modifiedMap.value = targetLogLevelMapBuilder.getDefaultMap() + tracingConfigurationStore.reset() + } + } + } + + return ConfigureTracingState( + targetsToLogLevel = modifiedMap.value.toImmutableMap(), + eventSink = ::handleEvents + ) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingState.kt new file mode 100644 index 0000000000..bc36a6ede3 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingState.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.preferences.impl.developer.tracing + +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target +import kotlinx.collections.immutable.ImmutableMap + +data class ConfigureTracingState( + val targetsToLogLevel: ImmutableMap, + val eventSink: (ConfigureTracingEvents) -> Unit +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingStateProvider.kt new file mode 100644 index 0000000000..fe2cfa44a9 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingStateProvider.kt @@ -0,0 +1,38 @@ +/* + * 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.preferences.impl.developer.tracing + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target +import kotlinx.collections.immutable.persistentMapOf + +open class ConfigureTracingStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aConfigureTracingState(), + ) +} + +fun aConfigureTracingState() = ConfigureTracingState( + targetsToLogLevel = persistentMapOf( + Target.COMMON to LogLevel.INFO, + Target.MATRIX_SDK_FFI to LogLevel.WARN, + Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.ERROR, + ), + eventSink = {} +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingView.kt new file mode 100644 index 0000000000..6b278f3c87 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingView.kt @@ -0,0 +1,250 @@ +/* + * 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.preferences.impl.developer.tracing + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.ArrowDropUp +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.ExperimentalMaterial3Api +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 androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.aliasScreenTitle +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.ListItem +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target +import io.element.android.libraries.theme.ElementTheme +import kotlinx.collections.immutable.ImmutableMap + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ConfigureTracingView( + state: ConfigureTracingState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + var showMenu by remember { mutableStateOf(false) } + Scaffold( + modifier = modifier + .fillMaxSize() + .systemBarsPadding() + .imePadding(), + contentWindowInsets = WindowInsets.statusBars, + topBar = { + TopAppBar( + navigationIcon = { + BackButton(onClick = onBackPressed) + }, + title = { + Text( + text = "Configure tracing", + style = ElementTheme.typography.aliasScreenTitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + actions = { + IconButton( + onClick = { showMenu = !showMenu } + ) { + Icon( + imageVector = Icons.Default.MoreVert, + tint = ElementTheme.materialColors.secondary, + contentDescription = null, + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false } + ) { + DropdownMenuItem( + onClick = { + showMenu = false + state.eventSink.invoke(ConfigureTracingEvents.ResetFilters) + }, + text = { Text("Reset to default") }, + leadingIcon = { + Icon( + Icons.Outlined.Delete, + tint = ElementTheme.materialColors.secondary, + contentDescription = null, + ) + } + ) + } + } + ) + }, + content = { + Column( + modifier = Modifier + .padding(it) + .consumeWindowInsets(it) + .verticalScroll(state = rememberScrollState()) + ) { + CrateListContent(state) + ListItem( + headlineContent = { + Text( + text = "Kill and restart the app for the change to take effect.", + style = ElementTheme.typography.fontHeadingSmMedium, + ) + }, + ) + } + } + ) +} + +@Composable +fun CrateListContent( + state: ConfigureTracingState, + modifier: Modifier = Modifier +) { + fun onLogLevelChange(target: Target, logLevel: LogLevel) { + state.eventSink(ConfigureTracingEvents.UpdateFilter(target, logLevel)) + } + + TargetAndLogLevelListView( + modifier = modifier, + data = state.targetsToLogLevel, + onLogLevelChange = ::onLogLevelChange, + ) +} + +@Composable +private fun TargetAndLogLevelListView( + data: ImmutableMap, + onLogLevelChange: (Target, LogLevel) -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + data.forEach { item -> + fun onLogLevelChange(logLevel: LogLevel) { + onLogLevelChange(item.key, logLevel) + } + + TargetAndLogLevelView( + target = item.key, + logLevel = item.value, + onLogLevelChange = ::onLogLevelChange + ) + } + } +} + +@Composable +fun TargetAndLogLevelView( + target: Target, + logLevel: LogLevel, + onLogLevelChange: (LogLevel) -> Unit, + modifier: Modifier = Modifier +) { + ListItem( + modifier = modifier, + headlineContent = { Text(text = target.filter.takeIf { it.isNotEmpty() } ?: "(common)") }, + trailingContent = ListItemContent.Custom { + LogLevelDropdownMenu( + logLevel = logLevel, + onLogLevelChange = onLogLevelChange, + ) + }, + ) +} + +@Composable +fun LogLevelDropdownMenu( + logLevel: LogLevel, + onLogLevelChange: (LogLevel) -> Unit, + modifier: Modifier = Modifier, +) { + var expanded by remember { mutableStateOf(false) } + Box(modifier = modifier) { + DropdownMenuItem( + modifier = Modifier.widthIn(max = 120.dp), + text = { Text(text = logLevel.filter) }, + onClick = { expanded = !expanded }, + trailingIcon = { + if (expanded) { + Icon(Icons.Default.ArrowDropUp, contentDescription = null) + } else { + Icon(Icons.Default.ArrowDropDown, contentDescription = null) + } + }, + ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + LogLevel.values().forEach { logLevel -> + DropdownMenuItem( + text = { + Text(text = logLevel.filter) + }, + onClick = { + expanded = false + onLogLevelChange(logLevel) + } + ) + } + } + } +} + +@DayNightPreviews +@Composable +internal fun ConfigureTracingViewPreview( + @PreviewParameter(ConfigureTracingStateProvider::class) state: ConfigureTracingState +) = ElementPreview { + ConfigureTracingView( + state = state, + onBackPressed = {}, + ) +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/TargetLogLevelMapBuilder.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/TargetLogLevelMapBuilder.kt new file mode 100644 index 0000000000..b851c15279 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/TargetLogLevelMapBuilder.kt @@ -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. + */ + +package io.element.android.features.preferences.impl.developer.tracing + +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target +import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations +import javax.inject.Inject + +class TargetLogLevelMapBuilder @Inject constructor( + private val tracingConfigurationStore: TracingConfigurationStore, +) { + private val defaultConfig = TracingFilterConfigurations.debug + + fun getDefaultMap(): Map { + return Target.entries.associateWith { target -> + defaultConfig.getLogLevel(target) + } + } + + fun getCurrentMap(): Map { + return Target.entries.associateWith { target -> + tracingConfigurationStore.getLogLevel(target) + ?: defaultConfig.getLogLevel(target) + } + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/TracingConfigurationStore.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/TracingConfigurationStore.kt new file mode 100644 index 0000000000..582231eb50 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/TracingConfigurationStore.kt @@ -0,0 +1,60 @@ +/* + * 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.preferences.impl.developer.tracing + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.DefaultPreferences +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target +import javax.inject.Inject + +interface TracingConfigurationStore { + fun getLogLevel(target: Target): LogLevel? + fun storeLogLevel(target: Target, logLevel: LogLevel) + fun reset() +} + +@ContributesBinding(AppScope::class) +class SharedPrefTracingConfigurationStore @Inject constructor( + @DefaultPreferences private val sharedPreferences: SharedPreferences +) : TracingConfigurationStore { + override fun getLogLevel(target: Target): LogLevel? { + return sharedPreferences.getString("$KEY_PREFIX${target.name}", null) + ?.let { LogLevel.valueOf(it) } + } + + override fun storeLogLevel(target: Target, logLevel: LogLevel) { + sharedPreferences.edit { + putString("$KEY_PREFIX${target.name}", logLevel.name) + } + } + + override fun reset() { + sharedPreferences.edit { + sharedPreferences.all.keys.filter { it.startsWith(KEY_PREFIX) }.forEach { + remove(it) + } + } + } + + companion object { + private const val KEY_PREFIX = "tracing_log_level_" + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt index 4b025c10ad..ad98756992 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt @@ -20,10 +20,17 @@ 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.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class AboutPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = AboutPresenter() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt index 2a7fdba258..775ad1b183 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt @@ -23,10 +23,17 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class AnalyticsSettingsPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val analyticsPresenter = DefaultAnalyticsPreferencesPresenter(FakeAnalyticsService(), aBuildMeta()) 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 9b1bda3631..17ffa2fe73 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 @@ -28,10 +28,17 @@ 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.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class DeveloperSettingsPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - ensures initial state is correct`() = runTest { val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt new file mode 100644 index 0000000000..5388e97e9e --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt @@ -0,0 +1,110 @@ +/* + * 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.preferences.impl.developer.tracing + +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.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.waitForPredicate +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class ConfigureTracingPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test + fun `present - initial state`() = runTest { + val store = InMemoryTracingConfigurationStore() + val presenter = ConfigureTracingPresenter( + store, + TargetLogLevelMapBuilder(store), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.targetsToLogLevel).isNotEmpty() + assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG) + } + } + + @Test + fun `present - store is taken into account`() = runTest { + val store = InMemoryTracingConfigurationStore() + store.givenLogLevel(LogLevel.ERROR) + val presenter = ConfigureTracingPresenter( + store, + TargetLogLevelMapBuilder(store), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.targetsToLogLevel).isNotEmpty() + assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.ERROR) + } + } + + @Test + fun `present - change a value`() = runTest { + val store = InMemoryTracingConfigurationStore() + val presenter = ConfigureTracingPresenter( + store, + TargetLogLevelMapBuilder(store), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG) + initialState.eventSink.invoke(ConfigureTracingEvents.UpdateFilter(Target.MATRIX_SDK_CRYPTO, LogLevel.WARN)) + val finalState = awaitItem() + assertThat(finalState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.WARN) + waitForPredicate { store.hasStoreLogLevelBeenCalled } + } + } + + @Test + fun `present - reset`() = runTest { + val store = InMemoryTracingConfigurationStore() + val presenter = ConfigureTracingPresenter( + store, + TargetLogLevelMapBuilder(store), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG) + initialState.eventSink.invoke(ConfigureTracingEvents.UpdateFilter(Target.MATRIX_SDK_CRYPTO, LogLevel.WARN)) + val finalState = awaitItem() + assertThat(finalState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.WARN) + waitForPredicate { store.hasStoreLogLevelBeenCalled } + finalState.eventSink.invoke(ConfigureTracingEvents.ResetFilters) + val resetState = awaitItem() + assertThat(resetState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG) + waitForPredicate { store.hasResetBeenCalled } + } + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/InMemoryTracingConfigurationStore.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/InMemoryTracingConfigurationStore.kt new file mode 100644 index 0000000000..1d17a1227b --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/InMemoryTracingConfigurationStore.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.preferences.impl.developer.tracing + +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.Target + +class InMemoryTracingConfigurationStore : TracingConfigurationStore { + var hasResetBeenCalled = false + private set + var hasStoreLogLevelBeenCalled = false + private set + private var logLevel: LogLevel? = null + + fun givenLogLevel(logLevel: LogLevel?) { + this.logLevel = logLevel + } + + override fun getLogLevel(target: Target): LogLevel? { + return logLevel + } + + override fun storeLogLevel(target: Target, logLevel: LogLevel) { + hasStoreLogLevelBeenCalled = true + } + + override fun reset() { + hasResetBeenCalled = true + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 9ad5db3d1b..5217519071 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -30,10 +30,17 @@ import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class PreferencesRootPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val matrixClient = FakeMatrixClient() diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index 464d521689..eced5c78b8 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -57,4 +57,5 @@ dependencies { testImplementation(libs.test.mockk) testImplementation(projects.libraries.matrix.test) testImplementation(projects.features.rageshake.test) + testImplementation(projects.tests.testutils) } diff --git a/features/rageshake/impl/src/main/res/values-ro/translations.xml b/features/rageshake/impl/src/main/res/values-ro/translations.xml index db0398c0db..e82c06826e 100644 --- a/features/rageshake/impl/src/main/res/values-ro/translations.xml +++ b/features/rageshake/impl/src/main/res/values-ro/translations.xml @@ -2,12 +2,13 @@ "Atașați o captură de ecran" "Puteți să mă contactați dacă aveți întrebări suplimentare" + "Contactați-mă" "Editați captura de ecran" "Vă rugăm să descrieți eroarea. Ce ați făcut? Ce vă aşteptați să se întâmple? Ce s-a întâmplat de fapt. Vă rugam să intrați în cât mai multe detalii cu putință." "Descrieți eroarea…" "Dacă posibil, vă rugăm să scrieți descrierea în engleză." "Trimiteți log-uri" - "Trimiteți log-uri pentru a ajuta" + "Permiteți log-uri" "Trimiteți captură de ecran" "Pentru a verifica că lucrurile funcționează conform așteptărilor, log-uri vor fi trimise împreună cu mesajul. Acestea vor fi private. Pentru a trimite doar mesajul, dezactivați această setare." "%1$s s-a blocat ultima dată când a fost folosit. Doriți să ne trimiteți un raport?" diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt index c0418783dd..204cd4aea1 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt @@ -26,13 +26,20 @@ import io.element.android.features.rageshake.test.screenshot.A_SCREENSHOT_URI import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test const val A_SHORT_DESCRIPTION = "bug!" const val A_LONG_DESCRIPTION = "I have seen a bug!" class BugReportPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = BugReportPresenter( diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt index b8b8c4b6d0..e77c1fd7e2 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt @@ -24,10 +24,17 @@ import io.element.android.features.rageshake.api.crash.CrashDetectionEvents import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter import io.element.android.features.rageshake.test.crash.A_CRASH_DATA import io.element.android.features.rageshake.test.crash.FakeCrashDataStore +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class CrashDetectionPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state no crash`() = runTest { val presenter = DefaultCrashDetectionPresenter( diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt index 02a0fc0794..d1d09131ad 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt @@ -28,14 +28,20 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.BeforeClass +import org.junit.Rule import org.junit.Test class RageshakeDetectionPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + companion object { private lateinit var aBitmap: Bitmap diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt index 56759c360c..74cd1906a9 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt @@ -24,10 +24,17 @@ import io.element.android.features.rageshake.api.preferences.RageshakePreference import io.element.android.features.rageshake.test.rageshake.A_SENSITIVITY import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class RageshakePreferencesPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state available`() = runTest { val presenter = DefaultRageshakePreferencesPresenter( 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 0ad5778cb2..19aeb7ba0f 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 @@ -16,6 +16,7 @@ package io.element.android.features.roomdetails.impl +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi @@ -58,6 +59,7 @@ import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection import io.element.android.features.roomdetails.impl.members.details.RoomMemberHeaderSection import io.element.android.features.roomdetails.impl.members.details.RoomMemberMainActionsSection +import io.element.android.libraries.designsystem.components.ClickableLinkText 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 @@ -293,11 +295,13 @@ internal fun TopicSection( onClick = { onActionClicked(RoomDetailsAction.AddTopic) }, ) } else if (roomTopic is RoomTopicState.ExistingTopic) { - Text( - roomTopic.topic, + ClickableLinkText( + text = roomTopic.topic, modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 12.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.tertiary + interactionSource = remember { MutableInteractionSource() }, + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.tertiary, + ), ) } } 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 c20cdab3f9..a2be94aee9 100644 --- a/features/roomdetails/impl/src/main/res/values-ro/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ro/translations.xml @@ -12,8 +12,13 @@ "Nu s-a putut actualiza camera" "Mesajele sunt securizate cu încuietori. Doar dumneavoastră și destinatarii aveți cheile unice pentru a le debloca." "Criptarea mesajelor este activată" + "A apărut o eroare la încărcarea setărilor pentru notificari." + "Dezactivarea notificarilor pentru această cameră a eșuat, încercați din nou." + "Activarea notificarilor pentru această cameră a eșuat, încercați din nou." "Invitați persoane" - "Notificare" + "Personalizat" + "Implicit" + "Notificări" "Numele camerei" "Partajați camera" "Se actualizează camera…" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 61da711d51..bbd67bd11a 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -36,27 +36,37 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState +import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test +import kotlin.time.Duration.Companion.milliseconds @ExperimentalCoroutinesApi class RoomDetailsPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() private fun aRoomDetailsPresenter( room: MatrixRoom, leaveRoomPresenter: LeaveRoomPresenter = LeaveRoomPresenterFake(), - dispatchers: CoroutineDispatchers + dispatchers: CoroutineDispatchers, + notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ): RoomDetailsPresenter { - val matrixClient = FakeMatrixClient() + val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId) @@ -352,6 +362,64 @@ class RoomDetailsPresenterTests { cancelAndIgnoreRemainingEvents() } } + + @Test + fun `present - notification mode changes`() = runTest { + val leaveRoomPresenter = LeaveRoomPresenterFake() + val notificationSettingsService = FakeNotificationSettingsService() + val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) + val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + + notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + val updatedState = consumeItemsUntilPredicate { + it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + }.last() + assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - mute room notifications`() = runTest { + val leaveRoomPresenter = LeaveRoomPresenterFake() + val notificationSettingsService = FakeNotificationSettingsService(initialMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) + val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(RoomDetailsEvent.MuteNotification) + val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { + it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE + }.last() + assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MUTE) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - unmute room notifications`() = runTest { + val leaveRoomPresenter = LeaveRoomPresenterFake() + val notificationSettingsService = FakeNotificationSettingsService( + initialMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, + initialDefaultMode = RoomNotificationMode.ALL_MESSAGES + ) + val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) + val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification) + val updatedState = consumeItemsUntilPredicate { + it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES + }.last() + assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES) + cancelAndIgnoreRemainingEvents() + } + } } fun aMatrixRoom( @@ -363,6 +431,7 @@ fun aMatrixRoom( isEncrypted: Boolean = true, isPublic: Boolean = true, isDirect: Boolean = false, + notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ) = FakeMatrixRoom( roomId = roomId, name = name, @@ -372,5 +441,6 @@ fun aMatrixRoom( isEncrypted = isEncrypted, isPublic = isPublic, isDirect = isDirect, + notificationSettingsService = notificationSettingsService ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt index e43703e235..587cc34429 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt @@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.tests.testutils.WarmUpRule import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -40,12 +41,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import java.io.File @ExperimentalCoroutinesApi class RoomDetailsEditPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + private lateinit var fakePickerProvider: FakePickerProvider private lateinit var fakeMediaPreProcessor: FakeMediaPreProcessor diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt index ede7342882..175e4ec48c 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt @@ -36,14 +36,20 @@ import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.usersearch.api.UserSearchResult import io.element.android.libraries.usersearch.test.FakeUserRepository +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test internal class RoomInviteMembersPresenterTest { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state has no results and no search`() = runTest { val presenter = RoomInviteMembersPresenter( diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt index 9de035d017..6dd8e5bf56 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt @@ -32,15 +32,21 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test @ExperimentalCoroutinesApi class RoomMemberListPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `search is done automatically on start, but is async`() = runTest { val presenter = createPresenter() diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index 71df3ad633..2fd240237f 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -29,13 +29,19 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test @ExperimentalCoroutinesApi class RoomMemberDetailsPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - returns the room member's data, then updates it if needed`() = runTest { val roomMember = aRoomMember(displayName = "Alice") diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt new file mode 100644 index 0000000000..29bec7f622 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -0,0 +1,83 @@ +/* + * 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.roomdetails.notificationsettings + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth +import io.element.android.features.roomdetails.aMatrixRoom +import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsEvents +import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsPresenter +import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE +import io.element.android.tests.testutils.consumeItemsUntilPredicate +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class RoomNotificationSettingsPresenterTests { + @Test + fun `present - initial state is created from room info`() = runTest { + val presenter = aNotificationPresenter + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.roomNotificationSettings).isNull() + Truth.assertThat(initialState.defaultRoomNotificationMode).isNull() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - notification mode changed`() = runTest { + val presenter = aNotificationPresenter + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) + val updatedState = consumeItemsUntilPredicate { + it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + }.last() + Truth.assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + } + } + + @Test + fun `present - notification settings restore default`() = runTest { + val presenter = aNotificationPresenter + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) + initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) + val defaultState = consumeItemsUntilPredicate { + it.roomNotificationSettings?.mode == A_ROOM_NOTIFICATION_MODE + }.last() + Truth.assertThat(defaultState.roomNotificationSettings?.mode).isEqualTo(A_ROOM_NOTIFICATION_MODE) + } + } + + private val aNotificationPresenter: RoomNotificationSettingsPresenter get() { + val room = aMatrixRoom() + return RoomNotificationSettingsPresenter( + room = room, + notificationSettingsService = room.notificationSettingsService + ) + } +} 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 176962ca2a..80d183a91b 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 @@ -18,11 +18,13 @@ package io.element.android.features.roomlist.impl import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState @@ -43,7 +45,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import io.element.android.features.leaveroom.api.LeaveRoomView -import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView +import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer import io.element.android.features.roomlist.impl.components.RequestVerificationHeader import io.element.android.features.roomlist.impl.components.RoomListMenuAction import io.element.android.features.roomlist.impl.components.RoomListTopBar @@ -52,8 +54,8 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.search.RoomListSearchResultView import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.FloatingActionButton +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.Scaffold import io.element.android.libraries.designsystem.utils.LogCompositions @@ -74,8 +76,10 @@ fun RoomListView( onMenuActionClicked: (RoomListMenuAction) -> Unit, modifier: Modifier = Modifier, ) { - Column(modifier = modifier) { - ConnectivityIndicatorView(isOnline = state.hasNetworkConnection) + ConnectivityIndicatorContainer( + modifier = modifier, + isOnline = state.hasNetworkConnection, + ) { topPadding -> Box { fun onRoomLongClicked( roomListRoomSummary: RoomListRoomSummary @@ -94,6 +98,7 @@ fun RoomListView( LeaveRoomView(state = state.leaveRoomState) RoomListContent( + modifier = Modifier.padding(top = topPadding), state = state, onVerifyClicked = onVerifyClicked, onRoomClicked = onRoomClicked, @@ -109,6 +114,8 @@ fun RoomListView( onRoomClicked = onRoomClicked, onRoomLongClicked = { onRoomLongClicked(it) }, modifier = Modifier + .statusBarsPadding() + .padding(top = topPadding) .fillMaxSize() .background(MaterialTheme.colorScheme.background) ) @@ -210,6 +217,11 @@ fun RoomListContent( HorizontalDivider() } } + // Add a last Spacer item to ensure that the FAB does not hide the last room item + // FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80 + item { + Spacer(modifier = Modifier.height(80.dp)) + } } }, floatingActionButton = { @@ -219,8 +231,7 @@ fun RoomListContent( onClick = onCreateRoomClicked ) { Icon( - // Correct icon alignment for better rendering. - modifier = Modifier.padding(start = 1.dp, bottom = 1.dp), + // Note cannot use Icons.Outlined.EditSquare, it does not exist :/ resourceId = DrawableR.drawable.ic_edit_square, 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 001c048e4e..508385dd84 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 @@ -17,10 +17,13 @@ package io.element.android.features.roomlist.impl.components import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.ExperimentalMaterial3Api @@ -30,24 +33,37 @@ import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import io.element.android.features.roomlist.impl.R 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.avatarBloom import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.text.applyScaleDown +import io.element.android.libraries.designsystem.text.roundToPx +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.aliasScreenTitle 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.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.MediumTopAppBar @@ -60,6 +76,9 @@ import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.libraries.designsystem.R as CommonR + +private val avatarBloomSize = 430.dp @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -89,6 +108,7 @@ fun RoomListTopBar( DefaultRoomListTopBar( matrixUser = matrixUser, + areSearchResultsDisplayed = areSearchResultsDisplayed, onOpenSettings = onOpenSettings, onSearchClicked = onToggleSearch, onMenuActionClicked = onMenuActionClicked, @@ -101,6 +121,7 @@ fun RoomListTopBar( @Composable private fun DefaultRoomListTopBar( matrixUser: MatrixUser?, + areSearchResultsDisplayed: Boolean, scrollBehavior: TopAppBarScrollBehavior, onOpenSettings: () -> Unit, onSearchClicked: () -> Unit, @@ -108,94 +129,145 @@ private fun DefaultRoomListTopBar( modifier: Modifier = Modifier, ) { var showMenu by remember { mutableStateOf(false) } - MediumTopAppBar( - modifier = modifier - .nestedScroll(scrollBehavior.nestedScrollConnection), - title = { - val fontStyle = if (scrollBehavior.state.collapsedFraction > 0.5) - ElementTheme.typography.aliasScreenTitle - else - ElementTheme.typography.fontHeadingLgBold.copy( - // Due to a limitation of MediumTopAppBar, and to avoid the text to be truncated, - // ensure that the font size will never be bigger than 28.dp. - fontSize = 28.dp.applyScaleDown().toSp() - ) - Text( - style = fontStyle, - text = stringResource(id = R.string.screen_roomlist_main_space_title) - ) - }, - navigationIcon = { - if (matrixUser != null) { - IconButton( - modifier = Modifier.testTag(TestTags.homeScreenSettings), - onClick = onOpenSettings - ) { - val avatarData by remember { - derivedStateOf { - matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar) - } - } - Avatar(avatarData, contentDescription = stringResource(CommonStrings.common_settings)) + + // We need this to manually clip the top app bar in preview mode + val previewAppBarHeight = if (LocalInspectionMode.current) { + 112.dp.roundToPx() + } else { + null + } + val collapsedFraction = scrollBehavior.state.collapsedFraction + var appBarHeight by remember { + mutableIntStateOf(previewAppBarHeight ?: 0) + } + + val avatarData by remember(matrixUser) { + derivedStateOf { + matrixUser?.getAvatarData(size = AvatarSize.CurrentUserTopBar) + } + } + + val statusBarPadding = with (LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() } + + Box(modifier = modifier) { + MediumTopAppBar( + modifier = Modifier + .onSizeChanged { + appBarHeight = it.height } - } - }, - actions = { - IconButton( - onClick = onSearchClicked, - ) { - Icon( - imageVector = Icons.Default.Search, - tint = ElementTheme.materialColors.secondary, - contentDescription = stringResource(CommonStrings.action_search), - ) - } - IconButton( - onClick = { showMenu = !showMenu } - ) { - Icon( - imageVector = Icons.Default.MoreVert, - tint = ElementTheme.materialColors.secondary, - contentDescription = null, - ) - } - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false } - ) { - DropdownMenuItem( - onClick = { - showMenu = false - onMenuActionClicked(RoomListMenuAction.InviteFriends) + .nestedScroll(scrollBehavior.nestedScrollConnection) + .avatarBloom( + avatarData = avatarData, + background = if (ElementTheme.isLightTheme) { + // Workaround to display a very subtle bloom for avatars with very soft colors + Color(0xFFF9F9F9) + } else { + ElementTheme.materialColors.background }, - text = { Text(stringResource(id = CommonStrings.action_invite)) }, - leadingIcon = { - Icon( - Icons.Outlined.Share, - tint = ElementTheme.materialColors.secondary, - contentDescription = null, + blurSize = DpSize(avatarBloomSize, avatarBloomSize), + offset = DpOffset(24.dp, 24.dp + statusBarPadding), + clipToSize = if (appBarHeight > 0) DpSize( + avatarBloomSize, + appBarHeight.toDp() + ) else DpSize.Unspecified, + bottomSoftEdgeColor = ElementTheme.materialColors.background, + bottomSoftEdgeAlpha = 1f - collapsedFraction, + alpha = if (areSearchResultsDisplayed) 0f else 1f, + ) + .statusBarsPadding(), + colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent, + ), + title = { + val fontStyle = if (scrollBehavior.state.collapsedFraction > 0.5) + ElementTheme.typography.aliasScreenTitle + else + ElementTheme.typography.fontHeadingLgBold.copy( + // Due to a limitation of MediumTopAppBar, and to avoid the text to be truncated, + // ensure that the font size will never be bigger than 28.dp. + fontSize = 28.dp.applyScaleDown().toSp() + ) + Text( + style = fontStyle, + text = stringResource(id = R.string.screen_roomlist_main_space_title) + ) + }, + navigationIcon = { + avatarData?.let { + IconButton( + modifier = Modifier.testTag(TestTags.homeScreenSettings), + onClick = onOpenSettings + ) { + Avatar( + avatarData = it, + contentDescription = stringResource(CommonStrings.common_settings), ) } - ) - DropdownMenuItem( - onClick = { - showMenu = false - onMenuActionClicked(RoomListMenuAction.ReportBug) - }, - text = { Text(stringResource(id = CommonStrings.common_report_a_bug)) }, - leadingIcon = { - Icon( - Icons.Outlined.BugReport, - tint = ElementTheme.materialColors.secondary, - contentDescription = null, - ) - } - ) - } - }, - scrollBehavior = scrollBehavior, - windowInsets = WindowInsets(0.dp), - ) + } + }, + actions = { + IconButton( + onClick = onSearchClicked, + ) { + Icon( + resourceId = CommonR.drawable.ic_search, + contentDescription = stringResource(CommonStrings.action_search), + ) + } + IconButton( + onClick = { showMenu = !showMenu } + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = null, + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false } + ) { + DropdownMenuItem( + onClick = { + showMenu = false + onMenuActionClicked(RoomListMenuAction.InviteFriends) + }, + text = { Text(stringResource(id = CommonStrings.action_invite)) }, + leadingIcon = { + Icon( + Icons.Outlined.Share, + tint = ElementTheme.materialColors.secondary, + contentDescription = null, + ) + } + ) + DropdownMenuItem( + onClick = { + showMenu = false + onMenuActionClicked(RoomListMenuAction.ReportBug) + }, + text = { Text(stringResource(id = CommonStrings.common_report_a_bug)) }, + leadingIcon = { + Icon( + Icons.Outlined.BugReport, + tint = ElementTheme.materialColors.secondary, + contentDescription = null, + ) + } + ) + } + }, + scrollBehavior = scrollBehavior, + windowInsets = WindowInsets(0.dp), + ) + + HorizontalDivider(modifier = + Modifier.fillMaxWidth() + .alpha(collapsedFraction) + .align(Alignment.BottomCenter), + color = ElementTheme.materialColors.outlineVariant, + ) + } } @Preview @@ -211,6 +283,7 @@ internal fun DefaultRoomListTopBarDarkPreview() = ElementPreviewDark { DefaultRo private fun DefaultRoomListTopBarPreview() { DefaultRoomListTopBar( matrixUser = MatrixUser(UserId("@id:domain"), "Alice"), + areSearchResultsDisplayed = false, scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), onOpenSettings = {}, onSearchClicked = {}, diff --git a/features/roomlist/impl/src/main/res/values-ro/translations.xml b/features/roomlist/impl/src/main/res/values-ro/translations.xml index b8ffc57090..e1f43a02ca 100644 --- a/features/roomlist/impl/src/main/res/values-ro/translations.xml +++ b/features/roomlist/impl/src/main/res/values-ro/translations.xml @@ -1,7 +1,9 @@ "Creați o conversație sau o cameră nouă" + "Începeți prin a trimite mesaje cuiva." + "Nu există încă discuții." "Toate conversatiile" - "Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea pentru acces la mesajele dumneavoastră criptate." - "Accesați istoricul mesajelor" + "Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea cu un alt dispozitiv pentru a accesa mesajele dumneavoastră criptate." + "Verificați că sunteți dumneavoastră" diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index d8524d5834..1a2f32e54d 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -50,15 +50,21 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class RoomListPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - should start with no user and then load user with success`() = runTest { val presenter = createRoomListPresenter() diff --git a/features/verifysession/impl/build.gradle.kts b/features/verifysession/impl/build.gradle.kts index cba9f2890e..a85d714d1e 100644 --- a/features/verifysession/impl/build.gradle.kts +++ b/features/verifysession/impl/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index 82664f0e03..ced2c48e9b 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -26,13 +26,19 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test @ExperimentalCoroutinesApi class VerifySelfSessionPresenterTests { + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - Initial state is received`() = runTest { val presenter = createPresenter() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d52d1e0df..d172bc120d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,19 +10,19 @@ molecule = "1.2.0" # AndroidX material = "1.9.0" -core = "1.10.1" +core = "1.12.0" datastore = "1.0.0" constraintlayout = "2.1.4" constraintlayout_compose = "1.0.1" recyclerview = "1.3.1" -lifecycle = "2.6.1" +lifecycle = "2.6.2" activity = "1.7.2" startup = "1.1.1" media3 = "1.1.1" browser = "1.6.0" # Compose -compose_bom = "2023.08.00" +compose_bom = "2023.09.00" composecompiler = "1.5.3" # Coroutines @@ -36,7 +36,7 @@ test_core = "1.5.0" #other coil = "2.4.0" -datetime = "0.4.0" +datetime = "0.4.1" serialization_json = "1.6.0" showkase = "1.0.0-beta18" jsoup = "1.16.1" @@ -45,11 +45,12 @@ dependencycheck = "8.4.0" dependencyanalysis = "1.21.0" stem = "2.3.0" sqldelight = "1.5.5" -telephoto = "0.6.0-SNAPSHOT" +telephoto = "0.6.0" +wysiwyg = "2.9.0" # DI -dagger = "2.47" -anvil = "2.4.7-1-8" +dagger = "2.48" +anvil = "2.4.8-1-8" # Auto service autoservice = "1.1.1" @@ -92,6 +93,8 @@ androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = androidx_preference = "androidx.preference:preference:1.2.1" androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" } +# Warning: issue on alpha07, make sure this is working when upgrading +# Context in https://github.com/vector-im/element-x-android/pull/1239#issuecomment-1711500332 androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha06" # Coroutines @@ -146,7 +149,9 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.48" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.50" +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 = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } @@ -155,7 +160,6 @@ sqlite = "androidx.sqlite:sqlite:2.3.1" 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" -vanniktech_emoji = "com.vanniktech:emoji-google:0.16.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.0" maplibre = "org.maplibre.gl:android-sdk:10.2.0" @@ -164,8 +168,11 @@ maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.1" # Analytics posthog = "com.posthog.android:posthog:2.0.3" -sentry = "io.sentry:sentry-android:6.28.0" -matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:42b2faa417c1e95f430bf8f6e379adba25ad5ef8" +sentry = "io.sentry:sentry-android:6.29.0" +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:e9cd9adaf18cec52ed851395eb84358b4f9b8d7f" + +# Emojibase +matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3" # Di inject = "javax.inject:javax.inject:1" @@ -178,7 +185,6 @@ anvil_compiler_utils = { module = "com.squareup.anvil:compiler-utils", version.r google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } google_autoservice_annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoservice" } - # Miscellaneous # Add unused dependency to androidx.compose.compiler:compiler to let Renovate create PR to change the # value of `composecompiler` (which is used to set composeOptions.kotlinCompilerExtensionVersion. @@ -202,6 +208,9 @@ dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version. dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" } dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyanalysis" } paparazzi = "app.cash.paparazzi:1.3.1" -sonarqube = "org.sonarqube:4.2.1.3168" kover = "org.jetbrains.kotlinx.kover:0.6.1" sqldelight = { id = "com.squareup.sqldelight", version.ref = "sqldelight" } + +# Version '4.3.1.3277' introduced some regressions in CI time (more than 2x slower), so make sure +# this is no longer the case before upgrading. +sonarqube = "org.sonarqube:4.2.1.3168" diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt index 6f8aa76d03..c3b7e3110e 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt @@ -22,7 +22,6 @@ import android.graphics.Matrix import androidx.core.graphics.scale import androidx.exifinterface.media.ExifInterface import java.io.File -import java.io.InputStream import kotlin.math.min fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) { @@ -32,13 +31,6 @@ fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int } } -/** - * Reads the EXIF metadata from the [inputStream] and rotates the current [Bitmap] to match it. - * @return The resulting [Bitmap] or `null` if no metadata was found. - */ -fun Bitmap.rotateToMetadataOrientation(inputStream: InputStream): Result = - runCatching { rotateToMetadataOrientation(this, ExifInterface(inputStream)) } - /** * Scales the current [Bitmap] to fit the ([maxWidth], [maxHeight]) bounds while keeping aspect ratio. * @throws IllegalStateException if [maxWidth] or [maxHeight] <= 0. @@ -77,8 +69,11 @@ fun BitmapFactory.Options.calculateInSampleSize(desiredWidth: Int, desiredHeight return inSampleSize } -private fun rotateToMetadataOrientation(bitmap: Bitmap, exifInterface: ExifInterface): Bitmap { - val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) +/** + * Decodes the [inputStream] into a [Bitmap] and applies the needed rotation based on [orientation]. + * This orientation value must be one of `ExifInterface.ORIENTATION_*` constants. + */ +fun Bitmap.rotateToMetadataOrientation(orientation: Int): Bitmap { val matrix = Matrix() when (orientation) { ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f) @@ -94,8 +89,8 @@ private fun rotateToMetadataOrientation(bitmap: Bitmap, exifInterface: ExifInter matrix.preRotate(90f) matrix.preScale(-1f, 1f) } - else -> return bitmap + else -> return this } - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt index 47b951baa2..20ebb85615 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt @@ -27,4 +27,18 @@ object VectorIcons { val ReportContent = R.drawable.ic_report_content val Groups = R.drawable.ic_groups val Share = R.drawable.ic_share + val Poll = R.drawable.ic_poll + val PollEnd = R.drawable.ic_poll_end + val Bold = R.drawable.ic_bold + val BulletList = R.drawable.ic_bullet_list + val CodeBlock = R.drawable.ic_code_block + val IndentIncrease = R.drawable.ic_indent_increase + val IndentDecrease = R.drawable.ic_indent_decrease + val InlineCode = R.drawable.ic_inline_code + val Italic = R.drawable.ic_italic + val Link = R.drawable.ic_link + val NumberedList = R.drawable.ic_numbered_list + val Quote = R.drawable.ic_quote + val Strikethrough = R.drawable.ic_strikethrough + val Underline = R.drawable.ic_underline } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt index 10a6f529af..d4b203d4ae 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt @@ -44,6 +44,7 @@ import io.element.android.libraries.designsystem.text.withColoredPeriod import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.theme.ForcedDarkElementTheme @Composable fun SunsetPage( @@ -53,9 +54,7 @@ fun SunsetPage( modifier: Modifier = Modifier, overallContent: @Composable () -> Unit, ) { - ElementTheme( - darkTheme = true - ) { + ForcedDarkElementTheme(lightStatusBar = true) { Box( modifier = modifier.fillMaxSize() ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.kt new file mode 100644 index 0000000000..abac299ca6 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.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.libraries.designsystem.colors + +import androidx.compose.ui.graphics.Color + +data class AvatarColors( + val background: Color, + val foreground: Color, +) + diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt new file mode 100644 index 0000000000..bf195e8160 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt @@ -0,0 +1,58 @@ +/* + * 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.designsystem.colors + +import androidx.collection.LruCache +import io.element.android.libraries.theme.colors.avatarColorsDark +import io.element.android.libraries.theme.colors.avatarColorsLight + +object AvatarColorsProvider { + private val cache = LruCache(200) + private var currentThemeIsLight = true + + fun provide(id: String, isLightTheme: Boolean): AvatarColors { + if (currentThemeIsLight != isLightTheme) { + currentThemeIsLight = isLightTheme + cache.evictAll() + } + val valueFromCache = cache.get(id) + return if (valueFromCache != null) { + valueFromCache + } else { + val colors = avatarColors(id, isLightTheme) + cache.put(id, colors) + colors + } + } + + private fun avatarColors(id: String, isLightTheme: Boolean): AvatarColors { + val hash = id.toHash() + val colors = if (isLightTheme) { + avatarColorsLight[hash] + } else { + avatarColorsDark[hash] + } + return AvatarColors( + background = colors.first, + foreground = colors.second, + ) + } +} + +internal fun String.toHash(): Int { + return toList().sumOf { it.code } % avatarColorsLight.size +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt new file mode 100644 index 0000000000..2a178af000 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt @@ -0,0 +1,564 @@ +/* + * 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.designsystem.components + +import android.graphics.Bitmap +import android.graphics.Typeface +import android.graphics.drawable.BitmapDrawable +import android.os.Build +import android.text.TextPaint +import androidx.annotation.FloatRange +import androidx.compose.foundation.Image +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.ClipOp +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.RadialGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFontFamilyResolver +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.isSpecified +import androidx.compose.ui.unit.toOffset +import androidx.compose.ui.unit.toSize +import coil.imageLoader +import coil.request.DefaultRequestOptions +import coil.request.ImageRequest +import coil.size.Size +import com.airbnb.android.showkase.annotation.ShowkaseComposable +import com.vanniktech.blurhash.BlurHash +import io.element.android.libraries.designsystem.R +import io.element.android.libraries.designsystem.colors.AvatarColorsProvider +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.text.toDp +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.MediumTopAppBar +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.theme.ElementTheme +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlin.math.max +import kotlin.math.roundToInt + +/** + * Default bloom configuration values. + */ +object BloomDefaults { + /** + * Number of components to use with BlurHash to generate the blur effect. + * Larger values mean more detailed blurs. + */ + const val HASH_COMPONENTS = 5 + + /** Default bloom layers. */ + @Composable + fun defaultLayers() = persistentListOf( + // Bottom layer + if (isSystemInDarkTheme()) { + BloomLayer(0.5f, BlendMode.Exclusion) + } else { + BloomLayer(0.2f, BlendMode.Hardlight) + }, + // Top layer + BloomLayer(if (isSystemInDarkTheme()) 0.2f else 0.8f, BlendMode.Color), + ) +} + +/** + * Bloom layer configuration. + * @param alpha The alpha value to apply to the layer. + * @param blendMode The blend mode to apply to the layer. + */ +data class BloomLayer( + val alpha: Float, + val blendMode: BlendMode, +) + +/** + * Bloom effect modifier. Applies a bloom effect to the component. + * @param hash The BlurHash to use as the bloom source. + * @param background The background color to use for the bloom effect. Since we use blend modes it must be non-transparent. + * @param blurSize The size of the bloom effect. If not specified the bloom effect will be the size of the component. + * @param offset The offset to use for the bloom effect. If not specified the bloom effect will be centered on the component. + * @param clipToSize The size to use for clipping the bloom effect. If not specified the bloom effect will not be clipped. + * @param layerConfiguration The configuration for the bloom layers. If not specified the default layers configuration will be used. + * @param bottomSoftEdgeColor The color to use for the bottom soft edge. If not specified the [background] color will be used. + * @param bottomSoftEdgeHeight The height of the bottom soft edge. If not specified the bottom soft edge will not be drawn. + * @param bottomSoftEdgeAlpha The alpha value to apply to the bottom soft edge. + * @param alpha The alpha value to apply to the bloom effect. + */ +fun Modifier.bloom( + hash: String?, + background: Color, + blurSize: DpSize = DpSize.Unspecified, + offset: DpOffset = DpOffset.Unspecified, + clipToSize: DpSize = DpSize.Unspecified, + layerConfiguration: ImmutableList? = null, + bottomSoftEdgeColor: Color = background, + bottomSoftEdgeHeight: Dp = 40.dp, + @FloatRange(from = 0.0, to = 1.0) + bottomSoftEdgeAlpha: Float = 1.0f, + @FloatRange(from = 0.0, to = 1.0) + alpha: Float = 1f, +) = composed { + val defaultLayers = BloomDefaults.defaultLayers() + val layers = layerConfiguration ?: defaultLayers + // Bloom only works on API 29+ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return@composed this + if (hash == null) return@composed this + + val hashedBitmap = remember(hash) { + BlurHash.decode(hash, BloomDefaults.HASH_COMPONENTS, BloomDefaults.HASH_COMPONENTS)?.asImageBitmap() + } ?: return@composed this + val density = LocalDensity.current + val pixelSize = remember(blurSize, density) { blurSize.toIntSize(density) } + val clipToPixelSize = remember(clipToSize, density) { clipToSize.toIntSize(density) } + val bottomSoftEdgeHeightPixels = remember(bottomSoftEdgeHeight, density) { with(density) { bottomSoftEdgeHeight.roundToPx() } } + val isRTL = LocalLayoutDirection.current == LayoutDirection.Rtl + drawWithCache { + val dstSize = if (pixelSize != IntSize.Zero) { + pixelSize + } else { + IntSize(size.width.toInt(), size.height.toInt()) + } + // Calculate where to place the center of the bloom effect + val centerOffset = if (offset.isSpecified) { + if (isRTL) { + IntOffset( + size.width.roundToInt() - offset.x.roundToPx(), + size.height.roundToInt() - offset.y.roundToPx(), + ) + } else { + IntOffset( + offset.x.roundToPx(), + offset.y.roundToPx(), + ) + } + } else { + IntOffset( + size.center.x.toInt(), + size.center.y.toInt(), + ) + } + // Calculate the offset to draw the different layers and apply clipping + // This offset is applied to place the top left corner of the bloom effect + val layersOffset = if (offset.isSpecified) { + // Offsets the layers so the center of the bloom effect is at the provided offset value + IntOffset( + centerOffset.x - dstSize.width / 2, + centerOffset.y - dstSize.height / 2, + ) + } else { + // Places the layers at the center of the component + IntOffset.Zero + } + val radius = max(dstSize.width, dstSize.height).toFloat() / 2 + val circularGradientShader = RadialGradientShader( + centerOffset.toOffset(), + radius, + listOf(Color.Red, Color.Transparent), + listOf(0f, 1f) + ) + val circularGradientBrush = ShaderBrush(circularGradientShader) + val bottomEdgeGradient = LinearGradientShader( + from = IntOffset(0, clipToPixelSize.height - bottomSoftEdgeHeightPixels).toOffset(), + to = IntOffset(0, clipToPixelSize.height).toOffset(), + listOf(Color.Transparent, bottomSoftEdgeColor), + listOf(0f, 1f) + ) + val bottomEdgeGradientBrush = ShaderBrush(bottomEdgeGradient) + onDrawBehind { + if (dstSize != IntSize.Zero) { + val circleClipPath = Path().apply { + addOval(Rect(centerOffset.toOffset(), radius - 1)) + } + // Clip the external radius of bloom gradient too, otherwise we have a 1px border + clipPath(circleClipPath, clipOp = ClipOp.Intersect) { + // Draw the bloom layers + drawWithLayer { + // Clip rect to the provided size if needed + if (clipToPixelSize != IntSize.Zero) { + drawContext.canvas.clipRect(Rect(Offset.Zero, clipToPixelSize.toSize()), ClipOp.Intersect) + } + // Draw background color for blending + drawRect(background, size = pixelSize.toSize()) + // Draw layers + for (layer in layers) { + drawImage( + hashedBitmap, + srcSize = IntSize(BloomDefaults.HASH_COMPONENTS, BloomDefaults.HASH_COMPONENTS), + dstSize = dstSize, + dstOffset = layersOffset, + alpha = layer.alpha * alpha, + blendMode = layer.blendMode, + ) + } + // Mask the layers erasing the outer radius using the gradient brush + drawCircle( + circularGradientBrush, + radius, + centerOffset.toOffset(), + blendMode = BlendMode.DstIn + ) + } + } + // Draw the bottom soft edge + drawRect( + bottomEdgeGradientBrush, + topLeft = IntOffset(0, clipToPixelSize.height - bottomSoftEdgeHeight.roundToPx()).toOffset(), + size = IntSize(pixelSize.width, bottomSoftEdgeHeight.roundToPx()).toSize(), + alpha = bottomSoftEdgeAlpha + ) + } + } + } +} + +/** + * Bloom effect modifier for avatars. Applies a bloom effect to the component. + * @param avatarData The avatar data to use as the bloom source. + * If the avatar data has a URL it will be used as the bloom source, otherwise the initials will be used. If `null` is passed, no bloom effect will be applied. + * @param background The background color to use for the bloom effect. Since we use blend modes it must be non-transparent. + * @param blurSize The size of the bloom effect. If not specified the bloom effect will be the size of the component. + * @param offset The offset to use for the bloom effect. If not specified the bloom effect will be centered on the component. + * @param clipToSize The size to use for clipping the bloom effect. If not specified the bloom effect will not be clipped. + * @param bottomSoftEdgeColor The color to use for the bottom soft edge. If not specified the [background] color will be used. + * @param bottomSoftEdgeHeight The height of the bottom soft edge. If not specified the bottom soft edge will not be drawn. + * @param bottomSoftEdgeAlpha The alpha value to apply to the bottom soft edge. + * @param alpha The alpha value to apply to the bloom effect. + */ +fun Modifier.avatarBloom( + avatarData: AvatarData?, + background: Color, + blurSize: DpSize = DpSize.Unspecified, + offset: DpOffset = DpOffset.Unspecified, + clipToSize: DpSize = DpSize.Unspecified, + bottomSoftEdgeColor: Color = background, + bottomSoftEdgeHeight: Dp = 40.dp, + @FloatRange(from = 0.0, to = 1.0) + bottomSoftEdgeAlpha: Float = 1.0f, + @FloatRange(from = 0.0, to = 1.0) + alpha: Float = 1f, +) = composed { + // Bloom only works on API 29+ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return@composed this + avatarData ?: return@composed this + + // Request the avatar contents to use as the bloom source + val context = LocalContext.current + val density = LocalDensity.current + if (avatarData.url != null) { + val painterRequest = remember(avatarData) { + ImageRequest.Builder(context) + .data(avatarData) + // Allow cache and default dispatchers + .defaults(DefaultRequestOptions()) + // Needed to be able to read pixels from the Bitmap for the hash + .allowHardware(false) + // Reduce size so it loads faster for large avatars + .size(with(density) { Size(64.dp.roundToPx(), 64.dp.roundToPx()) }) + .build() + } + + // By making it saveable, we'll 'cache' the previous bloom effect until a new one is loaded + var blurHash by rememberSaveable(avatarData) { mutableStateOf(null) } + LaunchedEffect(avatarData) { + withContext(Dispatchers.IO) { + val drawable = + context.imageLoader.execute(painterRequest).drawable ?: return@withContext + val bitmap = (drawable as? BitmapDrawable)?.bitmap ?: return@withContext + blurHash = BlurHash.encode( + bitmap, + BloomDefaults.HASH_COMPONENTS, + BloomDefaults.HASH_COMPONENTS + ) + } + } + + bloom( + hash = blurHash, + background = background, + blurSize = blurSize, + offset = offset, + clipToSize = clipToSize, + bottomSoftEdgeHeight = bottomSoftEdgeHeight, + bottomSoftEdgeAlpha = bottomSoftEdgeAlpha, + alpha = alpha, + ) + } else { + // There is no URL so we'll generate an avatar with the initials and use that as the bloom source + val avatarColors = AvatarColorsProvider.provide(avatarData.id, ElementTheme.isLightTheme) + val initialsBitmap = initialsBitmap( + width = avatarData.size.dp, + height = avatarData.size.dp, + text = avatarData.initial, + textColor = avatarColors.foreground, + backgroundColor = avatarColors.background, + ) + val hash = remember(avatarData, avatarColors) { + BlurHash.encode(initialsBitmap.asAndroidBitmap(), BloomDefaults.HASH_COMPONENTS, BloomDefaults.HASH_COMPONENTS) + } + bloom( + hash = hash, + background = background, + blurSize = blurSize, + offset = offset, + clipToSize = clipToSize, + bottomSoftEdgeColor = bottomSoftEdgeColor, + bottomSoftEdgeHeight = bottomSoftEdgeHeight, + bottomSoftEdgeAlpha = bottomSoftEdgeAlpha, + alpha = alpha, + ) + } +} + +// Used to create a Bitmap version of the initials avatar +@Composable +private fun initialsBitmap( + text: String, + backgroundColor: Color, + textColor: Color, + width: Dp = 32.dp, + height: Dp = 32.dp, +): ImageBitmap = with(LocalDensity.current) { + val backgroundPaint = remember(backgroundColor) { + Paint().also { it.color = backgroundColor } + } + val resolver: FontFamily.Resolver = LocalFontFamilyResolver.current + val fontSize = remember { height.toSp() / 2 } + val typeface: Typeface = remember(resolver) { + resolver.resolve( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Bold, + fontStyle = FontStyle.Normal, + ) + }.value as Typeface + val textPaint = remember(textColor, typeface) { + TextPaint().apply { + color = textColor.toArgb() + textSize = fontSize.toPx() + this.typeface = typeface + } + } + val textMeasurer = rememberTextMeasurer() + val result = remember(text) { textMeasurer.measure(text, TextStyle.Default.copy(fontSize = fontSize)) } + val centerPx = remember(width, height) { IntOffset(width.roundToPx() / 2, height.roundToPx() / 2) } + remember(text, width, height, backgroundColor, textColor) { + val bitmap = Bitmap.createBitmap(width.roundToPx(), height.roundToPx(), Bitmap.Config.ARGB_8888).asImageBitmap() + androidx.compose.ui.graphics.Canvas(bitmap).also { canvas -> + canvas.drawCircle(centerPx.toOffset(), width.toPx() / 2, backgroundPaint) + canvas.nativeCanvas.drawText(text, centerPx.x.toFloat() - result.size.width/2, centerPx.y * 2f - result.size.height/2 - 4, textPaint) + } + bitmap + } +} + +// Translates DP sizes into pixel sizes, taking into account unspecified values +private fun DpSize.toIntSize(density: Density) = with(density) { + if (isSpecified) { + IntSize(width.roundToPx(), height.roundToPx()) + } else { + IntSize.Zero + } +} + +/** + * Helper to draw to a canvas using layers. This allows us to apply clipping to those layers only. + */ +fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) { + with(drawContext.canvas.nativeCanvas) { + val checkPoint = saveLayer(null, null) + block() + restoreToCount(checkPoint) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@DayNightPreviews +@ShowkaseComposable(group = PreviewGroup.Bloom) +@Composable +internal fun BloomPreview() { + val blurhash = "eePn{tI?xExEja}ooKWWodjtNJoKR,j@a|sBWpS3WDbGazoKWWWWj@" + var topAppBarHeight by remember { mutableIntStateOf(-1) } + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(topAppBarState) + ElementPreview { + Scaffold( + modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + Box { + MediumTopAppBar( + modifier = Modifier + .onSizeChanged { size -> + topAppBarHeight = size.height + } + .bloom( + hash = blurhash, + background = ElementTheme.materialColors.background, + blurSize = DpSize(430.dp, 430.dp), + offset = DpOffset(24.dp, 24.dp), + clipToSize = if (topAppBarHeight > 0) DpSize(430.dp, topAppBarHeight.toDp()) else DpSize.Zero, + ), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Black.copy(alpha = 0.05f), + ), + navigationIcon = { + Image( + modifier = Modifier + .padding(start = 8.dp) + .size(32.dp) + .clip(CircleShape), + painter = painterResource(id = R.drawable.sample_avatar), + contentScale = ContentScale.Crop, + contentDescription = null + ) + }, + actions = { + IconButton(onClick = {}) { + Icon(imageVector = Icons.Default.Share, contentDescription = null) + } + }, + title = { + Text("Title") + }, + scrollBehavior = scrollBehavior, + ) + } + }, + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .consumeWindowInsets(paddingValues) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { + repeat(20) { + Text("Content", modifier = Modifier.padding(vertical = 20.dp)) + } + } + } + } +} + +class InitialsColorStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf(0, 1, 2, 3, 4, 5, 6, 7) +} + +@DayNightPreviews +@Composable +@ShowkaseComposable(group = PreviewGroup.Bloom) +internal fun BloomInitialsPreview(@PreviewParameter(InitialsColorStateProvider::class) color: Int) { + ElementPreview { + val avatarColors = AvatarColorsProvider.provide("$color", ElementTheme.isLightTheme) + val bitmap = initialsBitmap(text = "F", backgroundColor = avatarColors.background, textColor = avatarColors.foreground) + val hash = BlurHash.encode(bitmap.asAndroidBitmap(), BloomDefaults.HASH_COMPONENTS, BloomDefaults.HASH_COMPONENTS) + Box( + modifier = Modifier.size(256.dp) + .bloom( + hash = hash, + background = if (ElementTheme.isLightTheme) { + // Workaround to display a very subtle bloom for avatars with very soft colors + Color(0xFFF9F9F9) + } else { + ElementTheme.materialColors.background + }, + bottomSoftEdgeColor = ElementTheme.materialColors.background, + blurSize = DpSize(256.dp, 256.dp), + ), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier + .size(32.dp) + .clip(CircleShape), + painter = BitmapPainter(bitmap), + contentDescription = null + ) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index 2e2d98ddbb..4ce4aa8f14 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -26,13 +26,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage +import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar @@ -87,20 +87,19 @@ private fun InitialsAvatar( avatarData: AvatarData, modifier: Modifier = Modifier, ) { - // Use temporary color for default avatar background - val avatarColor = ElementTheme.colors.bgActionPrimaryDisabled + val avatarColors = AvatarColorsProvider.provide(avatarData.id, ElementTheme.isLightTheme) Box( - modifier.background(color = avatarColor), + modifier.background(color = avatarColors.background) ) { val fontSize = avatarData.size.dp.toSp() / 2 - val originalFont = ElementTheme.typography.fontBodyMdRegular + val originalFont = ElementTheme.typography.fontHeadingMdBold val ratio = fontSize.value / originalFont.fontSize.value val lineHeight = originalFont.lineHeight * ratio Text( modifier = Modifier.align(Alignment.Center), text = avatarData.initial, style = originalFont.copy(fontSize = fontSize, lineHeight = lineHeight, letterSpacing = 0.sp), - color = Color.White, + color = avatarColors.foreground, ) } } 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 2438e5f017..38b1df6dce 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 @@ -20,7 +20,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp enum class AvatarSize(val dp: Dp) { - CurrentUserTopBar(28.dp), + CurrentUserTopBar(32.dp), RoomHeader(96.dp), RoomListItem(52.dp), @@ -42,4 +42,6 @@ enum class AvatarSize(val dp: Dp) { RoomInviteItem(52.dp), InviteSender(16.dp), + + NotificationsOptIn(32.dp), } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/UserAvatarPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/UserAvatarPreview.kt new file mode 100644 index 0000000000..ab1ec70db4 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/UserAvatarPreview.kt @@ -0,0 +1,50 @@ +/* + * 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.designsystem.components.avatar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.unit.dp +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.theme.colors.avatarColorsLight + +@DayNightPreviews +@Composable +internal fun UserAvatarPreview() = ElementPreview { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + repeat(avatarColorsLight.size) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + // Note: it's OK, since the hash of "0" is 0, the hash of "1" is 1, etc. + Avatar(anAvatarData(id = "$it")) + Text(text = "Color index $it") + } + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt index 8bfd198f1c..a1f458ee08 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.designsystem.preview object PreviewGroup { const val AppBars = "App Bars" const val Avatars = "Avatars" + const val Bloom = "Bloom" const val BottomSheets = "Bottom Sheets" const val Buttons = "Buttons" const val DateTimePickers = "DateTime pickers" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt index e126a429d8..16af6203ca 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.FloatingActionButtonDefaults @@ -38,7 +39,7 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup fun FloatingActionButton( onClick: () -> Unit, modifier: Modifier = Modifier, - shape: Shape = FloatingActionButtonDefaults.shape, + shape: Shape = CircleShape, // FloatingActionButtonDefaults.shape, containerColor: Color = FloatingActionButtonDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), diff --git a/libraries/designsystem/src/main/res/drawable/ic_bold.xml b/libraries/designsystem/src/main/res/drawable/ic_bold.xml new file mode 100644 index 0000000000..c361f85d3d --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_bold.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml b/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml new file mode 100644 index 0000000000..72d8324622 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_code_block.xml b/libraries/designsystem/src/main/res/drawable/ic_code_block.xml new file mode 100644 index 0000000000..6e622f5b27 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_code_block.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_edit_square.xml b/libraries/designsystem/src/main/res/drawable/ic_edit_square.xml index 73b092ea47..121486a4a2 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_edit_square.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_edit_square.xml @@ -15,12 +15,13 @@ --> - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml b/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml new file mode 100644 index 0000000000..5a0b284223 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml b/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml new file mode 100644 index 0000000000..367686ceac --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml b/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml new file mode 100644 index 0000000000..c0dc504ed9 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_italic.xml b/libraries/designsystem/src/main/res/drawable/ic_italic.xml new file mode 100644 index 0000000000..f640c109f4 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_italic.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_link.xml b/libraries/designsystem/src/main/res/drawable/ic_link.xml new file mode 100644 index 0000000000..fd69ec4703 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml b/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml new file mode 100644 index 0000000000..f4f5862656 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_poll.xml b/libraries/designsystem/src/main/res/drawable/ic_poll.xml new file mode 100644 index 0000000000..6167653c82 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_poll.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_poll_end.xml b/libraries/designsystem/src/main/res/drawable/ic_poll_end.xml new file mode 100644 index 0000000000..86c169cf41 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_poll_end.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_quote.xml b/libraries/designsystem/src/main/res/drawable/ic_quote.xml new file mode 100644 index 0000000000..e17565a6cc --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_quote.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_search.xml b/libraries/designsystem/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000000..440aef72f6 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_search.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml b/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml new file mode 100644 index 0000000000..d1994f8045 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_underline.xml b/libraries/designsystem/src/main/res/drawable/ic_underline.xml new file mode 100644 index 0000000000..09f92f2104 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_underline.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt new file mode 100644 index 0000000000..7b3392607d --- /dev/null +++ b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt @@ -0,0 +1,49 @@ +/* + * 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.designsystem.colors + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.theme.colors.avatarColorsDark +import io.element.android.libraries.theme.colors.avatarColorsLight +import org.junit.Test + +class AvatarColorsTest { + + @Test + fun `ensure the size of the avatar color are equal for light and dark theme`() { + assertThat(avatarColorsDark.size).isEqualTo(avatarColorsLight.size) + } + + @Test + fun `compute string hash`() { + assertThat("@alice:domain.org".toHash()).isEqualTo(6) + assertThat("@bob:domain.org".toHash()).isEqualTo(3) + assertThat("@charlie:domain.org".toHash()).isEqualTo(0) + } + + @Test + fun `compute string hash reverse`() { + assertThat("0".toHash()).isEqualTo(0) + assertThat("1".toHash()).isEqualTo(1) + assertThat("2".toHash()).isEqualTo(2) + assertThat("3".toHash()).isEqualTo(3) + assertThat("4".toHash()).isEqualTo(4) + assertThat("5".toHash()).isEqualTo(5) + assertThat("6".toHash()).isEqualTo(6) + assertThat("7".toHash()).isEqualTo(7) + } +} diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt index 59e224a1ae..8089c837ff 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt @@ -28,7 +28,8 @@ interface FeatureFlagService { * @param feature the feature to enable or disable * @param enabled true to enable the feature * - * @return true if the method succeeds, ie if a RuntimeFeatureFlagProvider is registered + * @return true if the method succeeds, ie if a [io.element.android.libraries.featureflag.impl.MutableFeatureFlagProvider] + * is registered */ suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean } 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 4eade84a67..c7744f486d 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 @@ -16,24 +16,32 @@ package io.element.android.libraries.featureflag.api +/** + * To enable or disable a FeatureFlags, change the `defaultValue` value. + * Warning: to enable a flag for the release app, you MUST update the file + * [io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider] + */ enum class FeatureFlags( override val key: String, override val title: String, override val description: String? = null, - override val defaultValue: Boolean = true + override val defaultValue: Boolean ) : Feature { LocationSharing( key = "feature.locationsharing", title = "Allow user to share location", + defaultValue = true, ), Polls( key = "feature.polls", title = "Polls", description = "Create poll and render poll events in the timeline", - defaultValue = false, + defaultValue = true, ), NotificationSettings( key = "feature.notificationsettings", title = "Show notification settings", - ) + // Do not forget to edit StaticFeatureFlagProvider when enabling the feature. + defaultValue = false, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt index 7298929aea..b445599693 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt @@ -38,7 +38,7 @@ class DefaultFeatureFlagService @Inject constructor( } override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean { - return providers.filterIsInstance(RuntimeFeatureFlagProvider::class.java) + return providers.filterIsInstance(MutableFeatureFlagProvider::class.java) .sortedBy(FeatureFlagProvider::priority) .firstOrNull() ?.setFeatureEnabled(feature, enabled) diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/RuntimeFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt similarity index 92% rename from libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/RuntimeFeatureFlagProvider.kt rename to libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt index 1238ad354c..7e2da181b6 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/RuntimeFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt @@ -18,6 +18,6 @@ package io.element.android.libraries.featureflag.impl import io.element.android.libraries.featureflag.api.Feature -interface RuntimeFeatureFlagProvider : FeatureFlagProvider { +interface MutableFeatureFlagProvider : FeatureFlagProvider { suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean) } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt index 15ab08b338..ddffdebd34 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt @@ -30,12 +30,13 @@ import javax.inject.Inject private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_featureflag") -class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext context: Context) : RuntimeFeatureFlagProvider { - +/** + * Note: this will be used only in the nightly and in the debug build. + */ +class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext context: Context) : MutableFeatureFlagProvider { private val store = context.dataStore - override val priority: Int - get() = MEDIUM_PRIORITY + override val priority = MEDIUM_PRIORITY override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean) { store.edit { prefs -> diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/BuildtimeFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt similarity index 80% rename from libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/BuildtimeFeatureFlagProvider.kt rename to libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 0117a4aa22..50efdb9fc3 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/BuildtimeFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -20,17 +20,20 @@ import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlags import javax.inject.Inject -class BuildtimeFeatureFlagProvider @Inject constructor() : +/** + * This provider is used for release build. + * This is the place to enable or disable feature for the release build. + */ +class StaticFeatureFlagProvider @Inject constructor() : FeatureFlagProvider { - override val priority: Int - get() = LOW_PRIORITY + override val priority = LOW_PRIORITY override suspend fun isFeatureEnabled(feature: Feature): Boolean { return if (feature is FeatureFlags) { - when (feature) { + when(feature) { FeatureFlags.LocationSharing -> true - FeatureFlags.Polls -> false + FeatureFlags.Polls -> true FeatureFlags.NotificationSettings -> false } } else { diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt index 07ee53ceee..b2f0a4106d 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt @@ -22,7 +22,7 @@ import dagger.Provides import dagger.multibindings.ElementsIntoSet import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.AppScope -import io.element.android.libraries.featureflag.impl.BuildtimeFeatureFlagProvider +import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider import io.element.android.libraries.featureflag.impl.FeatureFlagProvider import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider @@ -35,14 +35,18 @@ object FeatureFlagModule { @ElementsIntoSet fun providesFeatureFlagProvider( buildType: BuildType, - runtimeFeatureFlagProvider: PreferencesFeatureFlagProvider, - buildtimeFeatureFlagProvider: BuildtimeFeatureFlagProvider, + mutableFeatureFlagProvider: PreferencesFeatureFlagProvider, + staticFeatureFlagProvider: StaticFeatureFlagProvider, ): Set { val providers = HashSet() - if (buildType == BuildType.RELEASE) { - providers.add(buildtimeFeatureFlagProvider) - } else { - providers.add(runtimeFeatureFlagProvider) + when (buildType) { + BuildType.RELEASE -> { + providers.add(staticFeatureFlagProvider) + } + BuildType.NIGHTLY, + BuildType.DEBUG -> { + providers.add(mutableFeatureFlagProvider) + } } return providers } diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt index ea9b03acdf..890959639f 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt @@ -39,7 +39,7 @@ class DefaultFeatureFlagServiceTest { @Test fun `given service with a runtime provider when set enabled feature is called then it returns true`() = runTest { - val featureFlagProvider = FakeRuntimeFeatureFlagProvider(0) + val featureFlagProvider = FakeMutableFeatureFlagProvider(0) val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider)) val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(result).isEqualTo(true) @@ -47,7 +47,7 @@ class DefaultFeatureFlagServiceTest { @Test fun `given service with a runtime provider and feature enabled when feature is checked then it returns the correct value`() = runTest { - val featureFlagProvider = FakeRuntimeFeatureFlagProvider(0) + val featureFlagProvider = FakeMutableFeatureFlagProvider(0) val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider)) featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(true) @@ -57,11 +57,11 @@ class DefaultFeatureFlagServiceTest { @Test fun `given service with 2 runtime providers when feature is checked then it uses the priority correctly`() = runTest { - val lowPriorityfeatureFlagProvider = FakeRuntimeFeatureFlagProvider(LOW_PRIORITY) - val highPriorityfeatureFlagProvider = FakeRuntimeFeatureFlagProvider(HIGH_PRIORITY) - val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityfeatureFlagProvider, highPriorityfeatureFlagProvider)) - lowPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, false) - highPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, true) + val lowPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(LOW_PRIORITY) + val highPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(HIGH_PRIORITY) + val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider)) + lowPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, false) + highPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(true) } } diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeRuntimeFeatureFlagProvider.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt similarity index 92% rename from libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeRuntimeFeatureFlagProvider.kt rename to libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt index 5ff5cf932f..f1d075ace2 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeRuntimeFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt @@ -18,7 +18,7 @@ package io.element.android.libraries.featureflag.impl import io.element.android.libraries.featureflag.api.Feature -class FakeRuntimeFeatureFlagProvider(override val priority: Int) : RuntimeFeatureFlagProvider { +class FakeMutableFeatureFlagProvider(override val priority: Int) : MutableFeatureFlagProvider { private val enabledFeatures = HashMap() 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 1f90913ef4..47f13e95b9 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 @@ -85,11 +85,11 @@ interface MatrixRoom : Closeable { suspend fun userAvatarUrl(userId: UserId): Result - suspend fun sendMessage(message: String): Result + suspend fun sendMessage(body: String, htmlBody: String): Result - suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, message: String): Result + suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String): Result - suspend fun replyMessage(eventId: EventId, message: String): Result + suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String): 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/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 9ae6c22e7d..de7f1f3918 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -32,6 +32,11 @@ interface RoomListService { data object Terminated : State() } + sealed class SyncIndicator { + data object Show : SyncIndicator() + data object Hide : SyncIndicator() + } + /** * returns a [RoomList] object of all rooms we want to display. * This will exclude some rooms like the invites, or spaces. @@ -49,6 +54,11 @@ interface RoomListService { */ fun updateAllRoomsVisibleRange(range: IntRange) + /** + * The sync indicator as a flow. + */ + val syncIndicator: StateFlow + /** * The state of the service as a flow. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt index 596b611296..3062fa3aa3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt @@ -19,24 +19,27 @@ package io.element.android.libraries.matrix.api.tracing data class TracingFilterConfiguration( val overrides: Map = emptyMap(), ) { + private val defaultLogLevel = LogLevel.INFO // Order should matters - private val targetsToLogLevel: MutableMap = mutableMapOf( - Target.COMMON to LogLevel.Info, - Target.HYPER to LogLevel.Warn, - Target.MATRIX_SDK_CRYPTO to LogLevel.Debug, - Target.MATRIX_SDK_HTTP_CLIENT to LogLevel.Debug, - Target.MATRIX_SDK_SLIDING_SYNC to LogLevel.Trace, - Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.Trace, - Target.MATRIX_SDK_UI_TIMELINE to LogLevel.Info, + private val targetsToLogLevel: Map = mapOf( + Target.HYPER to LogLevel.WARN, + Target.MATRIX_SDK_CRYPTO to LogLevel.DEBUG, + Target.MATRIX_SDK_HTTP_CLIENT to LogLevel.DEBUG, + Target.MATRIX_SDK_SLIDING_SYNC to LogLevel.TRACE, + Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.TRACE, ) + fun getLogLevel(target: Target): LogLevel { + return overrides[target] ?: targetsToLogLevel[target] ?: defaultLogLevel + } + val filter: String get() { - overrides.forEach { (target, logLevel) -> - targetsToLogLevel[target] = logLevel + val fullMap = Target.values().associateWith { + overrides[it] ?: targetsToLogLevel[it] ?: defaultLogLevel } - return targetsToLogLevel.map { + return fullMap.map { if (it.key.filter.isEmpty()) { it.value.filter } else { @@ -53,31 +56,32 @@ enum class Target(open val filter: String) { MATRIX_SDK_FFI("matrix_sdk_ffi"), MATRIX_SDK_UNIFFI_API("matrix_sdk_ffi::uniffi_api"), MATRIX_SDK_CRYPTO("matrix_sdk_crypto"), + MATRIX_SDK("matrix_sdk"), MATRIX_SDK_HTTP_CLIENT("matrix_sdk::http_client"), + MATRIX_SDK_CLIENT("matrix_sdk::client"), + MATRIX_SDK_OIDC("matrix_sdk::oidc"), MATRIX_SDK_SLIDING_SYNC("matrix_sdk::sliding_sync"), MATRIX_SDK_BASE_SLIDING_SYNC("matrix_sdk_base::sliding_sync"), MATRIX_SDK_UI_TIMELINE("matrix_sdk_ui::timeline"), } -sealed class LogLevel(val filter: String) { - data object Warn : LogLevel("warn") - data object Trace : LogLevel("trace") - data object Info : LogLevel("info") - data object Debug : LogLevel("debug") - data object Error : LogLevel("error") +enum class LogLevel(open val filter: String) { + ERROR("error"), + WARN("warn"), + INFO("info"), + DEBUG("debug"), + TRACE("trace"), } object TracingFilterConfigurations { val release = TracingFilterConfiguration( overrides = mapOf( - Target.COMMON to LogLevel.Info, - Target.ELEMENT to LogLevel.Debug + Target.ELEMENT to LogLevel.DEBUG ), ) val debug = TracingFilterConfiguration( overrides = mapOf( - Target.COMMON to LogLevel.Info, - Target.ELEMENT to LogLevel.Trace + Target.ELEMENT to LogLevel.TRACE ) ) diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index b2a21f26df..a2b616f989 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.network) implementation(projects.services.toolbox.api) + implementation(projects.libraries.featureflag.api) api(projects.libraries.matrix.api) implementation(libs.dagger) implementation(projects.libraries.core) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 408e0aeef7..090a0cf613 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -67,6 +67,7 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate +import org.matrix.rustcomponents.sdk.NotificationProcessSetup import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.use @@ -100,9 +101,13 @@ class RustMatrixClient constructor( client = client, dispatchers = dispatchers, ) - private val notificationClient = client.notificationClient().use { builder -> - builder.filterByPushRules().finish() - } + private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(syncService) + private val notificationClient = client.notificationClient(notificationProcessSetup) + .use { builder -> + builder + .filterByPushRules() + .finish() + } private val notificationSettings = client.getNotificationSettings() private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock) @@ -288,6 +293,7 @@ class RustMatrixClient constructor( syncService.destroy() innerRoomListService.destroy() notificationClient.destroy() + notificationProcessSetup.destroy() client.destroy() } @@ -325,6 +331,7 @@ class RustMatrixClient constructor( client.accountUrl() } } + override suspend fun loadUserDisplayName(): Result = withContext(sessionDispatcher) { runCatching { client.displayName() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index bb80032230..b37266342e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -53,7 +53,8 @@ class RustMatrixClientFactory @Inject constructor( client.restoreSession(sessionData.toSession()) - val syncService = client.syncService().finish() + val syncService = client.syncService() + .finish() RustMatrixClient( client = client, 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 53352ae640..b58e52e026 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 @@ -66,7 +66,7 @@ import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle import org.matrix.rustcomponents.sdk.genTransactionId -import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown +import org.matrix.rustcomponents.sdk.messageEventContentFromHtml import timber.log.Timber import java.io.File @@ -227,31 +227,32 @@ class RustMatrixRoom( } } - override suspend fun sendMessage(message: String): Result = withContext(roomDispatcher) { + override suspend fun sendMessage(body: String, htmlBody: String): Result = withContext(roomDispatcher) { val transactionId = genTransactionId() - messageEventContentFromMarkdown(message).use { content -> + messageEventContentFromHtml(body, htmlBody).use { content -> runCatching { innerRoom.send(content, transactionId) } } } - override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, message: String): Result = withContext(roomDispatcher) { - if (originalEventId != null) { - runCatching { - innerRoom.edit(messageEventContentFromMarkdown(message), originalEventId.value, transactionId?.value) - } - } else { - runCatching { - transactionId?.let { cancelSend(it) } - innerRoom.send(messageEventContentFromMarkdown(message), genTransactionId()) + override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String): Result = + withContext(roomDispatcher) { + if (originalEventId != null) { + runCatching { + innerRoom.edit(messageEventContentFromHtml(body, htmlBody), originalEventId.value, transactionId?.value) + } + } else { + runCatching { + transactionId?.let { cancelSend(it) } + innerRoom.send(messageEventContentFromHtml(body, htmlBody), genTransactionId()) + } } } - } - override suspend fun replyMessage(eventId: EventId, message: String): Result = withContext(roomDispatcher) { + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String): Result = withContext(roomDispatcher) { runCatching { - innerRoom.sendReply(messageEventContentFromMarkdown(message), eventId.value, genTransactionId()) + innerRoom.sendReply(messageEventContentFromHtml(body, htmlBody), eventId.value, genTransactionId()) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index 8d96990a9e..1e599a184d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -33,6 +33,8 @@ import org.matrix.rustcomponents.sdk.RoomListLoadingStateListener import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.RoomListServiceState import org.matrix.rustcomponents.sdk.RoomListServiceStateListener +import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator +import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener import timber.log.Timber fun RoomList.loadingStateFlow(): Flow = @@ -83,6 +85,18 @@ fun RoomListService.stateFlow(): Flow = } }.buffer(Channel.UNLIMITED) +fun RoomListService.syncIndicator(): Flow = + mxCallbackFlow { + val listener = object : RoomListServiceSyncIndicatorListener { + override fun onUpdate(syncIndicator: RoomListServiceSyncIndicator) { + trySendBlocking(syncIndicator) + } + } + tryOrNull { + syncIndicator(listener) + } + }.buffer(Channel.UNLIMITED) + fun RoomListService.roomOrNull(roomId: String): RoomListItem? { return try { room(roomId) 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 b57eb892e0..d33e2f58fc 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 @@ -20,25 +20,26 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.impl.room.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory -import org.matrix.rustcomponents.sdk.Room -import org.matrix.rustcomponents.sdk.RoomListItem +import org.matrix.rustcomponents.sdk.RoomInfo +import org.matrix.rustcomponents.sdk.use class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { - suspend fun create(roomListItem: RoomListItem, room: Room?): RoomSummaryDetails { - val latestRoomMessage = roomListItem.latestEvent()?.use { + fun create(roomInfo: RoomInfo): RoomSummaryDetails { + val latestRoomMessage = roomInfo.latestEvent?.use { roomMessageFactory.create(it) } return RoomSummaryDetails( - roomId = RoomId(roomListItem.id()), - name = roomListItem.name() ?: roomListItem.id(), - canonicalAlias = roomListItem.canonicalAlias(), - isDirect = roomListItem.isDirect(), - avatarURLString = roomListItem.avatarUrl(), - unreadNotificationCount = roomListItem.unreadNotifications().use { it.notificationCount().toInt() }, + roomId = RoomId(roomInfo.id), + name = roomInfo.name ?: roomInfo.id, + canonicalAlias = roomInfo.canonicalAlias, + isDirect = roomInfo.isDirect, + avatarURLString = roomInfo.avatarUrl, + unreadNotificationCount = roomInfo.notificationCount.toInt(), lastMessage = latestRoomMessage, lastMessageTimestamp = latestRoomMessage?.originServerTs, - inviter = room?.inviter()?.let(RoomMemberMapper::map), + inviter = roomInfo.inviter?.let(RoomMemberMapper::map), ) } } + diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index 9a67ff1f30..03b58ed2ce 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -22,11 +22,10 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate import org.matrix.rustcomponents.sdk.RoomListEntry -import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListService +import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.util.UUID @@ -34,7 +33,6 @@ class RoomSummaryListProcessor( private val roomSummaries: MutableStateFlow>, private val roomListService: RoomListService, private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), - private val shouldFetchFullRoom: Boolean = false, ) { private val roomSummariesByIdentifier = HashMap() @@ -120,9 +118,9 @@ class RoomSummaryListProcessor( private suspend fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary { val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem -> - roomListItem.fullRoomOrNull().use { fullRoom -> + roomListItem.roomInfo().use { roomInfo -> RoomSummary.Filled( - details = roomSummaryDetailsFactory.create(roomListItem, fullRoom) + details = roomSummaryDetailsFactory.create(roomInfo) ) } } ?: buildEmptyRoomSummary() @@ -130,14 +128,6 @@ class RoomSummaryListProcessor( return builtRoomSummary } - private fun RoomListItem.fullRoomOrNull(): Room? { - return if (shouldFetchFullRoom) { - fullRoom() - } else { - null - } - } - private suspend fun updateRoomSummaries(block: suspend MutableList.() -> Unit) = mutex.withLock { val mutableRoomSummaries = roomSummaries.value.toMutableList() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index f56e55d759..a2de2be89b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -38,6 +38,7 @@ import org.matrix.rustcomponents.sdk.RoomListInput import org.matrix.rustcomponents.sdk.RoomListLoadingState import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListServiceState +import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator import timber.log.Timber import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService @@ -52,9 +53,9 @@ class RustRoomListService( private val inviteRooms = MutableStateFlow>(emptyList()) private val allRoomsLoadingState: MutableStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) - private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, innerRoomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = false) + private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, innerRoomListService, roomSummaryDetailsFactory) private val invitesLoadingState: MutableStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) - private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, innerRoomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = true) + private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, innerRoomListService, roomSummaryDetailsFactory) init { sessionCoroutineScope.launch(dispatcher) { @@ -106,6 +107,15 @@ class RustRoomListService( } } + override val syncIndicator: StateFlow = + innerRoomListService.syncIndicator() + .map { it.toSyncIndicator() } + .onEach { syncIndicator -> + Timber.d("SyncIndicator = $syncIndicator") + } + .distinctUntilChanged() + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, RoomListService.SyncIndicator.Hide) + override val state: StateFlow = innerRoomListService.stateFlow() .map { it.toRoomListState() } @@ -126,6 +136,7 @@ private fun RoomListLoadingState.toLoadingState(): RoomList.LoadingState { private fun RoomListServiceState.toRoomListState(): RoomListService.State { return when (this) { RoomListServiceState.INITIAL, + RoomListServiceState.RECOVERING, RoomListServiceState.SETTING_UP -> RoomListService.State.Idle RoomListServiceState.RUNNING -> RoomListService.State.Running RoomListServiceState.ERROR -> RoomListService.State.Error @@ -133,6 +144,13 @@ private fun RoomListServiceState.toRoomListState(): RoomListService.State { } } +private fun RoomListServiceSyncIndicator.toSyncIndicator(): RoomListService.SyncIndicator { + return when (this) { + RoomListServiceSyncIndicator.SHOW -> RoomListService.SyncIndicator.Show + RoomListServiceSyncIndicator.HIDE -> RoomListService.SyncIndicator.Hide + } +} + private fun org.matrix.rustcomponents.sdk.RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow> { return entriesFlow { roomListEntries -> processor.postEntries(roomListEntries) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 22f8a062f0..01dd32c048 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -34,85 +34,92 @@ import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.poll.map import org.matrix.rustcomponents.sdk.TimelineItemContent import org.matrix.rustcomponents.sdk.TimelineItemContentKind +import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange import org.matrix.rustcomponents.sdk.OtherState as RustOtherState class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) { - fun map(content: TimelineItemContent): EventContent = content.use { - when (val kind = it.kind()) { - is TimelineItemContentKind.FailedToParseMessageLike -> { - FailedToParseMessageLikeContent( - eventType = kind.eventType, - error = kind.error - ) + fun map(content: TimelineItemContent): EventContent { + return content.use { + content.kind().use { kind -> + map(content, kind) } - is TimelineItemContentKind.FailedToParseState -> { - FailedToParseStateContent( - eventType = kind.eventType, - stateKey = kind.stateKey, - error = kind.error - ) - } - TimelineItemContentKind.Message -> { - val message = it.asMessage() - if (message == null) { - UnknownContent - } else { - eventMessageMapper.map(message) - } - } - is TimelineItemContentKind.ProfileChange -> { - ProfileChangeContent( - displayName = kind.displayName, - prevDisplayName = kind.prevDisplayName, - avatarUrl = kind.avatarUrl, - prevAvatarUrl = kind.prevAvatarUrl - ) - } - TimelineItemContentKind.RedactedMessage -> { - RedactedContent - } - is TimelineItemContentKind.RoomMembership -> { - RoomMembershipContent( - UserId(kind.userId), - kind.change?.map() - ) - } - is TimelineItemContentKind.State -> { - StateContent( - stateKey = kind.stateKey, - content = kind.content.map() - ) - } - is TimelineItemContentKind.Sticker -> { - StickerContent( - body = kind.body, - info = kind.info.map(), - url = kind.url, - ) - } - is TimelineItemContentKind.Poll -> { - PollContent( - question = kind.question, - kind = kind.kind.map(), - maxSelections = kind.maxSelections, - answers = kind.answers.map { answer -> answer.map() }, - votes = kind.votes.mapValues { vote -> - vote.value.map { userId -> UserId(userId) } - }, - endTime = kind.endTime, - ) - } - is TimelineItemContentKind.UnableToDecrypt -> { - UnableToDecryptContent( - data = kind.msg.map() - ) - } - else -> UnknownContent } } + + private fun map(content: TimelineItemContent, kind: TimelineItemContentKind) = when (kind) { + is TimelineItemContentKind.FailedToParseMessageLike -> { + FailedToParseMessageLikeContent( + eventType = kind.eventType, + error = kind.error + ) + } + is TimelineItemContentKind.FailedToParseState -> { + FailedToParseStateContent( + eventType = kind.eventType, + stateKey = kind.stateKey, + error = kind.error + ) + } + TimelineItemContentKind.Message -> { + val message = content.asMessage() + if (message == null) { + UnknownContent + } else { + eventMessageMapper.map(message) + } + } + is TimelineItemContentKind.ProfileChange -> { + ProfileChangeContent( + displayName = kind.displayName, + prevDisplayName = kind.prevDisplayName, + avatarUrl = kind.avatarUrl, + prevAvatarUrl = kind.prevAvatarUrl + ) + } + TimelineItemContentKind.RedactedMessage -> { + RedactedContent + } + is TimelineItemContentKind.RoomMembership -> { + RoomMembershipContent( + UserId(kind.userId), + kind.change?.map() + ) + } + is TimelineItemContentKind.State -> { + StateContent( + stateKey = kind.stateKey, + content = kind.content.map() + ) + } + is TimelineItemContentKind.Sticker -> { + StickerContent( + body = kind.body, + info = kind.info.map(), + url = kind.url, + ) + } + is TimelineItemContentKind.Poll -> { + PollContent( + question = kind.question, + kind = kind.kind.map(), + maxSelections = kind.maxSelections, + answers = kind.answers.map { answer -> answer.map() }, + votes = kind.votes.mapValues { vote -> + vote.value.map { userId -> UserId(userId) } + }, + endTime = kind.endTime, + ) + } + is TimelineItemContentKind.UnableToDecrypt -> { + UnableToDecryptContent( + data = kind.msg.map() + ) + } + else -> UnknownContent + } } private fun RustMembershipChange.map(): MembershipChange { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index ccf5cd4155..18c9f00e38 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -21,47 +21,45 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE -import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_SETTINGS import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow -class FakeNotificationSettingsService : NotificationSettingsService { +class FakeNotificationSettingsService( + initialMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE, + initialDefaultMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE +) : NotificationSettingsService { private var _roomNotificationSettingsStateFlow = MutableStateFlow(Unit) - private val muteRoomResult: Result = Result.success(Unit) - private val unmuteRoomResult: Result = Result.success(Unit) - private val setRoomNotificationMode: Result = Result.success(Unit) - private val restoreDefaultRoomNotificationMode: Result = Result.success(Unit) - private val getRoomNotificationSettingsResult: Result = Result.success(A_ROOM_NOTIFICATION_SETTINGS) - private val getDefaultRoomNotificationMode: Result = Result.success(A_ROOM_NOTIFICATION_MODE) + private var defaultRoomNotificationMode: RoomNotificationMode = initialDefaultMode + private var roomNotificationMode: RoomNotificationMode = initialMode override val notificationSettingsChangeFlow: SharedFlow get() = _roomNotificationSettingsStateFlow - override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result { - return getRoomNotificationSettingsResult + override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result { + return Result.success(RoomNotificationSettings(mode = roomNotificationMode, isDefault = roomNotificationMode == defaultRoomNotificationMode)) } - override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result { - return getDefaultRoomNotificationMode - } - - override suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result { - TODO("Not yet implemented") + override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, membersCount: Long): Result { + return Result.success(defaultRoomNotificationMode) } override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result { - return setRoomNotificationMode + roomNotificationMode = mode + _roomNotificationSettingsStateFlow.emit(Unit) + return Result.success(Unit) } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result { - return restoreDefaultRoomNotificationMode + roomNotificationMode = defaultRoomNotificationMode + _roomNotificationSettingsStateFlow.emit(Unit) + return Result.success(Unit) } override suspend fun muteRoom(roomId: RoomId): Result { - return muteRoomResult + return setRoomNotificationMode(roomId, RoomNotificationMode.MUTE) } - override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result { - return unmuteRoomResult + override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result { + return restoreDefaultRoomNotificationMode(roomId) } override suspend fun isRoomMentionEnabled(): Result { @@ -79,5 +77,4 @@ class FakeNotificationSettingsService : NotificationSettingsService { override suspend fun setCallEnabled(enabled: Boolean): Result { 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 c7a17e0134..d53b8e2c92 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 @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState @@ -38,6 +39,7 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler +import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.delay @@ -59,6 +61,7 @@ class FakeMatrixRoom( override val isDirect: Boolean = false, override val joinedMemberCount: Long = 123L, override val activeMemberCount: Long = 234L, + val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), canRedact: Boolean = false, ) : MatrixRoom { @@ -90,7 +93,7 @@ class FakeMatrixRoom( private var sendPollResponseResult = Result.success(Unit) private var endPollResult = Result.success(Unit) private var progressCallbackValues = emptyList>() - val editMessageCalls = mutableListOf() + val editMessageCalls = mutableListOf>() var sendMediaCount = 0 private set @@ -146,7 +149,9 @@ class FakeMatrixRoom( } override suspend fun updateRoomNotificationSettings(): Result = simulateLongTask { - updateRoomNotificationSettingsResult + val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, activeMemberCount).getOrThrow() + roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings) + return Result.success(Unit) } override val syncUpdateFlow: StateFlow = MutableStateFlow(0L) @@ -167,7 +172,7 @@ class FakeMatrixRoom( userAvatarUrlResult } - override suspend fun sendMessage(message: String): Result = simulateLongTask { + override suspend fun sendMessage(body: String, htmlBody: String) = simulateLongTask { Result.success(Unit) } @@ -196,16 +201,16 @@ class FakeMatrixRoom( return cancelSendResult } - override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, message: String): Result { - editMessageCalls += message + override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String): Result { + editMessageCalls += body to htmlBody return Result.success(Unit) } - var replyMessageParameter: String? = null + var replyMessageParameter: Pair? = null private set - override suspend fun replyMessage(eventId: EventId, message: String): Result { - replyMessageParameter = message + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String): Result { + replyMessageParameter = body to htmlBody return Result.success(Unit) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index fa2e347e3b..7e0f3e8891 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -29,6 +29,7 @@ class FakeRoomListService : RoomListService { private val allRoomsLoadingStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) private val inviteRoomsLoadingStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) private val roomListStateFlow = MutableStateFlow(RoomListService.State.Idle) + private val syncIndicatorStateFlow = MutableStateFlow(RoomListService.SyncIndicator.Hide) suspend fun postAllRooms(roomSummaries: List) { allRoomSummariesFlow.emit(roomSummaries) @@ -72,4 +73,6 @@ class FakeRoomListService : RoomListService { } override val state: StateFlow = roomListStateFlow + + override val syncIndicator: StateFlow = syncIndicatorStateFlow } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt index 8b421c6c28..8945c46817 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.ui.media +import android.content.Context import coil.ImageLoader import coil.decode.DataSource import coil.decode.ImageSource @@ -32,8 +33,10 @@ import okio.Buffer import okio.Path.Companion.toOkioPath import timber.log.Timber import java.nio.ByteBuffer +import kotlin.math.roundToLong internal class CoilMediaFetcher( + private val scalingFunction: (Float) -> Float, private val mediaLoader: MatrixMediaLoader, private val mediaData: MediaRequestData?, private val options: Options @@ -80,8 +83,8 @@ internal class CoilMediaFetcher( private suspend fun fetchThumbnail(mediaSource: MediaSource, kind: MediaRequestData.Kind.Thumbnail, options: Options): FetchResult? { return mediaLoader.loadMediaThumbnail( source = mediaSource, - width = kind.width, - height = kind.height + width = scalingFunction(kind.width.toFloat()).roundToLong(), + height = scalingFunction(kind.height.toFloat()).roundToLong(), ).map { byteArray -> byteArray.asSourceResult(options) }.getOrNull() @@ -102,6 +105,7 @@ internal class CoilMediaFetcher( } class MediaRequestDataFactory( + private val context: Context, private val client: MatrixClient ) : Fetcher.Factory { @@ -111,6 +115,7 @@ internal class CoilMediaFetcher( imageLoader: ImageLoader ): Fetcher { return CoilMediaFetcher( + scalingFunction = { context.resources.displayMetrics.density * it }, mediaLoader = client.mediaLoader, mediaData = data, options = options @@ -119,6 +124,7 @@ internal class CoilMediaFetcher( } class AvatarFactory( + private val context: Context, private val client: MatrixClient ) : Fetcher.Factory { @@ -129,6 +135,7 @@ internal class CoilMediaFetcher( imageLoader: ImageLoader ): Fetcher { return CoilMediaFetcher( + scalingFunction = { context.resources.displayMetrics.density * it }, mediaLoader = client.mediaLoader, mediaData = data.toMediaRequestData(), options = options diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt index c5b7f1ed44..c495558fd9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt @@ -46,8 +46,8 @@ class LoggedInImageLoaderFactory @Inject constructor( } add(AvatarDataKeyer()) add(MediaRequestDataKeyer()) - add(CoilMediaFetcher.AvatarFactory(matrixClient)) - add(CoilMediaFetcher.MediaRequestDataFactory(matrixClient)) + add(CoilMediaFetcher.AvatarFactory(context, matrixClient)) + add(CoilMediaFetcher.MediaRequestDataFactory(context, matrixClient)) } .build() } diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt index 8ff40fae39..9fc160252b 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt @@ -119,10 +119,17 @@ class AndroidMediaPreProcessor @Inject constructor( private suspend fun processImage(uri: Uri, mimeType: String, shouldBeCompressed: Boolean): MediaUploadInfo { suspend fun processImageWithCompression(): MediaUploadInfo { + // Read the orientation metadata from its own stream. Trying to reuse this stream for compression will fail. + val orientation = contentResolver.openInputStream(uri).use { input -> + val exifInterface = input?.let { ExifInterface(it) } + exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) + } ?: ExifInterface.ORIENTATION_UNDEFINED + val compressionResult = contentResolver.openInputStream(uri).use { input -> imageCompressor.compressToTmpFile( inputStream = requireNotNull(input), resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE), + orientation = orientation, ).getOrThrow() } val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt index ab30f67b65..a619a27bd9 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.mediaupload import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import androidx.exifinterface.media.ExifInterface import io.element.android.libraries.androidutils.bitmap.calculateInSampleSize import io.element.android.libraries.androidutils.bitmap.resizeToMax import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation @@ -37,17 +38,18 @@ class ImageCompressor @Inject constructor( /** * Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode], then writes it into a - * temporary file using the passed [format] and [desiredQuality]. + * temporary file using the passed [format], [orientation] and [desiredQuality]. * @return a [Result] containing the resulting [ImageCompressionResult] with the temporary [File] and some metadata. */ suspend fun compressToTmpFile( inputStream: InputStream, resizeMode: ResizeMode, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, + orientation: Int = ExifInterface.ORIENTATION_UNDEFINED, desiredQuality: Int = 80, ): Result = withContext(Dispatchers.IO) { runCatching { - val compressedBitmap = compressToBitmap(inputStream, resizeMode).getOrThrow() + val compressedBitmap = compressToBitmap(inputStream, resizeMode, orientation).getOrThrow() // Encode bitmap to the destination temporary file val tmpFile = context.createTmpFile(extension = "jpeg") tmpFile.outputStream().use { @@ -63,19 +65,20 @@ class ImageCompressor @Inject constructor( } /** - * Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode]. + * Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode] and [orientation]. * @return a [Result] containing the resulting [Bitmap]. */ fun compressToBitmap( inputStream: InputStream, resizeMode: ResizeMode, + orientation: Int, ): Result = runCatching { BufferedInputStream(inputStream).use { input -> val options = BitmapFactory.Options() calculateDecodingScale(input, resizeMode, options) val decodedBitmap = BitmapFactory.decodeStream(input, null, options) ?: error("Decoding Bitmap from InputStream failed") - val rotatedBitmap = decodedBitmap.rotateToMetadataOrientation(input).getOrThrow() + val rotatedBitmap = decodedBitmap.rotateToMetadataOrientation(orientation) if (resizeMode is ResizeMode.Strict) { rotatedBitmap.resizeToMax(resizeMode.maxWidth, resizeMode.maxHeight) } else { diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt new file mode 100644 index 0000000000..8ea50b4ada --- /dev/null +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt @@ -0,0 +1,30 @@ +/* + * 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.permissions.api + +import kotlinx.coroutines.flow.Flow + +interface PermissionStateProvider { + fun isPermissionGranted(permission: String): Boolean + suspend fun setPermissionDenied(permission: String, value: Boolean) + fun isPermissionDenied(permission: String): Flow + + suspend fun setPermissionAsked(permission: String, value: Boolean) + fun isPermissionAsked(permission: String): Flow + + suspend fun resetPermission(permission: String) +} diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/PermissionsStore.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt similarity index 95% rename from libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/PermissionsStore.kt rename to libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt index 25b41e2a71..be16dc74e5 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/PermissionsStore.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.permissions.impl +package io.element.android.libraries.permissions.api import kotlinx.coroutines.flow.Flow diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index 9808986f8f..7d05d9a1d7 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -56,6 +56,8 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.permissions.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt index 15acd868f2..02b4dd535c 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt @@ -26,13 +26,13 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import javax.inject.Inject -interface PermissionStateProvider { +interface ComposablePermissionStateProvider { @Composable fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState } @ContributesBinding(AppScope::class) -class AccompanistPermissionStateProvider @Inject constructor() : PermissionStateProvider { +class AccompanistPermissionStateProvider @Inject constructor() : ComposablePermissionStateProvider { @Composable override fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState { return rememberPermissionState( diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt new file mode 100644 index 0000000000..86cc646982 --- /dev/null +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt @@ -0,0 +1,48 @@ +/* + * 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.permissions.impl + +import android.content.Context +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.permissions.api.PermissionStateProvider +import io.element.android.libraries.permissions.api.PermissionsStore +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultPermissionStateProvider @Inject constructor( + @ApplicationContext private val context: Context, + private val permissionsStore: PermissionsStore, +): PermissionStateProvider { + override fun isPermissionGranted(permission: String): Boolean { + return context.checkSelfPermission(permission) == android.content.pm.PackageManager.PERMISSION_GRANTED + } + + override suspend fun setPermissionDenied(permission: String, value: Boolean) = permissionsStore.setPermissionDenied(permission, value) + + override fun isPermissionDenied(permission: String): Flow = permissionsStore.isPermissionDenied(permission) + + override suspend fun setPermissionAsked(permission: String, value: Boolean) = permissionsStore.setPermissionAsked(permission, value) + + override fun isPermissionAsked(permission: String): Flow = permissionsStore.isPermissionAsked(permission) + + override suspend fun resetPermission(permission: String) = permissionsStore.resetPermission(permission) +} diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt index 50012f01b7..1684833d70 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsState +import io.element.android.libraries.permissions.api.PermissionsStore import kotlinx.coroutines.launch import timber.log.Timber @@ -46,7 +47,7 @@ private val loggerTag = LoggerTag("DefaultPermissionsPresenter") class DefaultPermissionsPresenter @AssistedInject constructor( @Assisted val permission: String, private val permissionsStore: PermissionsStore, - private val permissionStateProvider: PermissionStateProvider, + private val composablePermissionStateProvider: ComposablePermissionStateProvider, ) : PermissionsPresenter { @AssistedFactory @@ -90,7 +91,7 @@ class DefaultPermissionsPresenter @AssistedInject constructor( } } - permissionState = permissionStateProvider.provide( + permissionState = composablePermissionStateProvider.provide( permission = permission, onPermissionResult = ::onPermissionResult ) @@ -123,10 +124,8 @@ class DefaultPermissionsPresenter @AssistedInject constructor( showDialog = showDialog.value, permissionAlreadyAsked = isAlreadyAsked, permissionAlreadyDenied = isAlreadyDenied, - eventSink = ::handleEvents - ).also { - Timber.tag(loggerTag.value).d("New state: $it") - } + eventSink = { handleEvents(it) } + ) } /* diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt index 9ee29b7a61..49528da329 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt @@ -26,6 +26,7 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.permissions.api.PermissionsStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -34,7 +35,7 @@ private val Context.dataStore: DataStore by preferencesDataStore(na @ContributesBinding(AppScope::class) class DefaultPermissionsStore @Inject constructor( - @ApplicationContext context: Context, + @ApplicationContext private val context: Context, ) : PermissionsStore { private val store = context.dataStore diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt new file mode 100644 index 0000000000..364f8072a1 --- /dev/null +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.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.libraries.permissions.impl + +import io.element.android.libraries.permissions.api.PermissionStateProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakePermissionStateProvider( + private var permissionGranted: Boolean = true, + permissionDenied: Boolean = false, + permissionAsked: Boolean = false, +): PermissionStateProvider { + private val permissionDeniedFlow = MutableStateFlow(permissionDenied) + private val permissionAskedFlow = MutableStateFlow(permissionAsked) + + fun setPermissionGranted() { + permissionGranted = true + } + + override fun isPermissionGranted(permission: String): Boolean = permissionGranted + + override suspend fun setPermissionDenied(permission: String, value: Boolean) { + permissionDeniedFlow.value = value + } + + override fun isPermissionDenied(permission: String): Flow = permissionDeniedFlow + + override suspend fun setPermissionAsked(permission: String, value: Boolean) { + permissionAskedFlow.value = value + } + + override fun isPermissionAsked(permission: String): Flow = permissionAskedFlow + + override suspend fun resetPermission(permission: String) { + setPermissionAsked(permission, false) + setPermissionDenied(permission, false) + } +} diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt index f501bcee8a..c25e8072e7 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt @@ -25,17 +25,31 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.common.truth.Truth.assertThat import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.test.InMemoryPermissionsStore +import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test const val A_PERMISSION = "A_PERMISSION" class DefaultPermissionsPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Granted) - val permissionStateProvider = FakePermissionStateProvider(permissionState) + val permissionState = FakePermissionState( + A_PERMISSION, + PermissionStatus.Granted + ) + val permissionStateProvider = + FakeComposablePermissionStateProvider( + permissionState + ) val presenter = DefaultPermissionsPresenter( A_PERMISSION, permissionsStore, @@ -57,8 +71,14 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user closes dialog`() = runTest { val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false)) - val permissionStateProvider = FakePermissionStateProvider(permissionState) + val permissionState = FakePermissionState( + A_PERMISSION, + PermissionStatus.Denied(shouldShowRationale = false) + ) + val permissionStateProvider = + FakeComposablePermissionStateProvider( + permissionState + ) val presenter = DefaultPermissionsPresenter( A_PERMISSION, permissionsStore, @@ -77,8 +97,14 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user does not grant permission`() = runTest { val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false)) - val permissionStateProvider = FakePermissionStateProvider(permissionState) + val permissionState = FakePermissionState( + A_PERMISSION, + PermissionStatus.Denied(shouldShowRationale = false) + ) + val permissionStateProvider = + FakeComposablePermissionStateProvider( + permissionState + ) val presenter = DefaultPermissionsPresenter( A_PERMISSION, permissionsStore, @@ -106,8 +132,14 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user does not grant permission second time`() = runTest { val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = true)) - val permissionStateProvider = FakePermissionStateProvider(permissionState) + val permissionState = FakePermissionState( + A_PERMISSION, + PermissionStatus.Denied(shouldShowRationale = true) + ) + val permissionStateProvider = + FakeComposablePermissionStateProvider( + permissionState + ) val presenter = DefaultPermissionsPresenter( A_PERMISSION, permissionsStore, @@ -134,9 +166,19 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user does not grant permission third time`() = runTest { - val permissionsStore = InMemoryPermissionsStore(permissionDenied = true, permissionAsked = true) - val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false)) - val permissionStateProvider = FakePermissionStateProvider(permissionState) + val permissionsStore = + InMemoryPermissionsStore( + permissionDenied = true, + permissionAsked = true + ) + val permissionState = FakePermissionState( + A_PERMISSION, + PermissionStatus.Denied(shouldShowRationale = false) + ) + val permissionStateProvider = + FakeComposablePermissionStateProvider( + permissionState + ) val presenter = DefaultPermissionsPresenter( A_PERMISSION, permissionsStore, @@ -157,8 +199,14 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user grants permission`() = runTest { val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false)) - val permissionStateProvider = FakePermissionStateProvider(permissionState) + val permissionState = FakePermissionState( + A_PERMISSION, + PermissionStatus.Denied(shouldShowRationale = false) + ) + val permissionStateProvider = + FakeComposablePermissionStateProvider( + permissionState + ) val presenter = DefaultPermissionsPresenter( A_PERMISSION, permissionsStore, diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt similarity index 95% rename from libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt rename to libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt index c204ff5fc6..900d9ccc69 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt @@ -27,9 +27,9 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.PermissionStatus -class FakePermissionStateProvider constructor( +class FakeComposablePermissionStateProvider constructor( private val permissionState: FakePermissionState -) : PermissionStateProvider { +) : ComposablePermissionStateProvider { private lateinit var onPermissionResult: (Boolean) -> Unit @OptIn(ExperimentalPermissionsApi::class) diff --git a/libraries/permissions/noop/build.gradle.kts b/libraries/permissions/noop/build.gradle.kts index e4b8963c89..9282d796bf 100644 --- a/libraries/permissions/noop/build.gradle.kts +++ b/libraries/permissions/noop/build.gradle.kts @@ -31,4 +31,5 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.tests.testutils) } diff --git a/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt b/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt index 912edd9294..f837529515 100644 --- a/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt +++ b/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt @@ -20,10 +20,17 @@ 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.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test class NoopPermissionsPresenterTest { + + @Rule + @JvmField + val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val presenter = NoopPermissionsPresenter() diff --git a/libraries/permissions/test/build.gradle.kts b/libraries/permissions/test/build.gradle.kts new file mode 100644 index 0000000000..6ed6a89677 --- /dev/null +++ b/libraries/permissions/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * 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") +} + +android { + namespace = "io.element.android.libraries.permissions.test" +} + +dependencies { + implementation(projects.libraries.architecture) + api(projects.libraries.permissions.api) +} diff --git a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt new file mode 100644 index 0000000000..f26f268860 --- /dev/null +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt @@ -0,0 +1,51 @@ +/* + * 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.permissions.test + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsPresenter +import io.element.android.libraries.permissions.api.PermissionsState +import io.element.android.libraries.permissions.api.aPermissionsState + +class FakePermissionsPresenter( + private val initialState: PermissionsState = aPermissionsState().copy(showDialog = false), +) : PermissionsPresenter { + + private fun eventSink(events: PermissionsEvents) { + when (events) { + PermissionsEvents.OpenSystemDialog -> state.value = state.value.copy(showDialog = true, permissionAlreadyAsked = true) + PermissionsEvents.CloseDialog -> state.value = state.value.copy(showDialog = false) + } + } + + private val state = mutableStateOf(initialState.copy(eventSink = ::eventSink)) + + fun setPermissionGranted() { + state.value = state.value.copy(permissionGranted = true) + } + + fun setPermissionDenied() { + state.value = state.value.copy(permissionAlreadyDenied = true) + } + + @Composable + override fun present(): PermissionsState { + return state.value + } +} diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/InMemoryPermissionsStore.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt similarity index 90% rename from libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/InMemoryPermissionsStore.kt rename to libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt index 3f5d925ccd..abb357ab98 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/InMemoryPermissionsStore.kt +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.element.android.libraries.permissions.impl +package io.element.android.libraries.permissions.test +import io.element.android.libraries.permissions.api.PermissionsStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -43,6 +44,5 @@ class InMemoryPermissionsStore( setPermissionDenied(permission, false) } - override suspend fun resetStore() { - } + override suspend fun resetStore() = Unit } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt index 2fe02a8958..9752c621d5 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt @@ -131,6 +131,6 @@ class PendingIntentFactory @Inject constructor( fun createInviteListPendingIntent(sessionId: SessionId): PendingIntent { val intent = intentProvider.getInviteListIntent(sessionId) - return PendingIntentCompat.getActivity(context, 0, intent, 0, false) + return PendingIntentCompat.getActivity(context, 0, intent, 0, false)!! } } diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index 96faa3197b..186ab121fd 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -20,6 +20,13 @@ plugins { android { namespace = "io.element.android.libraries.pushproviders.firebase" + + buildTypes { + release { + isMinifyEnabled = true + consumerProguardFiles("consumer-proguard-rules.pro") + } + } } anvil { @@ -38,7 +45,11 @@ dependencies { implementation(projects.libraries.pushproviders.api) api(platform(libs.google.firebase.bom)) - api("com.google.firebase:firebase-messaging-ktx") + api("com.google.firebase:firebase-messaging-ktx") { + exclude(group = "com.google.firebase", module = "firebase-core") + exclude(group = "com.google.firebase", module = "firebase-analytics") + exclude(group = "com.google.firebase", module = "firebase-measurement-connector") + } testImplementation(libs.test.junit) testImplementation(libs.test.truth) diff --git a/libraries/pushproviders/firebase/consumer-proguard-rules.pro b/libraries/pushproviders/firebase/consumer-proguard-rules.pro new file mode 100644 index 0000000000..0bc7b604bf --- /dev/null +++ b/libraries/pushproviders/firebase/consumer-proguard-rules.pro @@ -0,0 +1,4 @@ +# Fix this error: +# ERROR: Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in /Users/bmarty/workspaces/element-x-android/app/build/outputs/mapping/nightly/missing_rules.txt. +# ERROR: R8: Missing class com.google.firebase.analytics.connector.AnalyticsConnector (referenced from: void com.google.firebase.messaging.MessagingAnalytics.logToScion(java.lang.String, android.os.Bundle) and 1 other context) +-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector diff --git a/libraries/textcomposer/build.gradle.kts b/libraries/textcomposer/impl/build.gradle.kts similarity index 82% rename from libraries/textcomposer/build.gradle.kts rename to libraries/textcomposer/impl/build.gradle.kts index 76d7d00a03..633491d3b3 100644 --- a/libraries/textcomposer/build.gradle.kts +++ b/libraries/textcomposer/impl/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * 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. @@ -31,5 +31,11 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.constraintlayout.compose) + + implementation(libs.matrix.richtexteditor) + api(libs.matrix.richtexteditor.compose) + ksp(libs.showkase.processor) } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/Message.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/Message.kt new file mode 100644 index 0000000000..0f3a213427 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/Message.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.textcomposer + +data class Message( + val html: String, + val markdown: String, +) diff --git a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt similarity index 100% rename from libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt rename to libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt 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 new file mode 100644 index 0000000000..d137af18bb --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -0,0 +1,757 @@ +/* + * 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 + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.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.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension.Companion.fillToConstraints +import androidx.constraintlayout.compose.Visibility +import io.element.android.libraries.designsystem.VectorIcons +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.text.applyScaleUp +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.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.TransactionId +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail +import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo +import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType +import io.element.android.libraries.textcomposer.components.FormattingOption +import io.element.android.libraries.textcomposer.components.FormattingOptionState +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.wysiwyg.compose.RichTextEditor +import io.element.android.wysiwyg.compose.RichTextEditorDefaults +import io.element.android.wysiwyg.compose.RichTextEditorState +import io.element.android.wysiwyg.view.models.InlineFormat +import kotlinx.coroutines.android.awaitFrame +import uniffi.wysiwyg_composer.ActionState +import uniffi.wysiwyg_composer.ComposerAction + +@Composable +fun TextComposer( + state: RichTextEditorState, + composerMode: MessageComposerMode, + canSendMessage: Boolean, + modifier: Modifier = Modifier, + showTextFormatting: Boolean = false, + onRequestFocus: () -> Unit = {}, + onSendMessage: (Message) -> Unit = {}, + onResetComposerMode: () -> Unit = {}, + onAddAttachment: () -> Unit = {}, + onDismissTextFormatting: () -> Unit = {}, + onError: (Throwable) -> Unit = {}, +) { + val onSendClicked = { + onSendMessage(Message(html = state.messageHtml, markdown = state.messageMarkdown)) + } + + Column( + modifier = modifier + .padding( + start = 3.dp, + end = 6.dp, + top = 8.dp, + bottom = 5.dp, + ) + .fillMaxWidth(), + ) { + ConstraintLayout( + modifier = Modifier.fillMaxWidth(), + ) { + val (composeOptions, textInput, sendButton) = createRefs() + val showComposerOptionsButton by remember(showTextFormatting) { + derivedStateOf { !showTextFormatting } + } + IconButton( + modifier = Modifier + .size(48.dp) + .constrainAs(composeOptions) { + start.linkTo(parent.start) + bottom.linkTo(parent.bottom) + visibility = if (showComposerOptionsButton) Visibility.Visible else Visibility.Gone + }, + onClick = onAddAttachment + ) { + Icon( + modifier = Modifier.size(30.dp.applyScaleUp()), + resourceId = R.drawable.ic_plus, // TODO Replace with design system icon when available + contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment), + tint = ElementTheme.colors.iconPrimary, + ) + } + val roundCornerSmall = 20.dp.applyScaleUp() + val roundCornerLarge = 28.dp.applyScaleUp() + + val roundedCornerSize = remember(state.lineCount, composerMode) { + if (state.lineCount > 1 || composerMode is MessageComposerMode.Special) { + roundCornerSmall + } else { + roundCornerLarge + } + } + val roundedCornerSizeState = animateDpAsState( + targetValue = roundedCornerSize, + animationSpec = tween( + durationMillis = 100, + ) + ) + val roundedCorners = RoundedCornerShape(roundedCornerSizeState.value) + val colors = ElementTheme.colors + val bgColor = colors.bgSubtleSecondary + + val borderColor by remember(state.hasFocus, colors) { + derivedStateOf { + if (state.hasFocus) colors.borderDisabled else bgColor + } + } + + Column( + modifier = Modifier + .constrainAs(textInput) { + start.linkTo(composeOptions.end, margin = 3.dp, goneMargin = 9.dp) + end.linkTo(sendButton.start, margin = 6.dp, goneMargin = 6.dp) + bottom.linkTo(parent.bottom) + width = fillToConstraints + } + .padding(vertical = 3.dp) + .fillMaxWidth() + .clip(roundedCorners) + .background(color = bgColor) + .border(1.dp, borderColor, roundedCorners) + ) { + if (composerMode is MessageComposerMode.Special) { + ComposerModeView(composerMode = composerMode, onResetComposerMode = onResetComposerMode) + } + + TextInput( + state = state, + roundedCorners = roundedCorners, + bgColor = bgColor, + onError = onError, + ) + } + + SendButton( + canSendMessage = canSendMessage, + onClick = onSendClicked, + composerMode = composerMode, + modifier = Modifier + .constrainAs(sendButton) { + bottom.linkTo(parent.bottom) + end.linkTo(parent.end) + visibility = if (!showTextFormatting) Visibility.Visible else Visibility.Gone + } + ) + } + + if (showTextFormatting) { + TextFormatting( + state = state, + onDismiss = onDismissTextFormatting, + sendButton = { + SendButton( + canSendMessage = canSendMessage, + onClick = onSendClicked, + composerMode = composerMode, + modifier = it + ) + }, + ) + } + } + + // Request focus when changing mode, and show keyboard. + val keyboard = LocalSoftwareKeyboardController.current + LaunchedEffect(composerMode) { + if (composerMode is MessageComposerMode.Special) { + onRequestFocus() + keyboard?.let { + awaitFrame() + it.show() + } + } + } +} + +@Composable +private fun TextInput( + state: RichTextEditorState, + roundedCorners: RoundedCornerShape, + bgColor: Color, + modifier: Modifier = Modifier, + onError: (Throwable) -> Unit = {}, +) { + val minHeight = 42.dp.applyScaleUp() + val defaultTypography = ElementTheme.typography.fontBodyLgRegular + Box( + modifier = modifier + .heightIn(min = minHeight) + .background(color = bgColor, shape = roundedCorners) + .padding( + PaddingValues( + top = 4.dp.applyScaleUp(), + bottom = 4.dp.applyScaleUp(), + start = 12.dp.applyScaleUp(), + end = 42.dp.applyScaleUp() + ) + ), + contentAlignment = Alignment.CenterStart, + ) { + + // Placeholder + if (state.messageHtml.isEmpty()) { + Text( + stringResource(CommonStrings.common_message), + style = defaultTypography.copy( + color = ElementTheme.colors.textDisabled, + ), + ) + } + + RichTextEditor( + state = state, + modifier = Modifier + .fillMaxWidth(), + style = RichTextEditorDefaults.style( + text = RichTextEditorDefaults.textStyle( + color = if (state.hasFocus) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.secondary + } + ), + cursor = RichTextEditorDefaults.cursorStyle( + color = ElementTheme.colors.iconAccentTertiary, + ) + ), + onError = onError + ) + } +} + +@Composable +private fun TextFormatting( + state: RichTextEditorState, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + sendButton: @Composable (modifier: Modifier) -> Unit, +) { + ConstraintLayout( + modifier = modifier + .fillMaxWidth() + ) { + val (close, formatting, send) = createRefs() + + IconButton( + modifier = Modifier + .size(48.dp) + .constrainAs(close) { + start.linkTo(parent.start) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + }, + onClick = onDismiss + ) { + Icon( + modifier = Modifier.size(30.dp.applyScaleUp()), + resourceId = R.drawable.ic_cancel, // TODO Replace with design system icon when available + contentDescription = stringResource(CommonStrings.action_close), + tint = ElementTheme.colors.iconPrimary, + ) + } + + val scrollState = rememberScrollState() + Row( + modifier = Modifier + .constrainAs(formatting) { + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + start.linkTo(close.end, margin = 3.dp) + end.linkTo(send.start, margin = 20.dp) + width = fillToConstraints + } + .horizontalScroll(scrollState), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + FormattingOption( + state = state.actions[ComposerAction.BOLD].toButtonState(), + onClick = { state.toggleInlineFormat(InlineFormat.Bold) }, + imageVector = ImageVector.vectorResource(VectorIcons.Bold), + contentDescription = stringResource(CommonStrings.rich_text_editor_format_bold) + ) + FormattingOption( + state = state.actions[ComposerAction.ITALIC].toButtonState(), + onClick = { state.toggleInlineFormat(InlineFormat.Italic) }, + imageVector = ImageVector.vectorResource(VectorIcons.Italic), + contentDescription = stringResource(CommonStrings.rich_text_editor_format_italic) + ) + FormattingOption( + state = state.actions[ComposerAction.UNDERLINE].toButtonState(), + onClick = { state.toggleInlineFormat(InlineFormat.Underline) }, + imageVector = ImageVector.vectorResource(VectorIcons.Underline), + contentDescription = stringResource(CommonStrings.rich_text_editor_format_underline) + ) + FormattingOption( + state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(), + onClick = { state.toggleInlineFormat(InlineFormat.StrikeThrough) }, + imageVector = ImageVector.vectorResource(VectorIcons.Strikethrough), + contentDescription = stringResource(CommonStrings.rich_text_editor_format_strikethrough) + ) + FormattingOption( + state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(), + onClick = { state.toggleList(ordered = false) }, + imageVector = ImageVector.vectorResource(VectorIcons.BulletList), + contentDescription = stringResource(CommonStrings.rich_text_editor_bullet_list) + ) + FormattingOption( + state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(), + onClick = { state.toggleList(ordered = true) }, + imageVector = ImageVector.vectorResource(VectorIcons.NumberedList), + contentDescription = stringResource(CommonStrings.rich_text_editor_numbered_list) + ) + FormattingOption( + state = state.actions[ComposerAction.INDENT].toButtonState(), + onClick = { state.indent() }, + imageVector = ImageVector.vectorResource(VectorIcons.IndentIncrease), + contentDescription = stringResource(CommonStrings.rich_text_editor_indent) + ) + FormattingOption( + state = state.actions[ComposerAction.UNINDENT].toButtonState(), + onClick = { state.unindent() }, + imageVector = ImageVector.vectorResource(VectorIcons.IndentDecrease), + contentDescription = stringResource(CommonStrings.rich_text_editor_unindent) + ) + FormattingOption( + state = state.actions[ComposerAction.INLINE_CODE].toButtonState(), + onClick = { state.toggleInlineFormat(InlineFormat.InlineCode) }, + imageVector = ImageVector.vectorResource(VectorIcons.InlineCode), + contentDescription = stringResource(CommonStrings.rich_text_editor_inline_code) + ) + FormattingOption( + state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(), + onClick = { state.toggleCodeBlock() }, + imageVector = ImageVector.vectorResource(VectorIcons.CodeBlock), + contentDescription = stringResource(CommonStrings.rich_text_editor_code_block) + ) + FormattingOption( + state = state.actions[ComposerAction.QUOTE].toButtonState(), + onClick = { state.toggleQuote() }, + imageVector = ImageVector.vectorResource(VectorIcons.Quote), + contentDescription = stringResource(CommonStrings.rich_text_editor_quote) + ) + } + + sendButton( + Modifier.constrainAs(send) { + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + end.linkTo(parent.end) + }, + ) + } +} + +private fun ActionState?.toButtonState(): FormattingOptionState = + when (this) { + ActionState.ENABLED -> FormattingOptionState.Default + ActionState.REVERSED -> FormattingOptionState.Selected + ActionState.DISABLED, null -> FormattingOptionState.Disabled + } + +@Composable +private fun ComposerModeView( + composerMode: MessageComposerMode, + onResetComposerMode: () -> Unit, + modifier: Modifier = Modifier, +) { + when (composerMode) { + is MessageComposerMode.Edit -> { + EditingModeView(onResetComposerMode = onResetComposerMode, modifier = modifier) + } + is MessageComposerMode.Reply -> { + ReplyToModeView( + modifier = modifier.padding(8.dp), + senderName = composerMode.senderName, + text = composerMode.defaultContent, + attachmentThumbnailInfo = composerMode.attachmentThumbnailInfo, + onResetComposerMode = onResetComposerMode, + ) + } + else -> Unit + } +} + +@Composable +private fun EditingModeView( + onResetComposerMode: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(5.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .padding(start = 12.dp) + ) { + Icon( + resourceId = VectorIcons.Edit, + contentDescription = stringResource(CommonStrings.common_editing), + tint = ElementTheme.materialColors.secondary, + modifier = Modifier + .padding(vertical = 8.dp) + .size(16.dp.applyScaleUp()), + ) + Text( + stringResource(CommonStrings.common_editing), + style = ElementTheme.typography.fontBodySmRegular, + textAlign = TextAlign.Start, + color = ElementTheme.materialColors.secondary, + modifier = Modifier + .padding(vertical = 8.dp) + .weight(1f) + ) + Icon( + imageVector = Icons.Default.Close, + contentDescription = stringResource(CommonStrings.action_close), + tint = ElementTheme.materialColors.secondary, + modifier = Modifier + .padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 12.dp) + .size(16.dp.applyScaleUp()) + .clickable( + enabled = true, + onClick = onResetComposerMode, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false) + ), + ) + } +} + +@Composable +private fun ReplyToModeView( + senderName: String, + text: String?, + attachmentThumbnailInfo: AttachmentThumbnailInfo?, + onResetComposerMode: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier + .clip(RoundedCornerShape(13.dp)) + .background(MaterialTheme.colorScheme.surface) + .padding(4.dp) + ) { + if (attachmentThumbnailInfo != null) { + AttachmentThumbnail( + info = attachmentThumbnailInfo, + backgroundColor = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier + .size(36.dp) + .clip(RoundedCornerShape(9.dp)) + ) + } + Spacer(modifier = Modifier.width(8.dp)) + Column( + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically) + ) { + Text( + text = senderName, + modifier = Modifier.fillMaxWidth(), + style = ElementTheme.typography.fontBodySmMedium, + textAlign = TextAlign.Start, + color = ElementTheme.materialColors.primary, + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = text.orEmpty(), + style = ElementTheme.typography.fontBodyMdRegular, + textAlign = TextAlign.Start, + color = ElementTheme.materialColors.secondary, + maxLines = if (attachmentThumbnailInfo != null) 1 else 2, + overflow = TextOverflow.Ellipsis, + ) + } + Icon( + imageVector = Icons.Default.Close, + contentDescription = stringResource(CommonStrings.action_close), + tint = MaterialTheme.colorScheme.secondary, + modifier = Modifier + .padding(end = 4.dp, top = 4.dp, start = 16.dp, bottom = 16.dp) + .size(16.dp.applyScaleUp()) + .clickable( + enabled = true, + onClick = onResetComposerMode, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false) + ), + ) + } +} + +@Composable +private fun SendButton( + canSendMessage: Boolean, + onClick: () -> Unit, + composerMode: MessageComposerMode, + modifier: Modifier = Modifier, +) { + IconButton( + modifier = modifier + .size(48.dp.applyScaleUp()), + onClick = onClick, + enabled = canSendMessage, + ) { + val iconId = when (composerMode) { + is MessageComposerMode.Edit -> R.drawable.ic_tick + else -> R.drawable.ic_send + } + val contentDescription = when (composerMode) { + is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit) + else -> stringResource(CommonStrings.action_send) + } + Box( + modifier = Modifier + .clip(CircleShape) + .size(36.dp.applyScaleUp()) + .background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent) + ) { + Icon( + modifier = Modifier + .height(18.dp.applyScaleUp()) + .align(Alignment.Center), + resourceId = iconId, + contentDescription = contentDescription, + // Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary + tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled + ) + } + } +} + +@DayNightPreviews +@Composable +internal fun TextComposerSimplePreview() = ElementPreview { + Column { + TextComposer( + RichTextEditorState("", fake = true).apply { requestFocus() }, + canSendMessage = false, + onSendMessage = {}, + composerMode = MessageComposerMode.Normal(""), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState("A message", fake = true).apply { requestFocus() }, + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Normal(""), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState( + "A message\nWith several lines\nTo preview larger textfields and long lines with overflow", + fake = true + ).apply { + requestFocus() + }, + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Normal(""), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState("A message without focus", fake = true), + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Normal(""), + onResetComposerMode = {}, + ) + } +} + +@DayNightPreviews +@Composable +internal fun TextComposerFormattingPreview() = ElementPreview { + Column { + TextComposer( + RichTextEditorState("", fake = true), + canSendMessage = false, + showTextFormatting = true, + composerMode = MessageComposerMode.Normal(""), + ) + TextComposer( + RichTextEditorState("A message", fake = true), + canSendMessage = true, + showTextFormatting = true, + composerMode = MessageComposerMode.Normal(""), + ) + TextComposer( + RichTextEditorState("A message\nWith several lines\nTo preview larger textfields and long lines with overflow", fake = true), + canSendMessage = true, + showTextFormatting = true, + composerMode = MessageComposerMode.Normal(""), + ) + } +} + +@DayNightPreviews +@Composable +internal fun TextComposerEditPreview() = ElementPreview { + TextComposer( + RichTextEditorState("A message", fake = true).apply { requestFocus() }, + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", TransactionId("1234")), + onResetComposerMode = {}, + ) +} + +@DayNightPreviews +@Composable +internal fun TextComposerReplyPreview() = ElementPreview { + Column { + TextComposer( + RichTextEditorState("", fake = true), + canSendMessage = false, + onSendMessage = {}, + composerMode = MessageComposerMode.Reply( + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = null, + defaultContent = "A message\n" + + "With several lines\n" + + "To preview larger textfields and long lines with overflow" + ), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState("A message", fake = true), + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Reply( + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = MediaSource("https://domain.com/image.jpg"), + textContent = "image.jpg", + type = AttachmentThumbnailType.Image, + blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", + ), + defaultContent = "image.jpg" + ), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState("A message", fake = true), + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Reply( + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = MediaSource("https://domain.com/video.mp4"), + textContent = "video.mp4", + type = AttachmentThumbnailType.Video, + blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", + ), + defaultContent = "video.mp4" + ), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState("A message", fake = true), + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Reply( + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = null, + textContent = "logs.txt", + type = AttachmentThumbnailType.File, + blurHash = null, + ), + defaultContent = "logs.txt" + ), + onResetComposerMode = {}, + ) + TextComposer( + RichTextEditorState("A message", fake = true).apply { requestFocus() }, + canSendMessage = true, + onSendMessage = {}, + composerMode = MessageComposerMode.Reply( + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = null, + textContent = null, + type = AttachmentThumbnailType.Location, + blurHash = null, + ), + defaultContent = "Shared location" + ), + onResetComposerMode = {}, + ) + } +} diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt new file mode 100644 index 0000000000..7eb1d08293 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt @@ -0,0 +1,69 @@ +/* + * 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.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.text.applyScaleUp +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.theme.compound.generated.SemanticColors + +@Composable +internal fun FormattingOption( + state: FormattingOptionState, + onClick: () -> Unit, + imageVector: ImageVector, + contentDescription: String, + modifier: Modifier = Modifier, + colors: SemanticColors = ElementTheme.colors, +) { + val backgroundColor = when (state) { + FormattingOptionState.Selected -> colors.bgActionPrimaryRest + FormattingOptionState.Default, + FormattingOptionState.Disabled -> Color.Transparent + } + + val foregroundColor = when (state) { + FormattingOptionState.Selected -> colors.iconOnSolidPrimary + FormattingOptionState.Default -> colors.iconPrimary + FormattingOptionState.Disabled -> colors.iconDisabled + } + Box( + modifier = modifier + .clickable { onClick() } + .size(44.dp.applyScaleUp()) + .background(backgroundColor, shape = RoundedCornerShape(4.dp.applyScaleUp())) + ) { + Icon( + modifier = Modifier.align(Alignment.Center), + imageVector = imageVector, + contentDescription = contentDescription, + tint = foregroundColor, + ) + } +} + diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt new file mode 100644 index 0000000000..386fa5a668 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.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.textcomposer.components + +internal enum class FormattingOptionState { + Default, Selected, Disabled +} + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_cancel.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000000..5c27ba82d9 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_plus.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_plus.xml new file mode 100644 index 0000000000..ead38721dc --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml new file mode 100644 index 0000000000..bf346b3a01 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/textcomposer/src/main/res/drawable/ic_tick.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_tick.xml similarity index 100% rename from libraries/textcomposer/src/main/res/drawable/ic_tick.xml rename to libraries/textcomposer/impl/src/main/res/drawable/ic_tick.xml diff --git a/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml b/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..dbac717093 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,4 @@ + + + "Přidat přílohu" + diff --git a/libraries/textcomposer/impl/src/main/res/values-de/translations.xml b/libraries/textcomposer/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..6b75a1c9a7 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,4 @@ + + + "Anhang hinzufügen" + diff --git a/libraries/textcomposer/impl/src/main/res/values-ro/translations.xml b/libraries/textcomposer/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..25f0b025ca --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,4 @@ + + + "Adăugați un atașament" + diff --git a/libraries/textcomposer/impl/src/main/res/values-ru/translations.xml b/libraries/textcomposer/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..318cf9dadd --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,4 @@ + + + "Прикрепить файл" + diff --git a/libraries/textcomposer/impl/src/main/res/values-sk/translations.xml b/libraries/textcomposer/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..9d1daf99b6 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,4 @@ + + + "Pridať prílohu" + diff --git a/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml b/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..5aeda1b9e2 --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,4 @@ + + + "新增附件" + diff --git a/libraries/textcomposer/impl/src/main/res/values/localazy.xml b/libraries/textcomposer/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..11af785e8c --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values/localazy.xml @@ -0,0 +1,4 @@ + + + "Add attachment" + diff --git a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt deleted file mode 100644 index 3c0e0fa723..0000000000 --- a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ /dev/null @@ -1,567 +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 - -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusEvent -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.VectorIcons -import io.element.android.libraries.designsystem.modifiers.applyIf -import io.element.android.libraries.designsystem.preview.DayNightPreviews -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.text.applyScaleUp -import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.libraries.designsystem.theme.components.Surface -import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.TransactionId -import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail -import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo -import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType -import io.element.android.libraries.theme.ElementTheme -import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.coroutines.android.awaitFrame - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) -@Composable -fun TextComposer( - composerText: String?, - composerMode: MessageComposerMode, - composerCanSendMessage: Boolean, - modifier: Modifier = Modifier, - focusRequester: FocusRequester = FocusRequester(), - onSendMessage: (String) -> Unit = {}, - onResetComposerMode: () -> Unit = {}, - onComposerTextChange: (String) -> Unit = {}, - onAddAttachment: () -> Unit = {}, - onFocusChanged: (Boolean) -> Unit = {}, -) { - val text = composerText.orEmpty() - Row( - modifier.padding( - horizontal = 12.dp, - vertical = 8.dp - ), verticalAlignment = Alignment.Bottom - ) { - AttachmentButton(onClick = onAddAttachment, modifier = Modifier.padding(vertical = 6.dp)) - Spacer(modifier = Modifier.width(12.dp)) - val roundCornerSmall = 20.dp.applyScaleUp() - val roundCornerLarge = 28.dp.applyScaleUp() - var lineCount by remember { mutableIntStateOf(0) } - - val roundedCornerSize = remember(lineCount, composerMode) { - if (lineCount > 1 || composerMode is MessageComposerMode.Special) { - roundCornerSmall - } else { - roundCornerLarge - } - } - val roundedCornerSizeState = animateDpAsState( - targetValue = roundedCornerSize, - animationSpec = tween( - durationMillis = 100, - ) - ) - val roundedCorners = RoundedCornerShape(roundedCornerSizeState.value) - val minHeight = 42.dp.applyScaleUp() - val bgColor = ElementTheme.colors.bgSubtleSecondary - // Change border color depending on focus - var hasFocus by remember { mutableStateOf(false) } - val borderColor = if (hasFocus) ElementTheme.colors.borderDisabled else bgColor - Column( - modifier = Modifier - .fillMaxWidth() - .clip(roundedCorners) - .background(color = bgColor) - .border(1.dp, borderColor, roundedCorners) - ) { - if (composerMode is MessageComposerMode.Special) { - ComposerModeView(composerMode = composerMode, onResetComposerMode = onResetComposerMode) - } - val defaultTypography = ElementTheme.typography.fontBodyLgRegular - Box { - BasicTextField( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = minHeight) - .focusRequester(focusRequester) - .onFocusEvent { - hasFocus = it.hasFocus - onFocusChanged(it.hasFocus) - }, - value = text, - onValueChange = { onComposerTextChange(it) }, - onTextLayout = { - lineCount = it.lineCount - }, - keyboardOptions = KeyboardOptions( - capitalization = KeyboardCapitalization.Sentences, - ), - textStyle = defaultTypography.copy(color = MaterialTheme.colorScheme.primary), - cursorBrush = SolidColor(ElementTheme.colors.iconAccentTertiary), - decorationBox = { innerTextField -> - TextFieldDefaults.DecorationBox( - value = text, - innerTextField = innerTextField, - enabled = true, - singleLine = false, - visualTransformation = VisualTransformation.None, - shape = roundedCorners, - contentPadding = PaddingValues( - top = 10.dp.applyScaleUp(), - bottom = 10.dp.applyScaleUp(), - start = 12.dp.applyScaleUp(), - end = 42.dp.applyScaleUp(), - ), - interactionSource = remember { MutableInteractionSource() }, - placeholder = { - Text(stringResource(CommonStrings.common_message), style = defaultTypography) - }, - colors = TextFieldDefaults.colors( - unfocusedTextColor = MaterialTheme.colorScheme.secondary, - focusedTextColor = MaterialTheme.colorScheme.primary, - unfocusedPlaceholderColor = ElementTheme.colors.textDisabled, - focusedPlaceholderColor = ElementTheme.colors.textDisabled, - unfocusedIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - unfocusedContainerColor = bgColor, - focusedContainerColor = bgColor, - errorContainerColor = bgColor, - disabledContainerColor = bgColor, - ) - ) - } - ) - - SendButton( - text = text, - canSendMessage = composerCanSendMessage, - onSendMessage = onSendMessage, - composerMode = composerMode, - modifier = Modifier.padding(end = 6.dp.applyScaleUp(), bottom = 6.dp.applyScaleUp()) - ) - } - } - } - - // Request focus when changing mode, and show keyboard. - val keyboard = LocalSoftwareKeyboardController.current - LaunchedEffect(composerMode) { - if (composerMode is MessageComposerMode.Special) { - focusRequester.requestFocus() - keyboard?.let { - awaitFrame() - it.show() - } - } - } -} - -@Composable -private fun ComposerModeView( - composerMode: MessageComposerMode, - onResetComposerMode: () -> Unit, - modifier: Modifier = Modifier, -) { - when (composerMode) { - is MessageComposerMode.Edit -> { - EditingModeView(onResetComposerMode = onResetComposerMode, modifier = modifier) - } - is MessageComposerMode.Reply -> { - ReplyToModeView( - modifier = modifier.padding(8.dp), - senderName = composerMode.senderName, - text = composerMode.defaultContent.toString(), - attachmentThumbnailInfo = composerMode.attachmentThumbnailInfo, - onResetComposerMode = onResetComposerMode, - ) - } - else -> Unit - } -} - -@Composable -private fun EditingModeView( - onResetComposerMode: () -> Unit, - modifier: Modifier = Modifier, -) { - Row( - horizontalArrangement = Arrangement.spacedBy(5.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .padding(start = 12.dp) - ) { - Icon( - resourceId = VectorIcons.Edit, - contentDescription = stringResource(CommonStrings.common_editing), - tint = ElementTheme.materialColors.secondary, - modifier = Modifier - .padding(vertical = 8.dp) - .size(16.dp.applyScaleUp()), - ) - Text( - stringResource(CommonStrings.common_editing), - style = ElementTheme.typography.fontBodySmRegular, - textAlign = TextAlign.Start, - color = ElementTheme.materialColors.secondary, - modifier = Modifier - .padding(vertical = 8.dp) - .weight(1f) - ) - Icon( - imageVector = Icons.Default.Close, - contentDescription = stringResource(CommonStrings.action_close), - tint = ElementTheme.materialColors.secondary, - modifier = Modifier - .padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 12.dp) - .size(16.dp.applyScaleUp()) - .clickable( - enabled = true, - onClick = onResetComposerMode, - interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false) - ), - ) - } -} - -@Composable -private fun ReplyToModeView( - senderName: String, - text: String?, - attachmentThumbnailInfo: AttachmentThumbnailInfo?, - onResetComposerMode: () -> Unit, - modifier: Modifier = Modifier, -) { - Row( - modifier - .clip(RoundedCornerShape(13.dp)) - .background(MaterialTheme.colorScheme.surface) - .padding(4.dp) - ) { - if (attachmentThumbnailInfo != null) { - AttachmentThumbnail( - info = attachmentThumbnailInfo, - backgroundColor = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier - .size(36.dp) - .clip(RoundedCornerShape(9.dp)) - ) - } - Spacer(modifier = Modifier.width(8.dp)) - Column( - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically) - ) { - Text( - text = senderName, - modifier = Modifier.fillMaxWidth(), - style = ElementTheme.typography.fontBodySmMedium, - textAlign = TextAlign.Start, - color = ElementTheme.materialColors.primary, - ) - Text( - modifier = Modifier.fillMaxWidth(), - text = text.orEmpty(), - style = ElementTheme.typography.fontBodyMdRegular, - textAlign = TextAlign.Start, - color = ElementTheme.materialColors.secondary, - maxLines = if (attachmentThumbnailInfo != null) 1 else 2, - overflow = TextOverflow.Ellipsis, - ) - } - Icon( - imageVector = Icons.Default.Close, - contentDescription = stringResource(CommonStrings.action_close), - tint = MaterialTheme.colorScheme.secondary, - modifier = Modifier - .padding(end = 4.dp, top = 4.dp, start = 16.dp, bottom = 16.dp) - .size(16.dp.applyScaleUp()) - .clickable( - enabled = true, - onClick = onResetComposerMode, - interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false) - ), - ) - } -} - -@Composable -private fun AttachmentButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - Surface( - modifier - .size(30.dp.applyScaleUp()) - .clickable(onClick = onClick), - shape = CircleShape, - color = ElementTheme.colors.iconPrimary - ) { - Image( - modifier = Modifier.size(12.5f.dp.applyScaleUp()), - painter = painterResource(R.drawable.ic_add_attachment), - contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment), - contentScale = ContentScale.Inside, - colorFilter = ColorFilter.tint( - LocalContentColor.current - ) - ) - } -} - -@Composable -private fun BoxScope.SendButton( - text: String, - canSendMessage: Boolean, - onSendMessage: (String) -> Unit, - composerMode: MessageComposerMode, - modifier: Modifier = Modifier, -) { - val interactionSource = remember { MutableInteractionSource() } - Box( - modifier = modifier - .clip(CircleShape) - .background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent) - .size(30.dp.applyScaleUp()) - .align(Alignment.BottomEnd) - .applyIf(composerMode !is MessageComposerMode.Edit, ifTrue = { - padding(start = 1.dp.applyScaleUp()) // Center the arrow in the circle - }) - .clickable( - enabled = canSendMessage, - interactionSource = interactionSource, - indication = rememberRipple(bounded = false), - onClick = { - onSendMessage(text) - }), - contentAlignment = Alignment.Center, - ) { - val iconId = when (composerMode) { - is MessageComposerMode.Edit -> R.drawable.ic_tick - else -> R.drawable.ic_send - } - val contentDescription = when (composerMode) { - is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit) - else -> stringResource(CommonStrings.action_send) - } - Icon( - modifier = Modifier.size(16.dp.applyScaleUp()), - resourceId = iconId, - contentDescription = contentDescription, - // Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary - tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled - ) - } -} - -@DayNightPreviews -@Composable -internal fun TextComposerSimplePreview() = ElementPreview { - Column { - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Normal(""), - onResetComposerMode = {}, - composerCanSendMessage = false, - composerText = "", - ) - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Normal(""), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Normal(""), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message\nWith several lines\nTo preview larger textfields and long lines with overflow", - ) - } -} - -@DayNightPreviews -@Composable -internal fun TextComposerEditPreview() = ElementPreview { - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", TransactionId("1234")), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) -} - -@DayNightPreviews -@Composable -internal fun TextComposerReplyPreview() = ElementPreview { - Column { - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Reply( - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = null, - defaultContent = "A message\n" + - "With several lines\n" + - "To preview larger textfields and long lines with overflow" - ), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Reply( - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = MediaSource("https://domain.com/image.jpg"), - textContent = "image.jpg", - type = AttachmentThumbnailType.Image, - blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", - ), - defaultContent = "image.jpg" - ), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Reply( - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = MediaSource("https://domain.com/video.mp4"), - textContent = "video.mp4", - type = AttachmentThumbnailType.Video, - blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", - ), - defaultContent = "video.mp4" - ), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Reply( - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = null, - textContent = "logs.txt", - type = AttachmentThumbnailType.File, - blurHash = null, - ), - defaultContent = "logs.txt" - ), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) - TextComposer( - onSendMessage = {}, - onComposerTextChange = {}, - composerMode = MessageComposerMode.Reply( - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = null, - textContent = null, - type = AttachmentThumbnailType.Location, - blurHash = null, - ), - defaultContent = "Shared location" - ), - onResetComposerMode = {}, - composerCanSendMessage = true, - composerText = "A message", - ) - } -} diff --git a/libraries/textcomposer/src/main/res/drawable/ic_add_attachment.xml b/libraries/textcomposer/src/main/res/drawable/ic_add_attachment.xml deleted file mode 100644 index ac9d53639b..0000000000 --- a/libraries/textcomposer/src/main/res/drawable/ic_add_attachment.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/libraries/textcomposer/src/main/res/drawable/ic_send.xml b/libraries/textcomposer/src/main/res/drawable/ic_send.xml deleted file mode 100644 index 64e0f120c4..0000000000 --- a/libraries/textcomposer/src/main/res/drawable/ic_send.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/libraries/textcomposer/src/main/res/values-cs/translations.xml b/libraries/textcomposer/src/main/res/values-cs/translations.xml deleted file mode 100644 index c37f419ec4..0000000000 --- a/libraries/textcomposer/src/main/res/values-cs/translations.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - "Přidat přílohu" - "Přepnout seznam s odrážkami" - "Přepnout blok kódu" - "Zpráva…" - "Použít tučný text" - "Použít kurzívu" - "Použít přeškrtnutí" - "Použít podtržení" - "Přepnout režim celé obrazovky" - "Odsazení" - "Použít formát inline kódu" - "Nastavit odkaz" - "Přepnout číslovaný seznam" - "Přepnout citaci" - "Zrušit odsazení" - diff --git a/libraries/textcomposer/src/main/res/values-de/translations.xml b/libraries/textcomposer/src/main/res/values-de/translations.xml deleted file mode 100644 index dea872d7c0..0000000000 --- a/libraries/textcomposer/src/main/res/values-de/translations.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - "Anhang hinzufügen" - "Aufzählungsliste ein-/ausschalten" - "Codeblock umschalten" - "Nachricht…" - "Fettformatierung anwenden" - "Kursivformat anwenden" - "Durchgestrichenes Format anwenden" - "Unterstreichungsformat anwenden" - "Vollbildmodus umschalten" - "Einrücken" - "Inline-Codeformat anwenden" - "Link setzen" - "Nummerierte Liste ein-/ausschalten" - "Zitat umschalten" - "Einrücken aufheben" - diff --git a/libraries/textcomposer/src/main/res/values-es/translations.xml b/libraries/textcomposer/src/main/res/values-es/translations.xml deleted file mode 100644 index 606e3bde8e..0000000000 --- a/libraries/textcomposer/src/main/res/values-es/translations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - "Lista de puntos" - "Bloque de código" - "Mensaje…" - "Aplicar formato negrita" - "Aplicar formato cursiva" - "Aplicar formato tachado" - "Aplicar formato de subrayado" - "Pantalla completa" - "Añadir sangría" - "Código" - "Enlazar" - "Lista numérica" - "Cita" - "Quitar sangría" - diff --git a/libraries/textcomposer/src/main/res/values-fr/translations.xml b/libraries/textcomposer/src/main/res/values-fr/translations.xml deleted file mode 100644 index 4b239c0f93..0000000000 --- a/libraries/textcomposer/src/main/res/values-fr/translations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - "Afficher une liste à puces" - "Afficher le bloc de code" - "Envoyer un message…" - "Appliquer le format gras" - "Appliquer le format italique" - "Appliquer le format barré" - "Appliquer le format souligné" - "Afficher en mode plein écran" - "Décaler vers la droite" - "Appliquer le formatage de code en ligne" - "Définir un lien" - "Afficher une liste numérotée" - "Afficher une citation" - "Décaler vers la gauche" - diff --git a/libraries/textcomposer/src/main/res/values-it/translations.xml b/libraries/textcomposer/src/main/res/values-it/translations.xml deleted file mode 100644 index e3034e8dfe..0000000000 --- a/libraries/textcomposer/src/main/res/values-it/translations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - "Attiva/disattiva l\'elenco puntato" - "Attiva/disattiva il blocco di codice" - "Messaggio…" - "Applica il formato in grassetto" - "Applicare il formato corsivo" - "Applica il formato barrato" - "Applicare il formato di sottolineatura" - "Attiva/disattiva la modalità a schermo intero" - "Rientro a destra" - "Applicare il formato del codice in linea" - "Imposta collegamento" - "Attiva/disattiva elenco numerato" - "Attiva/disattiva citazione" - "Rientro a sinistra" - diff --git a/libraries/textcomposer/src/main/res/values-ro/translations.xml b/libraries/textcomposer/src/main/res/values-ro/translations.xml deleted file mode 100644 index a7e1a7135c..0000000000 --- a/libraries/textcomposer/src/main/res/values-ro/translations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - "Comutați lista cu puncte" - "Comutați blocul de cod" - "Mesaj…" - "Aplicați formatul aldin" - "Aplicați formatul italic" - "Aplicați formatul barat" - "Aplică formatul de subliniere" - "Comutați modul ecran complet" - "Indentare" - "Aplicați formatul de cod inline" - "Setați linkul" - "Comutați lista numerotată" - "Aplicați citatul" - "Dez-identare" - diff --git a/libraries/textcomposer/src/main/res/values-ru/translations.xml b/libraries/textcomposer/src/main/res/values-ru/translations.xml deleted file mode 100644 index 9f7324f086..0000000000 --- a/libraries/textcomposer/src/main/res/values-ru/translations.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - "Прикрепить файл" - "Переключить список маркеров" - "Переключить блок кода" - "Сообщение" - "Применить жирный шрифт" - "Применить курсивный формат" - "Применить формат зачеркивания" - "Применить формат подчеркивания" - "Переключение полноэкранного режима" - "Отступ" - "Применить встроенный формат кода" - "Установить ссылку" - "Переключить нумерованный список" - "Переключить цитату" - "Без отступа" - diff --git a/libraries/textcomposer/src/main/res/values-sk/translations.xml b/libraries/textcomposer/src/main/res/values-sk/translations.xml deleted file mode 100644 index 26ac1df436..0000000000 --- a/libraries/textcomposer/src/main/res/values-sk/translations.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - "Pridať prílohu" - "Prepnúť zoznam odrážok" - "Prepnúť blok kódu" - "Správa…" - "Použiť tučný formát" - "Použiť formát kurzívy" - "Použiť formát prečiarknutia" - "Použiť formát podčiarknutia" - "Prepnúť režim celej obrazovky" - "Odsadenie" - "Použiť formát riadkového kódu" - "Nastaviť odkaz" - "Prepnúť číslovaný zoznam" - "Prepnúť citáciu" - "Zrušiť odsadenie" - diff --git a/libraries/textcomposer/src/main/res/values-zh-rTW/translations.xml b/libraries/textcomposer/src/main/res/values-zh-rTW/translations.xml deleted file mode 100644 index 93777d4ca5..0000000000 --- a/libraries/textcomposer/src/main/res/values-zh-rTW/translations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - "新增附件" - "切換項目編號" - "切換程式碼區塊" - "訊息" - "套用粗體" - "套用斜體" - "套用刪除線" - "套用底線" - "切換全螢幕模式" - "增加縮排" - "設定連結" - "切換數字編號" - "切換引用" - "減少縮排" - diff --git a/libraries/textcomposer/src/main/res/values/localazy.xml b/libraries/textcomposer/src/main/res/values/localazy.xml deleted file mode 100644 index 0bd53c3bee..0000000000 --- a/libraries/textcomposer/src/main/res/values/localazy.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - "Add attachment" - "Toggle bullet list" - "Toggle code block" - "Message…" - "Apply bold format" - "Apply italic format" - "Apply strikethrough format" - "Apply underline format" - "Toggle full screen mode" - "Indent" - "Apply inline code format" - "Set link" - "Toggle numbered list" - "Toggle quote" - "Unindent" - diff --git a/libraries/textcomposer/test/build.gradle.kts b/libraries/textcomposer/test/build.gradle.kts new file mode 100644 index 0000000000..04e36e337d --- /dev/null +++ b/libraries/textcomposer/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * 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") +} + +android { + namespace = "io.element.android.libraries.textcomposer.test" +} + +dependencies { + api(projects.libraries.textcomposer.impl) + implementation(projects.tests.testutils) +} diff --git a/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/ElementTheme.kt b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/ElementTheme.kt index fa7bd279e7..81780d1c2d 100644 --- a/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/ElementTheme.kt +++ b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/ElementTheme.kt @@ -92,6 +92,7 @@ internal val LocalCompoundColors = staticCompositionLocalOf { compoundColorsLigh @Composable fun ElementTheme( darkTheme: Boolean = isSystemInDarkTheme(), + lightStatusBar: Boolean = !darkTheme, dynamicColor: Boolean = false, /* true to enable MaterialYou */ compoundColors: SemanticColors = if (darkTheme) compoundColorsDark else compoundColorsLight, materialLightColors: ColorScheme = materialColorSchemeLight, @@ -111,8 +112,19 @@ fun ElementTheme( darkTheme -> materialDarkColors else -> materialLightColors } + val statusBarColorScheme = if (lightStatusBar) { + when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + dynamicLightColorScheme(context) + } + else -> materialLightColors + } + } else { + colorScheme + } SideEffect { - systemUiController.applyTheme(colorScheme = colorScheme, darkTheme = darkTheme) + systemUiController.applyTheme(colorScheme = statusBarColorScheme, darkTheme = darkTheme && !lightStatusBar) } CompositionLocalProvider( LocalCompoundColors provides currentCompoundColor, @@ -132,6 +144,7 @@ fun ElementTheme( */ @Composable fun ForcedDarkElementTheme( + lightStatusBar: Boolean = false, content: @Composable () -> Unit, ) { val systemUiController = rememberSystemUiController() @@ -142,7 +155,7 @@ fun ForcedDarkElementTheme( systemUiController.applyTheme(colorScheme, wasDarkTheme) } } - ElementTheme(darkTheme = true, content = content) + ElementTheme(darkTheme = true, lightStatusBar = lightStatusBar, content = content) } private fun SystemUiController.applyTheme( diff --git a/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/colors/AvatarColorsDark.kt b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/colors/AvatarColorsDark.kt new file mode 100644 index 0000000000..a18721a60b --- /dev/null +++ b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/colors/AvatarColorsDark.kt @@ -0,0 +1,35 @@ +/* + * 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.theme.colors + +import io.element.android.libraries.theme.compound.generated.internal.DarkDesignTokens + +/** + * Avatar colors are not yet part of SemanticColors, so create list here. + * DarkDesignTokens is internal to the module. + */ + +val avatarColorsDark = listOf( + DarkDesignTokens.colorBlue300 to DarkDesignTokens.colorBlue1200, + DarkDesignTokens.colorFuchsia300 to DarkDesignTokens.colorFuchsia1200, + DarkDesignTokens.colorGreen300 to DarkDesignTokens.colorGreen1200, + DarkDesignTokens.colorPink300 to DarkDesignTokens.colorPink1200, + DarkDesignTokens.colorOrange300 to DarkDesignTokens.colorOrange1200, + DarkDesignTokens.colorCyan300 to DarkDesignTokens.colorCyan1200, + DarkDesignTokens.colorPurple300 to DarkDesignTokens.colorPurple1200, + DarkDesignTokens.colorLime300 to DarkDesignTokens.colorLime1200, +) diff --git a/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/colors/AvatarColorsLight.kt b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/colors/AvatarColorsLight.kt new file mode 100644 index 0000000000..d2c320d479 --- /dev/null +++ b/libraries/theme/src/main/kotlin/io/element/android/libraries/theme/colors/AvatarColorsLight.kt @@ -0,0 +1,35 @@ +/* + * 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.theme.colors + +import io.element.android.libraries.theme.compound.generated.internal.LightDesignTokens + +/** + * Avatar colors are not yet part of SemanticColors, so create list here. + * LightDesignTokens is internal to the module. + */ + +val avatarColorsLight = listOf( + LightDesignTokens.colorBlue300 to LightDesignTokens.colorBlue1200, + LightDesignTokens.colorFuchsia300 to LightDesignTokens.colorFuchsia1200, + LightDesignTokens.colorGreen300 to LightDesignTokens.colorGreen1200, + LightDesignTokens.colorPink300 to LightDesignTokens.colorPink1200, + LightDesignTokens.colorOrange300 to LightDesignTokens.colorOrange1200, + LightDesignTokens.colorCyan300 to LightDesignTokens.colorCyan1200, + LightDesignTokens.colorPurple300 to LightDesignTokens.colorPurple1200, + LightDesignTokens.colorLime300 to LightDesignTokens.colorLime1200, +) 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 1ace446132..bbb68b53e8 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -23,6 +23,7 @@ "Hotovo" "Upravit" "Povolit" + "Ukončit hlasování" "Zapomněli jste heslo?" "Přeposlat" "Pozvat" @@ -171,6 +172,20 @@ "Zdá se, že jste frustrovaně třásli telefonem. Chcete otevřít obrazovku 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" + "Přepnout seznam s odrážkami" + "Přepnout blok kódu" + "Zpráva…" + "Použít tučný text" + "Použít kurzívu" + "Použít přeškrtnutí" + "Použít podtržení" + "Přepnout režim celé obrazovky" + "Odsazení" + "Použít formát inline kódu" + "Nastavit odkaz" + "Přepnout číslovaný seznam" + "Přepnout citaci" + "Zrušit odsazení" "Toto je začátek %1$s." "Toto je začátek této konverzace." "Nové" 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 00979596a9..7aa02bddae 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -169,6 +169,20 @@ "Du scheinst frustriert das Telefon zu schütteln. Möchtest du den Fehlerberichtsbildschirm öffnen?" "Diese Nachricht wird an deinen Heimserver-Admin gemeldet. Er wird nicht in der Lage sein, verschlüsselte Nachrichten zu lesen." "Grund für die Meldung dieses Inhalts" + "Aufzählungsliste ein-/ausschalten" + "Codeblock umschalten" + "Nachricht…" + "Fettformatierung anwenden" + "Kursivformat anwenden" + "Durchgestrichenes Format anwenden" + "Unterstreichungsformat anwenden" + "Vollbildmodus umschalten" + "Einrücken" + "Inline-Codeformat anwenden" + "Link setzen" + "Nummerierte Liste ein-/ausschalten" + "Zitat umschalten" + "Einrücken aufheben" "Dies ist der Anfang von %1$s." "Dies ist der Beginn dieser Konversation." "Neu" 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 fa8a80f953..b56e1dfc7c 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -120,6 +120,20 @@ "Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?" "Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado." "Motivo para denunciar este contenido" + "Lista de puntos" + "Bloque de código" + "Mensaje…" + "Aplicar formato negrita" + "Aplicar formato cursiva" + "Aplicar formato tachado" + "Aplicar formato de subrayado" + "Pantalla completa" + "Añadir sangría" + "Código" + "Enlazar" + "Lista numérica" + "Cita" + "Quitar sangría" "Este es el principio de %1$s." "Este es el principio de esta conversación." "Nuevos" 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 e7020bc47b..745bde6497 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -158,6 +158,20 @@ "Vous semblez secouer le téléphone de frustration. Voulez-vous ouvrir le formulaire de rapport de problème ?" "Ce message sera signalé à l’administrateur de votre serveur d\'accueil. Ils ne pourront lire aucun message chiffré." "Raison du signalement de ce contenu" + "Afficher une liste à puces" + "Afficher le bloc de code" + "Envoyer un message…" + "Appliquer le format gras" + "Appliquer le format italique" + "Appliquer le format barré" + "Appliquer le format souligné" + "Afficher en mode plein écran" + "Décaler vers la droite" + "Appliquer le formatage de code en ligne" + "Définir un lien" + "Afficher une liste numérotée" + "Afficher une citation" + "Décaler vers la gauche" "Ceci est le début de %1$s." "Ceci est le début de cette conversation." "Nouveau" 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 b15d570dfc..740fe81b55 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -120,6 +120,20 @@ "Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?" "Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati." "Motivo della segnalazione di questo contenuto" + "Attiva/disattiva l\'elenco puntato" + "Attiva/disattiva il blocco di codice" + "Messaggio…" + "Applica il formato in grassetto" + "Applicare il formato corsivo" + "Applica il formato barrato" + "Applicare il formato di sottolineatura" + "Attiva/disattiva la modalità a schermo intero" + "Rientro a destra" + "Applicare il formato del codice in linea" + "Imposta collegamento" + "Attiva/disattiva elenco numerato" + "Attiva/disattiva citazione" + "Rientro a sinistra" "Questo è l\'inizio di %1$s." "Questo è l\'inizio della conversazione." "Nuovo" 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 d90a7a6cfc..d96abca3cc 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -1,6 +1,8 @@ "Ascundeți parola" + "Doar mențiuni" + "Notificări dezactivate" "Trimiteți fișiere" "Afișați parola" "Meniu utilizator" @@ -23,6 +25,7 @@ "Efectuat" "Editați" "Activați" + "Închideți sondajul" "Ați uitat parola?" "Redirecționați" "Invitați" @@ -40,6 +43,7 @@ "Deschideți cu" "Raspuns rapid" "Citat" + "Reacționați" "Ștergeți" "Răspundeți" "Raportați o eroare" @@ -94,6 +98,9 @@ "Parola" "Persoane" "Permalink" + "Voturi finale: %1$s" + "Total voturi: %1$s" + "Rezultatele vor fi afișate după încheierea sondajului" "Politica de confidențialitate" "Reacții" "Se actualizează" @@ -116,11 +123,12 @@ "Succes" "Sugestii" "Se sincronizează…" + "Text" "Notificări despre software de la terți" "Subiect" "Despre ce este vorba în această cameră?" "Nu s-a putut decripta" - "Nu am putut trimite cu succes invitații unuia sau mai multor utilizatori." + "Nu am putut trimite invitații unuia sau mai multor utilizatori." "Nu s-a putut trimite invitația (invitațiile)" "Activați sunetul" "Eveniment neacceptat" @@ -140,7 +148,11 @@ "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" + "%1$s nu a putut accesa locația dumneavoastră. Vă rugăm să încercați din nou mai târziu." + "%1$s nu are permisiuni pentru a accesa locația dumneavoastră. Puteți permite accesul în Setări." + "%1$s nu are permisiuni pentru a accesa locația dumneavoastră. Permiteți accesul mai jos." "Unele mesaje nu au fost trimise" "Ne pare rău, a apărut o eroare" "🔐️ Alăturați-vă mie pe %1$s" @@ -154,10 +166,34 @@ "%1$d membri" "%1$d membri" + + "%d vot" + "%d voturi" + "%d voturi" + "Rageshake pentru a raporta erori" "Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?" "Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat." "Motivul raportării acestui conținut" + "Comutați lista cu puncte" + "Închideți opțiunile de formatare" + "Comutați blocul de cod" + "Mesaj…" + "Creați un link" + "Editați link-ul" + "Aplicați formatul aldin" + "Aplicați formatul italic" + "Aplicați formatul barat" + "Aplică formatul de subliniere" + "Comutați modul ecran complet" + "Indentare" + "Aplicați formatul de cod inline" + "Setați linkul" + "Comutați lista numerotată" + "Deschideți opțiunile de compunere" + "Aplicați citatul" + "Dez-identare" + "Link" "Acesta este începutul conversației %1$s." "Acesta este începutul acestei conversații." "Nou" @@ -165,9 +201,40 @@ "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" + "Cont și dispozitive" "Partajați locația" "Distribuiți locația mea" + "Deschideți în Apple Maps" + "Deschideți în Google Maps" + "Deschideți în OpenStreetMap" "Distribuiți această locație" "Locație" "Rageshake" 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 23df74fff5..28284f6347 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -171,6 +171,20 @@ "Кажется, вы трясли телефон. Хотите открыть экран отчета об ошибке?" "Это сообщение будет передано администратору вашего домашнего сервера. Они не смогут прочитать зашифрованные сообщения." "Причина, по которой вы пожаловались на этот контент" + "Переключить список маркеров" + "Переключить блок кода" + "Сообщение" + "Применить жирный шрифт" + "Применить курсивный формат" + "Применить формат зачеркивания" + "Применить формат подчеркивания" + "Переключение полноэкранного режима" + "Отступ" + "Применить встроенный формат кода" + "Установить ссылку" + "Переключить нумерованный список" + "Переключить цитату" + "Без отступа" "Это начало %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 b8d919faa0..cad1b5a564 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -171,6 +171,20 @@ "Zdá sa, že zúrivo trasiete telefónom. Chcete otvoriť obrazovku s nahlásením chýb?" "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" + "Prepnúť zoznam odrážok" + "Prepnúť blok kódu" + "Správa…" + "Použiť tučný formát" + "Použiť formát kurzívy" + "Použiť formát prečiarknutia" + "Použiť formát podčiarknutia" + "Prepnúť režim celej obrazovky" + "Odsadenie" + "Použiť formát riadkového kódu" + "Nastaviť odkaz" + "Prepnúť číslovaný zoznam" + "Prepnúť citáciu" + "Zrušiť odsadenie" "Toto je začiatok %1$s." "Toto je začiatok tejto konverzácie." "Nové" 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 32f8a23086..ddaaed0d33 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 @@ -141,6 +141,19 @@ "%d 票" "檢舉這個內容的原因" + "切換項目編號" + "切換程式碼區塊" + "訊息" + "套用粗體" + "套用斜體" + "套用刪除線" + "套用底線" + "切換全螢幕模式" + "增加縮排" + "設定連結" + "切換數字編號" + "切換引用" + "減少縮排" "新訊息" "分享分析數據" "無法上傳媒體檔案,請稍後再試。" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index d4e74d29e4..d6f9f2cbaf 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -2,6 +2,8 @@ "Use an identity server to invite by email. ""Use the default (%(defaultIdentityServerName)s)"" or manage in ""Settings""." "Hide password" + "Mentions only" + "Muted" "Send files" "Show password" "User menu" @@ -24,6 +26,7 @@ "Done" "Edit" "Enable" + "End poll" "Forgot password?" "Forward" "Invite" @@ -121,6 +124,7 @@ "Success" "Suggestions" "Syncing" + "Text" "Third-party notices" "Topic" "What is this room about?" @@ -174,6 +178,25 @@ "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?" "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" + "Toggle bullet list" + "Close formatting options" + "Toggle code block" + "Message…" + "Create a link" + "Edit link" + "Apply bold format" + "Apply italic format" + "Apply strikethrough format" + "Apply underline format" + "Toggle full screen mode" + "Indent" + "Apply inline code format" + "Set link" + "Toggle numbered list" + "Open compose options" + "Toggle quote" + "Unindent" + "Link" "This is the beginning of %1$s." "This is the beginning of this conversation." "New" diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 0e9d807014..98ebdc5f6f 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 1 // 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 = 6 +private const val versionPatch = 7 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 fb082e27a7..592cf3c52a 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -99,7 +99,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:mediapickers:impl")) implementation(project(":libraries:mediaupload:impl")) implementation(project(":libraries:usersearch:impl")) - implementation(project(":libraries:textcomposer")) + implementation(project(":libraries:textcomposer:impl")) } fun DependencyHandlerScope.allServicesImpl() { diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 8063ac9b0a..1473bd7f93 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { implementation(projects.features.login.impl) implementation(projects.features.networkmonitor.impl) implementation(projects.services.toolbox.impl) + implementation(projects.libraries.featureflag.impl) implementation(libs.coroutines.core) coreLibraryDesugaring(libs.android.desugar) } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index a915e70046..d0309ebec7 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -54,7 +54,7 @@ class MainActivity : ComponentActivity() { coroutineDispatchers = Singleton.coroutineDispatchers, sessionStore = sessionStore, userAgentProvider = userAgentProvider, - clock = DefaultSystemClock() + clock = DefaultSystemClock(), ) ) } diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index f3b3a7fab2..c52f66ef2d 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -32,6 +32,7 @@ class FakeAnalyticsService( private val isEnabledFlow = MutableStateFlow(isEnabled) private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) val capturedEvents = mutableListOf() + val trackedErrors = mutableListOf() override fun getAvailableAnalyticsProviders(): Set = emptySet() @@ -66,6 +67,7 @@ class FakeAnalyticsService( } override fun trackError(throwable: Throwable) { + trackedErrors += throwable } override suspend fun reset() { diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt index b4fd6dcd18..40c72142c6 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt @@ -25,10 +25,12 @@ import javax.inject.Inject class PostHogFactory @Inject constructor( @ApplicationContext private val context: Context, private val buildMeta: BuildMeta, + private val posthogEndpointConfigProvider: PosthogEndpointConfigProvider, ) { fun createPosthog(): PostHog { - return PostHog.Builder(context, PosthogConfig.postHogApiKey, PosthogConfig.postHogHost) + val endpoint = posthogEndpointConfigProvider.provide() + return PostHog.Builder(context, endpoint.apiKey, endpoint.host) // Record certain application events automatically! (off/false by default) // .captureApplicationLifecycleEvents() // Record screen views automatically! (off/false by default) diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt index fb2e341e1e..e371c6fef4 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt @@ -35,7 +35,7 @@ import javax.inject.Inject class PosthogAnalyticsProvider @Inject constructor( private val postHogFactory: PostHogFactory, ) : AnalyticsProvider { - override val name = PosthogConfig.name + override val name = "Posthog" private var posthog: PostHog? = null private var analyticsId: String? = null @@ -71,7 +71,7 @@ class PosthogAnalyticsProvider @Inject constructor( } override fun trackError(throwable: Throwable) { - TODO("Not yet implemented") + // Not implemented } private fun createPosthog(): PostHog = postHogFactory.createPosthog() diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt similarity index 77% rename from services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt rename to services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt index 96d8659b11..a037c53797 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt @@ -16,8 +16,7 @@ package io.element.android.services.analyticsproviders.posthog -object PosthogConfig { - const val name = "Posthog" - const val postHogHost = "https://posthog.element.dev" - const val postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN" -} +data class PosthogEndpointConfig( + val host: String, + val apiKey: String, +) diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt new file mode 100644 index 0000000000..53fdb0947f --- /dev/null +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.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.services.analyticsproviders.posthog + +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.meta.BuildType +import javax.inject.Inject + +class PosthogEndpointConfigProvider @Inject constructor( + private val buildMeta: BuildMeta, +) { + fun provide(): PosthogEndpointConfig { + return when (buildMeta.buildType) { + BuildType.RELEASE -> PosthogEndpointConfig( + host = "https://posthog.element.io", + apiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", + ) + BuildType.NIGHTLY, + BuildType.DEBUG -> PosthogEndpointConfig( + host = "https://posthog.element.dev", + apiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", + ) + } + } +} diff --git a/services/toolbox/test/build.gradle.kts b/services/toolbox/test/build.gradle.kts new file mode 100644 index 0000000000..cb8857ceaa --- /dev/null +++ b/services/toolbox/test/build.gradle.kts @@ -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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.services.toolbox.test" +} + +dependencies { + api(projects.services.toolbox.api) +} diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.kt new file mode 100644 index 0000000000..072afeca77 --- /dev/null +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.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.services.toolbox.test.sdk + +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider + +class FakeBuildVersionSdkIntProvider( + private val sdkInt: Int +) : BuildVersionSdkIntProvider { + override fun get(): Int = sdkInt +} diff --git a/tests/testutils/build.gradle.kts b/tests/testutils/build.gradle.kts index 184bbc418a..8275c975d2 100644 --- a/tests/testutils/build.gradle.kts +++ b/tests/testutils/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.ksp) } @@ -31,4 +31,5 @@ dependencies { implementation(libs.coroutines.test) implementation(projects.libraries.core) implementation(libs.test.turbine) + implementation(libs.molecule.runtime) } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt new file mode 100644 index 0000000000..a192cfbd63 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt @@ -0,0 +1,48 @@ +/* + * 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.tests.testutils + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import kotlinx.coroutines.test.runTest +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import kotlin.time.Duration.Companion.seconds + +/** + * moleculeFlow can take time to initialise during the first test of any given + * test class. + * + * Applying this test rule ensures that the slow initialisation is not done + * inside runTest which has a short default timeout. + */ +class WarmUpRule: TestRule { + override fun apply(base: Statement, description: Description): Statement = object: Statement() { + override fun evaluate() { + runTest(timeout = 60.seconds) { + moleculeFlow(RecompositionMode.Immediate) { + // Do nothing + }.test { + awaitItem() // Await a Unit composition + } + } + base.evaluate() + } + } +} diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_1,NEXUS_5,1.0,en].png index 7631149941..f0232ef1fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:208bb2b2743d33d15741f502329ac9ed67eb73c0556c14803edf33eafeadbc06 -size 28586 +oid sha256:2dc520372ac1b4ae340a1e75e0a1f4cc65f54c9e9ebf44c750309018613fa5d4 +size 28865 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_2,NEXUS_5,1.0,en].png index fae8a6fca3..dffeee671e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c89ac73df77c2bccb0c2aa80cee1420f78e7d07f0eda89a90bffef55e8cf753 -size 4464 +oid sha256:7bf3e51b483ca9600d9f589721bdfe93e0977ebcfe59ff66484fcbd7cfb12e20 +size 81101 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_1,NEXUS_5,1.0,en].png index f95d150d54..439ea49b7b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abc707d2ec2e23878e78126063ea31c5eaa93559d2803fba5cf453e4a02a01bb -size 29293 +oid sha256:f8b78908805bb8745613be2f9fad2c1c4fd0d6087dce7f9b301b4fe3dc485d19 +size 30273 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_2,NEXUS_5,1.0,en].png index 665c8811ac..75c9154caf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_null_AddPeopleViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb0d3bfcfd75cbd75fd9270ff1dc27090e5dbac79ca8db8a46d91a4c12bc966b -size 4457 +oid sha256:25b86e5211512c4a676cb142b847455ba34c13eb3712f75a22c8b313ca725eeb +size 83750 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png index 9005d6750d..19e170130f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchMultipleUsersResultItem_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:013b3e67d39a895fb391b82a633d85bc5e372e2b209854dcda9a72db0c243812 -size 86646 +oid sha256:b051a4492303db0e5a1343269cb57301939105ab4c062db67326edb699a02d0f +size 86337 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchSingleUserResultItem_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchSingleUserResultItem_0_null,NEXUS_5,1.0,en].png index 349c6d27ac..037a2c9e40 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchSingleUserResultItem_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_SearchSingleUserResultItem_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a72958cbdae4807f345d98501420e8f6ac1409ac534f8434341df4c3e53cd5e -size 45342 +oid sha256:88608834541e31673c52014cc56072953f1ff1814bb3f89852822ebc6f762188 +size 45269 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_1,NEXUS_5,1.0,en].png index fd8b3f3e85..b87255406a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f66d2db6c718056a13f463f0f0f62321250222039c234d267d788351a051ed4d -size 25777 +oid sha256:dda8e627414be70d99e5d7055fe32cb4d31014e5b55fb6fce326f90eb210a32f +size 26147 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_5,NEXUS_5,1.0,en].png index ef70f98f62..fd718e2025 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e688c0f31238dd9870c8634409f854b29cb5ab43ca251fb41466f959103627af -size 64149 +oid sha256:c67a25ba48fdd4edc640f848712427656299cfd397a24805e6ebb89da533e1d9 +size 63838 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_6,NEXUS_5,1.0,en].png index 97a7dd126d..3bb77b278a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:421f94cb97db6395d5b53188e7084599c2adba2ae44d38004d3e088bcc0daad2 -size 67968 +oid sha256:67861c2fc225ca366c3c2206fcee0d59f77b232eedd29b1bce9ed630af002ea0 +size 68143 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_1,NEXUS_5,1.0,en].png index e84042b3eb..85cb7efca2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44a6cda760e51d3710a951f38d14e1cc17a4fdcb91f281e8c002d424e53b82d6 -size 25829 +oid sha256:7c01cba121aac57126e79becb6b5d2de7c3bee0f50418f9c17ab9228156c2030 +size 26274 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_5,NEXUS_5,1.0,en].png index 4d4ea9cb15..09e93acea2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:251f0553c3b80a4ca7c1c21fbf776c74ec6f0c85233d0481e0a6859c5f7fa53b -size 65976 +oid sha256:663b97daf508ebb9e619c1a6a14c5ea005b93bf004ed6ef7c08a202284b87e33 +size 64676 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_6,NEXUS_5,1.0,en].png index 6b00bf5b03..dc6259ef33 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_null_UserListViewLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71639d6a2869732e57c682158360c1bf281a3c3ed3522007139200606794b3ec -size 69992 +oid sha256:e85d3ebafb87a593fa8977f9def17c8f45f9e067e02fe05c48b3f9650835a000 +size 69456 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewDark_0_null_1,NEXUS_5,1.0,en].png index 780a9a189a..6e3c656d80 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71255625dff4f333e8c8e0fe3cb77435dc552fdc756b8b517e2a783cf6c4677e -size 83540 +oid sha256:a88f6e1fdc1b3cd26f21a94866d0348e458509c5ce129c86c69b2673c7dc75f1 +size 84357 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewLight_0_null_1,NEXUS_5,1.0,en].png index 6ecce2c456..80d8dc14fd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.configureroom_null_ConfigureRoomViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0af148880e1609550dca835b9ca7d18ca277f19c056d5acc160d6877dc2192e0 -size 87215 +oid sha256:e116827aaa9d30b356088805bf6a820cce3ba42c49e6ffca2460aab1a4cdc8ba +size 87872 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_1,NEXUS_5,1.0,en].png index c45576d5cc..20ea3618e0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e49f264556720e31a6974923f38765a4612f65a6be682dcb30cb65b4693c1171 -size 20738 +oid sha256:8f04a88c84cb7e4e6d0f487b47aaf5e80c9ae1472ab25bf5412e084c356d4bbe +size 20765 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_2,NEXUS_5,1.0,en].png index 5bf0a8b17f..d8fa732c18 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdc6810543c33b89da0d3ed0242d36ae4f33d3d16c73a6d4a6169abbbe28d8e3 -size 27046 +oid sha256:b85cf6982f409276e1bedc1a06322ecefef8ea86d4e5459a39d607fe10f5d08b +size 27093 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_1,NEXUS_5,1.0,en].png index 8d4037d07b..3de381303a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97dd59ac203eab2b727c3ceeb5191db5c4ad2d75eb1bc6676694ed06db7081ff -size 23810 +oid sha256:d0be20df5f6bdf095fd9a93df3dc6ca81939365e4f27c8f605a1ab12b4959fc6 +size 23724 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_2,NEXUS_5,1.0,en].png index 02d70404c1..49c0cecf77 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_null_CreateRoomRootViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5288dbf84bb4f976917d7be59c469306397d97cc42065ad085dbe10ee75aa20f -size 31185 +oid sha256:31bfc0054eff2bce1e43c49fa628968504e16853a198579710d08a2134d24efd +size 31099 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_null_NotificationsOptInView-D-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_null_NotificationsOptInView-D-1_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..aa866c7812 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_null_NotificationsOptInView-D-1_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a47f94d02280f2122c0743fadc2d53824555f2e57e2c595b07dc32836f80586 +size 37142 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_null_NotificationsOptInView-N-1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_null_NotificationsOptInView-N-1_3_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..49c3a7bafb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_null_NotificationsOptInView-N-1_3_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9028f6a5a6f67954385bcbcc098535071c096f37d12428c32af94a3e898bf6a +size 33685 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-2_3_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-1_2_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-2_3_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-2_4_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-1_3_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-2_4_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png index ee189e660a..dd4b64afcf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e13873cff0df39c7aec90c64041d8eb557bd2cd65935a34fb7122951b7cc0b87 -size 28452 +oid sha256:245c5b206b5f560c9457069761694f0198a89ad700cfc39241b22441a7f2c31e +size 28345 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png index cc4b32b204..6c1fee3405 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6128ba7f2b0b5389be5310bf3778c093ea30a92fa3f78173e7d3aafce834b14d -size 32920 +oid sha256:70b2df85168ea6d5d7f7cbae84a91f45a9ec7fe716d4d6c087d21f8b787a3fb6 +size 32648 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png index 8b95e73430..124e9ba417 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cb2252cfbd249fdb47c17881410f44fbdc8cbc0f8a8e79b8c973a311f77f3a9 -size 33053 +oid sha256:4e3ad562682d7e0df76faf0041947158c0e90b93a60d2aa4129ea1d222e439ce +size 32762 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png index dd98b758bb..83bcdc038e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c57803651c508e3d041a1a99cb3454935f2d7ae36ef82f8d0099c00089c96af -size 14041 +oid sha256:88b77b856b1a6582aad3fe26f08a48f405d2a0cbc1a495f691de7af569bc7370 +size 13744 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png index 82f1f4484f..14139948d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a3693f62c718cb061106b0c33e162d4eb82efc766aa81d164dc860ec4394a17 -size 28611 +oid sha256:9e2d4495246144df6f91ed8ad6ef5f32b507f56c2f8ab9713230e7212078d7ea +size 28479 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png index 48f2d5d109..9af73d58bd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb11c46a91a1fe4e2227a6de21d418e698bb70ce68afafaefc5280a979a86bf3 -size 29114 +oid sha256:730f27b45cb2176ac6f8415780b74e2f117b9cc3f64d4602e6c42253f65b23e7 +size 29181 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png index 900343f7d4..84b761658b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2229208fd03717884b2af74aebdce70411f50c0b69a47093a6e8346dd9766610 -size 34892 +oid sha256:5bcafa377dfdd0a3e3db955c2d81791f1c36c95b22d978090b8d4e6b063c7d69 +size 34866 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png index 5b4967d6f0..149ea8dca8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02fd7d1c9b3f982c2fbfad109c55bd444ef183855800490cf9117606b493c314 -size 35018 +oid sha256:0b77b77a20d1c3d936dea733542f1b2bd8e575ccb6d3021ce1c2d84a701bea35 +size 34965 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png index bccbae96c0..e9a0097fe1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18589707589516bd02024a4989b47bb57204bdc77b882ebbcfc0b79be6314c9d -size 14173 +oid sha256:d3092c176ec732f3e378430779a8645c02cd6c47756a6dafaf09a7fe28e79608 +size 14112 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png index 8615c02572..18eb4c8c8a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl.components_null_InviteSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaaa719eafbd61ad499b9dbc7d1ce1afa3d1b3dfc8e3a170b2f785a51684802a -size 29424 +oid sha256:8b3cf23c4ef3ee18a3dd4a20a683266b2cdc6b6bca90b1006c109e23c82c6ebb +size 29445 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_0,NEXUS_5,1.0,en].png index 12c6450dea..af4db9ed9a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4bef80d54bce17851bbc1d003055743e1c7e95b0ee069261aa5e2732e7b722e6 -size 52319 +oid sha256:079fe39c7dced18433ef0c768b30ec0c66bda3d910b392bfc33319f6be7ce892 +size 51852 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_2,NEXUS_5,1.0,en].png index 41adebbccb..68751cdec3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6814fb800186b8c0e6d2c4c07f2f959a5962a94fbbfd1c15d83d2a789457d930 -size 49646 +oid sha256:e002caef4c4be424468938e2ca05a25c6f6502340f216eca908ba8784a4d98b2 +size 49289 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_3,NEXUS_5,1.0,en].png index 322b98f06b..4ba1ea763c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09b31e87abee30225e796c3232b84c4670d57feb19c056de4238f60329e036d9 -size 50315 +oid sha256:d7bfc8f00d9286162292ed04e9a53f21f5f8f2f44bab88c00356d330b56fd426 +size 49965 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_4,NEXUS_5,1.0,en].png index c685dd2cba..c7a3568641 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90ff5065ec23e1c835ac395015e0f5491d44722262951f72a19a184d9e3a47da -size 40245 +oid sha256:6f23a5df96bb6faca94a8096cf4b884af1dc97a755f0664ca05c9d64657052f8 +size 39772 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_5,NEXUS_5,1.0,en].png index c685dd2cba..c7a3568641 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90ff5065ec23e1c835ac395015e0f5491d44722262951f72a19a184d9e3a47da -size 40245 +oid sha256:6f23a5df96bb6faca94a8096cf4b884af1dc97a755f0664ca05c9d64657052f8 +size 39772 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_0,NEXUS_5,1.0,en].png index cff979f7a8..6b5fc9bfeb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a348097d43335133ff83958b0b1dc24de2e4aae0fa2be6d000a7ca78516964b -size 55172 +oid sha256:9766ca3ad7562ead5bbc4b40eaf4712383a8c90876010fc0a23fc3ba4eb7eb42 +size 54662 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_2,NEXUS_5,1.0,en].png index aa81a4ff8d..39c860e6f5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0aa744f1d069c68202a087789328c1ac46c45d6fb9bae8b5593bc477c182dde -size 54182 +oid sha256:b564f1c08b839c6758e3abba916a0b20f6cc0f590b27773de1d6a1e88a2cdc56 +size 53899 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_3,NEXUS_5,1.0,en].png index cb710c9dcd..7e1c4760e3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7edbfc8e9e8bdcea6897860d1872fb8fabce139ec8610ec6180b28ca9ce9007 -size 54976 +oid sha256:6881032ea2d387c2bb8f8dd776fe6b581c100328d13b023e190c22dc0832e4a1 +size 54689 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_4,NEXUS_5,1.0,en].png index 7b79f5ff42..cb2628494c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:380ae6ad1fa6f00427eb7267a338964b6f77a4d5f8c45c25f535502481e34cc6 -size 44834 +oid sha256:9717ce6ae0559ae73cc9397976887b7acb1a534a957a3a29b0d0da2442e77130 +size 44397 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_5,NEXUS_5,1.0,en].png index 7b79f5ff42..cb2628494c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.invitelist.impl_null_InviteListViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:380ae6ad1fa6f00427eb7267a338964b6f77a4d5f8c45c25f535502481e34cc6 -size 44834 +oid sha256:9717ce6ae0559ae73cc9397976887b7acb1a534a957a3a29b0d0da2442e77130 +size 44397 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_0,NEXUS_5,1.0,en].png index 8e5282128a..8d2683d552 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1625b6184c01ab75efcfe4c8d1594af1ffb84f0416219580adce5a9371fabe7d -size 21292 +oid sha256:ad8519086ae725ac7b1aeae9cd628237afeb489c6000c814b4d8e3f644592e58 +size 22278 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_1,NEXUS_5,1.0,en].png index 135577ac2f..173cc88678 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91d1b5d5c58a4f2cbf048ad20b3a9742c209ea19703eafaf6d3e6f542caf2111 -size 40640 +oid sha256:f4cc5cd0ba741aa217ee000749ec4041186c2c83d13f6c87023d16f16689a9ed +size 41359 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_2,NEXUS_5,1.0,en].png index 8f41e9cece..153b681988 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe37eb6328241c70b4219c9fde9118593b1354fe99628e106af828868f9cc4c6 -size 39178 +oid sha256:1ff04ca56c00e2bbf4858bcf6f8faed596e2c538937ec26d61abac7922cda2e1 +size 39890 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_3,NEXUS_5,1.0,en].png index 8e5282128a..8d2683d552 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1625b6184c01ab75efcfe4c8d1594af1ffb84f0416219580adce5a9371fabe7d -size 21292 +oid sha256:ad8519086ae725ac7b1aeae9cd628237afeb489c6000c814b4d8e3f644592e58 +size 22278 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_4,NEXUS_5,1.0,en].png index 6c7b3227a0..95dff5613b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-D-0_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c94b320a8a6e1bbf2824eaa66d80476a293405b6bd0e7c2c96ce3b016be8f291 -size 21430 +oid sha256:b461bb08151c6efb41b0c4d7a40c33b3710dc5a1d0a32cb707ba90f0ef1ee2d1 +size 22419 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_0,NEXUS_5,1.0,en].png index 34ad716b56..ba420c0094 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eba27416d7da08fbd8c47a172f3b54a603c893e1d07faff443417e4949fbc985 -size 19849 +oid sha256:0a59281bb44aea2e220f48f1229afd6b4d2d0551dc0a7dd6e19b7a6ff3984f09 +size 20742 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_1,NEXUS_5,1.0,en].png index 685b83696c..ed4299da45 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40184d796909922b4316ee027e4500fa183d275bc73d7f69e51801e3f956333b -size 37569 +oid sha256:ac124e959b3c2303ecabe6eeba7c4faf7de55929757c0b69ceb4c0021cc1a390 +size 38197 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_2,NEXUS_5,1.0,en].png index 7d313adf76..29f176bdf8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5647bd3d7605f49358a4e1a76bf5fa86ab9a3ad32d180a134ea5e5271d8d8093 -size 36026 +oid sha256:7e064103aa27ef0b2765962bb5d21f24387ac215d27691570991a75dd85f8725 +size 36633 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_3,NEXUS_5,1.0,en].png index 34ad716b56..ba420c0094 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eba27416d7da08fbd8c47a172f3b54a603c893e1d07faff443417e4949fbc985 -size 19849 +oid sha256:0a59281bb44aea2e220f48f1229afd6b4d2d0551dc0a7dd6e19b7a6ff3984f09 +size 20742 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_4,NEXUS_5,1.0,en].png index b418af4d4e..4a37a3011c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.send_null_SendLocationView-N-0_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:434170541079e63b9c606e592407168f8e7e1e695f5f3e188428570ec364e2c6 -size 20047 +oid sha256:5b7db80ee7f9c4a4d74b0665803c1cf85a391b6af0643f7cc823de5e8897ba6e +size 20904 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_0,NEXUS_5,1.0,en].png index fe9329c1ed..f2ece2ce45 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d502b465f6f70ead5b9bc373b443d548053bc1d0867b4cbfb52d84e59fa253f1 -size 11596 +oid sha256:905d574bc64f14e309072a508732281c87dd00f8fa756bbd20b6d59c9b50eee6 +size 12428 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_1,NEXUS_5,1.0,en].png index 5d205c3650..cc035b2513 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81c29f9bb336e74a97189d8361490a3a29c4f4678d80f62c2e384396cb90a871 -size 32465 +oid sha256:1cb7669bfe98a30d0658c4444f08c11de6b8589a8a81163cb9a83366343c3aea +size 33113 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_2,NEXUS_5,1.0,en].png index 24894be572..b5ee476b1e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daf1ab743402e503ff6a5d0ec5f7a599b531c23fcbd9767570527828530bf5b3 -size 30817 +oid sha256:520a4053273d7d72a7339fdc609355ffbca183cbf151a24c701b9674e806306d +size 31453 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_3,NEXUS_5,1.0,en].png index fe9329c1ed..f2ece2ce45 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d502b465f6f70ead5b9bc373b443d548053bc1d0867b4cbfb52d84e59fa253f1 -size 11596 +oid sha256:905d574bc64f14e309072a508732281c87dd00f8fa756bbd20b6d59c9b50eee6 +size 12428 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_4,NEXUS_5,1.0,en].png index cdb697c87c..535f1e9742 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03769782960e9fc1abf6c609a318ca90debb03484d7b8d61d0703ef1374a0b55 -size 11786 +oid sha256:71e4415423627091abedfccd7a03e20afa27fc9f599de70dd82be1571c4131a0 +size 12616 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_5,NEXUS_5,1.0,en].png index 2cad2a5615..7d9397fdf7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0c113e7eb9d495ab2fa3d03a945fd6fac35810af2df4571703a8a3f2b55a079 -size 15018 +oid sha256:ca76be1bf0a76e93614c5bca704269932d690f6133f53d09275e2f08d3d27200 +size 15843 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_6,NEXUS_5,1.0,en].png index 36cf496a43..a30c830e66 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92da6c43ebe2da8ad035e0f03ddd5261491e846212396240aee8287d4f4d9a10 -size 22631 +oid sha256:4d0e5e459ed40f12fa3d2897a53928bc6808903bc8a36938e7954be41eab80b2 +size 23415 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_7,NEXUS_5,1.0,en].png index aa84f464a9..7186047697 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewDark_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c23fded123579979a9b6acf41def9a864ff99de6adbf224014c6d77a552fb2fe -size 24754 +oid sha256:adb557b5ccc00a8bbb5b605e372c892f6c0b7d194ed3e6e352f05b975e2e0eab +size 25525 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_0,NEXUS_5,1.0,en].png index a4889e3b27..2c284aec09 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6519ea26cb85aa1cecdab38d9f3cd54f3e986d33a9d411c576714fb070835d6f -size 12503 +oid sha256:c581e4cf30caec4e5039f11091417780ab0decf779ec7b31112a94fec0a596e1 +size 13524 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_1,NEXUS_5,1.0,en].png index 1cae9d483f..09e3c2f716 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1cdb9c379d685e7664c76f4ae7c712665de8dc8ad1f65441121dde4475a92f57 -size 35269 +oid sha256:e82b0029ac4e23aacfde992e36267eec369108d6ef9021138510d4b421017bd3 +size 36033 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_2,NEXUS_5,1.0,en].png index b0da61c4b7..b346f9e4a7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c5285cb82229e84a7cf2c1f2ce375060ec4f99577c88a9840b6db3b8d0966cb -size 33736 +oid sha256:9229d55e1a109abacfa82f066a48fa60606a0d0ed0d155634b4c31fb2c7be98c +size 34501 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_3,NEXUS_5,1.0,en].png index a4889e3b27..2c284aec09 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6519ea26cb85aa1cecdab38d9f3cd54f3e986d33a9d411c576714fb070835d6f -size 12503 +oid sha256:c581e4cf30caec4e5039f11091417780ab0decf779ec7b31112a94fec0a596e1 +size 13524 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_4,NEXUS_5,1.0,en].png index cb89527934..60d4ccb3c9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d33eeef612e03599f835991ad70c5f128c51e0ac49e67fff445b2e98ed3b71ef -size 12706 +oid sha256:9703d279e6401268fd066984d4cc91403c7f8f8e2c248c6ba2ea4c242694170f +size 13729 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_5,NEXUS_5,1.0,en].png index aafeea9a40..ca5503a3e0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b47af4d036f0cf1275773fb8b81d3cae847626b49f5197e2249df1d9d9be2cbb -size 16501 +oid sha256:11a6fd8151812c8733bab26904f53754899db2caeaa0f4f24ea010dd2d54b538 +size 17522 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_6,NEXUS_5,1.0,en].png index c1de14e11e..81bf0e3914 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aca2f20b99dbbdbd70ca437c06949b634d10c30701f7634cfebaf78593c34d8 -size 25515 +oid sha256:0083578f572e51de0a0464caf90ec68e8a82930b97e0217d20a550ce269f7519 +size 26504 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_7,NEXUS_5,1.0,en].png index 0833282d1f..050b78e680 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.location.impl.show_null_ShowLocationViewLight_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f612c17dedc33b8c4089828673dd58d6f1b51faa0c6df3edb1a0f3398c4c55e2 -size 27885 +oid sha256:1a5e11eed79ab3f92c130c3055e70e5cf0df2b22200e8e73e2180c0114ff5b04 +size 28881 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_2,NEXUS_5,1.0,en].png index 50a0dbc183..04e2ba64b3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:830ead63e3965435fc6fff2c8aa0c83d2654dd151972d8d88ad025dab2ff2902 -size 38536 +oid sha256:db2d848fded08ca0532f718f12ee9dab007d7bb129afcf949d26585e47efa054 +size 38389 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c94ec815dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-D-0_1_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6a15254ca331bc6d8ec6a6408b0a5a3d4fd26961e7c6025805ec7841a22e017 +size 28313 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_2,NEXUS_5,1.0,en].png index 7902cce465..a5a732b30f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e433bf7d2f96067dc0743e262d8907a45f0e404cfb307a018fe671dcee9d5f02 -size 37054 +oid sha256:67060b3bd4d09915ec4eda311726a8657cf09e455b5e8ca42631fa3b448ad5b7 +size 37049 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3152bb0b19 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_null_SheetContent-N-0_2_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0355effa8376bc88f9474f4d0e7f595708702f4fda5e39becf811fef10b0d16 +size 27081 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_2,NEXUS_5,1.0,en].png index d6ec63a2fc..80a6200a6c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5e4d1ba12c6372c7d53e3d6a8a4d11e2882a16d1dad0899248b5663557bc359 -size 26999 +oid sha256:7afef32647fb7ca666ba516c3f348d1fb168be290279869b73c3b64522e20301 +size 27069 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_3,NEXUS_5,1.0,en].png index dfdc242956..28307e18cc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e10d7e07f854017d9fe902649d66dd30b39624a0667023962004236986c30b2 -size 26540 +oid sha256:e2d9ea4c550094975fbdaa0f42c85dd871e414b1553c96b1256c8534233d6050 +size 26618 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_4,NEXUS_5,1.0,en].png index f2759edd0a..58213edfc0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57776151ae74ec98a498b59ee05841cfa50c6cb657c2706e54a7deba058a2a0a -size 26530 +oid sha256:2a216a2aefd132b3ffb64e89de3c8b2013595573783f7dcb9ad6e0f04e1cbf1c +size 26607 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_5,NEXUS_5,1.0,en].png index f2759edd0a..58213edfc0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57776151ae74ec98a498b59ee05841cfa50c6cb657c2706e54a7deba058a2a0a -size 26530 +oid sha256:2a216a2aefd132b3ffb64e89de3c8b2013595573783f7dcb9ad6e0f04e1cbf1c +size 26607 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_7,NEXUS_5,1.0,en].png index f2759edd0a..58213edfc0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewDark_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57776151ae74ec98a498b59ee05841cfa50c6cb657c2706e54a7deba058a2a0a -size 26530 +oid sha256:2a216a2aefd132b3ffb64e89de3c8b2013595573783f7dcb9ad6e0f04e1cbf1c +size 26607 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_2,NEXUS_5,1.0,en].png index 6ed0384bfd..3c82e866f9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e1503c710bf466e643720c73387d92b38c34aed007b34153dc5170066688dee -size 28759 +oid sha256:2333902757fee47ccdcab42188b5f5379fe48e2419896a4d525855510b7925a1 +size 28241 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_3,NEXUS_5,1.0,en].png index 0754809b7f..2281938ed5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a104e7f7fa15a31d9d6ebf30e6a8e59ba6cdc3155cc7e7274ef63ab6bc884473 -size 28164 +oid sha256:7c4bcdf48d7ba2f11cb7e09ad56fb1facce1722fe71b2b10ac5c4a86ed201e37 +size 27646 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_4,NEXUS_5,1.0,en].png index bcf6d823ab..605e036675 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da0a285163516046676b6a0f5d2660f03be7a9c08209e7198d2d5726df2cafa3 -size 28230 +oid sha256:fa25fe1ec3626e1726043987c5b727d5fd10660c4134d7b714616933a9e0d01c +size 27678 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_5,NEXUS_5,1.0,en].png index bcf6d823ab..605e036675 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da0a285163516046676b6a0f5d2660f03be7a9c08209e7198d2d5726df2cafa3 -size 28230 +oid sha256:fa25fe1ec3626e1726043987c5b727d5fd10660c4134d7b714616933a9e0d01c +size 27678 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_7,NEXUS_5,1.0,en].png index bcf6d823ab..605e036675 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.forward_null_ForwardMessagesViewLight_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da0a285163516046676b6a0f5d2660f03be7a9c08209e7198d2d5726df2cafa3 -size 28230 +oid sha256:fa25fe1ec3626e1726043987c5b727d5fd10660c4134d7b714616933a9e0d01c +size 27678 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png index 58ebf07374..efcd596b40 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79aeef6875265e119c3b4b97cea4d36ba3354ae52c4b94b69bbc09461b7bc319 -size 22259 +oid sha256:e67b171ca09fd2efb25338a20fe7af7464e0e1ecb1b02afd679dbfc7cd3cd7df +size 25646 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png index 6e91b56f10..a385dd8498 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8dafa9a97ebc77f00fdb0432c7b94272f6ea1873c3475353be47ecde95e8b057 -size 20670 +oid sha256:e92e96cc117bf42ffb9fa282bcb9e40fd51c414e1d432862940c0f8fb6600fd0 +size 23453 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png index f11cec4281..ee0937d439 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:906c643393af8d290f0635d7560eaa54339fc0498744f9ab8139932986d73a8c -size 9740 +oid sha256:f3ea303577f655368800debe9c40e1292dc08a20da7f2ea6ddddb87f8407b112 +size 10431 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png index eb5a63601c..e537e910e3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd57f885f161316550712dcb471ed213537a395539ed5b9975e17465310803b7 -size 9823 +oid sha256:b646c06e55b50b64eb7b566fbc0bcafa7f7e348398f87152d008a47e7448f4e0 +size 10736 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..aaab9d35c1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ea42394330d37a0aa5ba8940e023fc4454d902caf6d265adec0e11c8a34d85f +size 187744 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..65a27f228d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fb074cd58034c90010dc437fc01d6ea77a2a22aa07bc1d4cf0c1730b543b41a +size 186707 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png index 9bd8087f0c..84f2abf39d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15d14bf99af3cd0433870ebd6032d9bf4a45196e2ef1df7184cc55859a704dee -size 49008 +oid sha256:339e5f22a47e29b6f681ac169be3b65568b7fa5d1197b0e81fb68bef639ae5c2 +size 49033 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png index 170f3d997a..0fbe7c6581 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e47fc219bbd63b76d01a5e50c3e4c6b1b0a8b4ec40b08b13271d0b2673d8d5e -size 50932 +oid sha256:c439f506df4eaed4ad35148d1734630a89b0de3ccb097864b7ff853c679772c1 +size 50964 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png index cb9d6334b0..dacffc90ea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cacb91ffca97f21bbc19b29525813f58fc8017a858aa28ccd4e620b70a8cd9ca -size 46119 +oid sha256:f85799360092c7e2d02f64f32668279c4e0c39bca3d45c9db13d503ddb7bf753 +size 46162 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png index ec2787d842..40e0174919 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f1c14a23ee598ece6843a68d3ca0b1d1f725f53174bd493e27c6137de70c508 -size 48296 +oid sha256:b05475d6509b15fcffeae54bdebf434a529a99af5173c48992677a88b654f3eb +size 48336 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_1,NEXUS_5,1.0,en].png index a9f640a918..73cb512e55 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:367f76f13127a604d1fec6e86d56611b01765dc90462dd329cd567e55a52fdca -size 7827 +oid sha256:eb78a3bc85d7f9ead19ba7d2723b997af1a399167ce28004be8ccbf0dd8ce5fd +size 7736 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_3,NEXUS_5,1.0,en].png index a40d484b80..30d4d8b3f7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a14f96fc1119481ca8e7336ac0a65cb831a2bfff3e8e43270b0da8844f419f0e -size 7982 +oid sha256:499df8785b50d0bd1d5466b8a5b65ab1a8eb358f0c1341ad5956f5d1931859ed +size 7987 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_5,NEXUS_5,1.0,en].png index 4f4adf041d..95464be951 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9caf6d9b49cf7eeaa41025617a7cc5b1d89bbd7468f7ecbfc0cac73ca4a04cc -size 7452 +oid sha256:021d867d665e0fa77aa10a85e734e4a9416b96d627b21ce462f0e327c5e58333 +size 7392 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-14_15_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-14_15_null_0,NEXUS_5,1.0,en].png index 1e577f0904..56702cb5b9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-14_15_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-14_15_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0c32a81b2e3c751a7eb8c60ba71ed4b53e73fee920f02aab63d35fa492be87a -size 25266 +oid sha256:fbe739da995dca5700bcbb6a944c611e7c0b427d39b5e085345d8a7e93759e7c +size 25201 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-14_16_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-14_16_null_0,NEXUS_5,1.0,en].png index 5f41e9dfef..a61dfbb6e3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-14_16_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-14_16_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c47835cf1fc4cf11f183ee7e658d2a4d16a83b17b6a03750f16e660867442b51 -size 24948 +oid sha256:09864a80f5eaf0874dfdde8698b3fb6ef8ad95059f2d955e822f80009f49849f +size 24966 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 8838c1cbf2..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd761ca4ff5a70770869780c2575006ed8bd187ed4b3f197ffe1e4ea18d8390b -size 189559 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 80a5da9cd1..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8c6e3f139ca634c47c45fbeeaccf3e7e903fb8f83a88fdf8d7247455e76e4a9 -size 188653 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowDark_0_null,NEXUS_5,1.0,en].png index 9158fb98e9..722bd8c6a5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad2c0bb00b406a7fc3497117a89be7422390c7be16aa1f078c5be3791f8fcfd8 -size 151481 +oid sha256:f0ad6895582183b697732ef032f89a6a0cad32106c9bbf30925c390dcab65ee6 +size 151752 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLight_0_null,NEXUS_5,1.0,en].png index cf59c8c1a2..fb9457779e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb32728cfcad251e3bd2ddee2a40456f329e8e3a910d145b58a3323322c8a721 -size 156825 +oid sha256:676b62ab782652c45a7267aac5df12943fd181bea48964d28dce664884ae4182 +size 156619 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLongSenderName_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLongSenderName_0_null,NEXUS_5,1.0,en].png index 56d35e8f11..4011a2572a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLongSenderName_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowLongSenderName_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb071f845598b35931253304f4c9f4c865b5fe9f57662c03c88a0c94a7835d83 -size 18615 +oid sha256:82459b6a9a3882bcdba78e89f8938f3749d6fabb4673bd0a4cc953d2b7f069d9 +size 18215 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_0,NEXUS_5,1.0,en].png index 1bba8742c9..5bb87f7b39 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:860bc20fb4c847183290adcf1f76d1a382b0dad4d3f11a987924a816e6b5609a -size 62000 +oid sha256:f6010f8c84263cad47587a20c9d7da64aef2a8520c01765ee4966e4b6bdd8327 +size 63371 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_1,NEXUS_5,1.0,en].png index dcb828c9db..27e6956a21 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70f4e9bff2565317041fd037c2a0d9bf596b0c67d04927e64e89f7674f4a9966 -size 64038 +oid sha256:6e12464ce600a1cd6ca6ea9dd930c2235e033896b336109e094b84dcf2d4e624 +size 65459 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_2,NEXUS_5,1.0,en].png index bc89b7cde5..21092833ec 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:474628a1247dc41f3b17dfaa0174fd47b4468f075fc728d14419b7299d7952a1 -size 68584 +oid sha256:e74d57cb6f5cda78086989289d0f0543cfd3747e1cebec0bd656bd5be2d8bac1 +size 69944 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_3,NEXUS_5,1.0,en].png index 740f11b5bf..4bf217a2e5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d30f7b8de3ba0b44350eff2ebb8cd0d2572b435208673edbaf6c9914cf79a703 -size 70541 +oid sha256:9baf1ea5fca980be4744655283bd61de650c9f8399a9f3441a9a3f9c5f43566e +size 71852 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_0,NEXUS_5,1.0,en].png index dd47e9ed26..31b384c132 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:705c1bdcd680cd85759b9644f266eb0c1d343f76001bea1ac667b21ca9b9e390 -size 63441 +oid sha256:343d84dc714bb672084362155362b04936ae491f437e492332bff553d7776078 +size 62478 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_1,NEXUS_5,1.0,en].png index 2744202175..7fedf5139d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8307f0003cea667faf6d8fd57b4b650a00c6728d7bebebed22d7991a0c2f158a -size 65924 +oid sha256:daee8f18d24bcc07cf8edd9b902b03c3fb406199a925606b1caf34cee08ff9a1 +size 65076 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_2,NEXUS_5,1.0,en].png index 19023b2f69..7bf4f6f57b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b519caf1e062d1fd9f3f6b0b122efcbe77775508fe272283cda71cc76eff9b8a -size 70408 +oid sha256:f0cb7b8bd23784f2323ab61baad1a910b343fc498ea61f7364edeff9111992d0 +size 69478 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_3,NEXUS_5,1.0,en].png index a7f9730ab8..ab0a440978 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowTimestampLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e15b3ac5a03ddcd0bdd72528541a8f95e4fd40ccfa3bad2269b878890dc4aeac -size 73123 +oid sha256:54a52c6de5fd501e5c4f260343dd9ec582b0a1dd094a18f43f2fc4aa2c8acfcc +size 72145 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsDark_0_null,NEXUS_5,1.0,en].png index 6052944bf4..ee5176435a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5edcd092587b8c006f32f5da93b2658ca0ab6f0fecb17f617b93cd1fb5f4b03 -size 81123 +oid sha256:21f0f4844935aec3102efc6bda98be54f48e8dedbdc539907199e5f4edc3a9b9 +size 81343 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsLight_0_null,NEXUS_5,1.0,en].png index 757252b315..37620fce39 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithManyReactionsLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40fee515ef4e1bbc3cda0ccedf7b9c6fa48e040db79de5c9c40bdae6461cd018 -size 84907 +oid sha256:155baab65e9a3431f7afc8320e0b9ff12454a5faaef6c34701e50e7b0d1c3c0f +size 84763 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyDark_0_null,NEXUS_5,1.0,en].png index 7484490f6d..c39b41db87 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe770a92f211c69118868e0fa8c451680ee30f64c9ddc99c426c13690a4b8c8e -size 127240 +oid sha256:b991a34a137d82ff4dedf48316a05ebbc008233984b11af70e64889a5fb5eda8 +size 127438 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyLight_0_null,NEXUS_5,1.0,en].png index 8b506e4e44..a6500ca748 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventRowWithReplyLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe1687609b8da30cea710072927512219d628e40b74ca53751c0560b5c72a9d6 -size 132412 +oid sha256:43b6514716d18382c8962520928d9d2ff6d0f665b2471e8a24e5d2e44a25c88c +size 132295 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png index c1d740b8cb..4ba636ed5c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f689d80043e7d5121d072b928c866a3bde0358b5d5df06e1d4f0ceeb9a11dfef -size 56344 +oid sha256:bdcc950b0ce924a73ae742f0bb3fc7279de46f36ba01d377822a2a14b6f90343 +size 56239 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_0,NEXUS_5,1.0,en].png index 8474499ecc..93b0744c82 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af68664850a3604c8df21d0395a0ea06ad7286a5ca6889c4a2814e61003f14a2 -size 52312 +oid sha256:e40acbd30f8d9f815161b6a3ed1646733568015f03ffb0f24efaf6ec7fcdad6e +size 52145 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_1,NEXUS_5,1.0,en].png index b9c07b4b9e..021bf7cada 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1dbc2fec7de4e8ec1cddce4f2c8522b74c3ff4c8688170f8188412ca7dc7b561 -size 64049 +oid sha256:fd2166469e00817c11b429c3e4c8508f56aba93696bb182310a277873e8ef080 +size 63847 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_10,NEXUS_5,1.0,en].png index b2ab3a2244..f3e8bdfb28 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9329845bb7ddd9cbdf6756061a442d5a227b0acfffc4d372570245422ed2766e -size 50300 +oid sha256:53c5d2bce5d3502d5b09610faefeb68d20514076ba4a8189f7fc8f41c8e3174c +size 50150 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_11,NEXUS_5,1.0,en].png index fd56993e7a..2c4900d9e7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa69e3166a92a13a1b263f4040fc2431a33d52a6542bc809fc026e5a7dad3cbd -size 67359 +oid sha256:1223ea8bca4d42e8f5a6cace4a530055df5f6f0eea02c62e9b35104aff7408ad +size 67145 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_12,NEXUS_5,1.0,en].png index 9ffdb4df92..3138d3ee2a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ef257207d4497ea056fa37bff1ce9bff7793517d1ed83bda82fdaf2792147ce -size 57441 +oid sha256:48784809032f7474179742d8cf83986a366c62e1678a2d2f4a94e09856ff6302 +size 57274 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_4,NEXUS_5,1.0,en].png index 411213a72b..ef3c05fdb1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b03bb08a6cebdc1adece3deaecc07964d29f893d151ab693b490af1c9b4078d1 -size 72362 +oid sha256:ed11b0598ea4b2a991bbf6188372c33f35a3a661785ffdaefe4ef5257576e460 +size 72181 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_5,NEXUS_5,1.0,en].png index a41c9434c0..83b1c4316c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:160f3917d6918f0f5be4bc39d537528dd654c8e26ac6e3d5b8020bb2ef132ac5 -size 87912 +oid sha256:5c1d4fa3e1f403c0e5cd7583f9204759a0cba7dc63b4d1bb5d6a889cfcd10ab8 +size 87675 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_8,NEXUS_5,1.0,en].png index c071274718..0e34e05ec5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0725e61bc775166de6d82f5730e223ff9d10f6ffa3e7598a17fd3cc4c2af434 -size 53848 +oid sha256:edcc8b013786ec7b316161deb347278085fa196520b2d4acee74b5e2b426efc6 +size 53677 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_9,NEXUS_5,1.0,en].png index 30335490d1..89f5efe6e2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-D-2_3_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bc72ead6dc9f348f5bf2dd1cba24e2903c990a85414f6f155437bc0237e7ddf -size 66060 +oid sha256:d9ee29be03d3e25834cfa50194ae8280b09fcac2dbf8fc79416638b36ffd07eb +size 65852 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_0,NEXUS_5,1.0,en].png index 2b0a249003..a9b5cc33de 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e28eec0e60cef1d1234ccae1031413105e2291f1367537b9eb36b038402dd29e -size 50314 +oid sha256:bdd5697196181f91a5d36f8ec4170d5d8668052f67478d22f0c2f1d39ed3cf37 +size 50397 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_1,NEXUS_5,1.0,en].png index 38fd06b5d1..15a02387b7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef4295d36c3fc110ba5b04032e4fa71f39879d05e368f6eb88fadba7ffd501a5 -size 61306 +oid sha256:b029a486993477abddf2cd602c5b935dd1fa4b22d1df5ef918c428f5714ca6bf +size 61327 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_10,NEXUS_5,1.0,en].png index 066d89473d..7b2dc3f516 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23703da549937fc871388f12a07f0aca70095273cd90f0eb21bce9e89500bf81 -size 48528 +oid sha256:d9c28c48732089f547388c779922c3cf3dfe1c41d5d38df93366ab6c0a546327 +size 48617 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_11,NEXUS_5,1.0,en].png index a08f83f399..9e476a55e1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:015afae65070b8df055542730915e029dcf36493ed491f23a801861547cb7314 -size 64378 +oid sha256:56e14c49836bd57eec889ac66bf0016da7e0c5bcdc9c51309e680ef77adc12ba +size 64406 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_12,NEXUS_5,1.0,en].png index 710a494e69..b43b1872c1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc3691993fab8d245c72ab1d3318f73c36f02709f4a7fa3abb664209947bb257 -size 54972 +oid sha256:cb26b5d108b12720d523d2912b4f71a7799322565bda95087fc8cf52a6bfd2b9 +size 55070 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_4,NEXUS_5,1.0,en].png index d51df54093..2a2361797c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33ae97a6acd31df8419b2066b86d97628151a3aed0464abdec5fbcb4028e3f9d -size 69435 +oid sha256:27d7a635a477aa1b2a4d0ae657333ecf55eafb9a84f29e5494526a64e7c085a0 +size 69525 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_5,NEXUS_5,1.0,en].png index d1f3020e18..8507c48fa6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74637c58a18194e1d3af5d3be5755cda81107216732cf8064c49a3dda346c487 -size 82836 +oid sha256:8776d4f031fbb8c934081ac29b6b6bd5746bdd67edac0904e1468b76727ef541 +size 82893 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_8,NEXUS_5,1.0,en].png index a4e5f88942..be7f3f8601 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:354d3112e77e3839dfa894f5baffc68c61795592d3f17a3401595dd47a1c63b7 -size 51648 +oid sha256:60858ea74ed5fe4ef865ccd3f0290edd32bf87ad010f5f7691153491c163c42a +size 51734 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_9,NEXUS_5,1.0,en].png index d997157e7c..d69df0df42 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline_null_TimelineView-N-2_4_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b988dd6ede4b8e7fd4d488677bf1cb2ab46c2a50f30551268d1573b1466633c -size 63282 +oid sha256:4745bb37c991a9a90996a9ce8975639cf5fcb2d8981d0147d6a3445650d63620 +size 63304 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png index 3f327480f0..a80b2ce36d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ebcbfc34ae17c122d3372d3cf5676c95d1c82db8c23bec467490b1787e267bc -size 51288 +oid sha256:2b55dbcb731dfc40a1e100e8caf26802f759b4bcd186dbbc4432644db08f8316 +size 52141 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png index d8ec14e7c2..03f50fe82f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83d62d8eb4aadd86e7aa07076df9ea615f2f18b6520086bcaf44e9ae086c870f -size 52540 +oid sha256:d5953ae4bb021c39157b4118ea628ca9bb2277f9672749cc6313f0b378d31cb8 +size 53312 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png index 44c28c0a3f..eb75a98270 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:937c9762afb8aa96715acb5239364f42ed0bc2afba764a2f15eadb5782803286 -size 51523 +oid sha256:fd991a67e7d6c1169e08e62cb74ed6fa7d12ad7f91d69bbfa778e0053efebfec +size 52284 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_3,NEXUS_5,1.0,en].png index ee186b8130..3c98269fe1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87770ae87e7d8a74e04842c47d55c3554c348997e2c03fc65b67add3db7980c0 -size 53931 +oid sha256:1d6ba255d9b1aee8d560b51f2bf0c1d4433c63502c83f6cf18ef8eaa598f05c4 +size 54071 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png index 951de46085..35d5965ff3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8737e6006d60b361b5039c0989fd142554b09ce9847dc649139cb7ef9f901d9a -size 50597 +oid sha256:dc662d180d1766706f249f1585fb100f5933206b65d4644fae65abc6a121a633 +size 51331 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png index 0f04cd7988..b2e621fc71 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d71dea06589fdd8b62843d6898a74eff256ed0206ed8ad1ddc689f69d145a72 -size 48869 +oid sha256:db0107e648e2acfc253d83f5e3b2f3e41de1abc9bdc4922dfd923ad4e50b8f5f +size 49700 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png index cf1b87bde4..65cdf717bb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bda5e19667a7acab4f72ad4dc10fead50f1f3373bd67b6db82eee50c3b53fcdc -size 53096 +oid sha256:3eb1479510a8cd04d133265e33ea15115158ac55c93387d1dafa93a3a3a858f0 +size 53620 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png index a3785ac776..a5959f6d60 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8188739ddc45610d6539592e7aa62a679436f90248b3eaa95ff76b7da808e6e3 -size 54453 +oid sha256:c0d5272b14b347f18b2d67d5fae6eaf507b8d6f7c9d361317bb6b8a062e63ceb +size 54899 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png index 0be5726cab..9603a65588 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14d058d0867360a6648876572001087835a06aba20710ed5381bdf393ab5e3df -size 53409 +oid sha256:d421656ae8ad316e65f406c74dffa2ceb4e47b868b7f033f9cc4139e3e6be788 +size 53909 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_3,NEXUS_5,1.0,en].png index 4a53e5dbbc..47d15c2bf6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2485a5713950429ea18c4a7275c2b86a214e45f100116a93c1252b4011aa75a -size 55956 +oid sha256:fa96d668bd220fb1e47850c86c7ded9fe99b14b9704f1a5662cfde1e932c5299 +size 55443 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png index 82e1cd0fea..8212494315 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf7a41495ded2f16c86fd293096234ff730dc9bc84814aaec98b01fb9eb56e69 -size 55402 +oid sha256:dff46ed537c81602be0dea8782e37bb78669ee798ec4d422c23ff29cbbdebc17 +size 55927 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png index becdf3d51c..cbdda6b794 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87ab35c42a7b42a2625625bc99575ef37d54b84256554ee2e2b5bab80bf4700b -size 50465 +oid sha256:7e162503c27609f01e05bb2c634c2d3123abf6e786ecfe763c902d400ead050f +size 51283 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..63eb2c9cdc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2c67243e8def6f8d188eb918b0dbd57dec3aae03fe8fca90ef5529658de537b +size 327304 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cfc1433bcd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84e5641cce0113690d0d626bd3b17e8945728b3835a06b06a645418d12a05022 +size 421112 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png index 9bd8087f0c..84f2abf39d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15d14bf99af3cd0433870ebd6032d9bf4a45196e2ef1df7184cc55859a704dee -size 49008 +oid sha256:339e5f22a47e29b6f681ac169be3b65568b7fa5d1197b0e81fb68bef639ae5c2 +size 49033 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png index cb9d6334b0..dacffc90ea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cacb91ffca97f21bbc19b29525813f58fc8017a858aa28ccd4e620b70a8cd9ca -size 46119 +oid sha256:f85799360092c7e2d02f64f32668279c4e0c39bca3d45c9db13d503ddb7bf753 +size 46162 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png index f148a727ae..e700e58b7e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a9ccbbc0a208ac398f07f0070a43d0ca5ab67ef322b274b641270a9c21a4a60 -size 48972 +oid sha256:85fbaa916a38c9b75a527dc3fddded9e9d06a98ab20f9f3eb74596c24ba4b5b7 +size 49105 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png index df2a5c30b9..26b2576508 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5736eb1d232b841d62f9d96070fc9b2993453652f5d7c448b6a819db9543615e -size 45756 +oid sha256:cee9e96cfefa6f84fb924857a92fd4ba04bbd196d94a6345bb821ff769fbd56d +size 45889 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png index 7319bf6694..41f7fba3e5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2264139761f7fe3977ece3c9a7d949ac51223dd209497b7b311987cba3e5a069 -size 47154 +oid sha256:53cffdc5ea12b16db3f87ab12c28fede82ce5a744c222c9f19e5c35ef751c583 +size 47179 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png index 3a129abb0b..cb51eea54a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3ff35998ce8558b5a7af84e378bee039e36099084b357fffccf329a4983b035 -size 43551 +oid sha256:96d696014b24a2a222ba331d754d6048063a9ec573158edc41ded92ed588b60d +size 43593 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png index cc49158545..8e47757aa3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:155cee63d3e031c912786b95bcc8e28dc4d1b296f8f0e4c01bd96398bb2dd040 -size 40623 +oid sha256:636e6c2494e69d7f1383e0c2ef178db484cfa6802c6714ee20a66aad10f4421f +size 40500 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png index 127289d30b..96707cb507 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b682b0025c98d9d37ab8dc67cbc8a637f672ae2bf9e4a26e48209188d612c0c -size 36455 +oid sha256:58ced9ebe2a161167f5515efa18b87eb02c340b75db47af84f91eab0866b113f +size 36370 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..acf8d934bb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e55b03652dc0149cdc2623fa81b678a68a8080f57043c1f71cc99565e44c98e0 +size 35025 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-0_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2a8c0680e3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-0_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e38e363ecdec4e70699204a83a01ac3e4a840f20f1ebc84bd014b49e0e52c77b +size 31291 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png index 29125d81e3..9dbdf12053 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ecff1b486e8aca39a5b7c81139e1132829d9baded8888977eeaa88fd1a6e5f2 -size 49665 +oid sha256:e6b1bfbd1d8c29347433e0c8a1037ea219b0935f9a74196f4c96952d144417e9 +size 48845 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_1,NEXUS_5,1.0,en].png index 29125d81e3..9dbdf12053 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ecff1b486e8aca39a5b7c81139e1132829d9baded8888977eeaa88fd1a6e5f2 -size 49665 +oid sha256:e6b1bfbd1d8c29347433e0c8a1037ea219b0935f9a74196f4c96952d144417e9 +size 48845 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png index be5e358d49..c1ebd00c8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a2123c483c9609b3341c762907e1eb5f50052faa3731454ff0953cdb862809c -size 54357 +oid sha256:6814348aebbbb561fe42ae5e7c5ae9bec77cc7838b41adbc5158954b338fb0d1 +size 53755 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_1,NEXUS_5,1.0,en].png index be5e358d49..c1ebd00c8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer_null_DeveloperSettingsViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a2123c483c9609b3341c762907e1eb5f50052faa3731454ff0953cdb862809c -size 54357 +oid sha256:6814348aebbbb561fe42ae5e7c5ae9bec77cc7838b41adbc5158954b338fb0d1 +size 53755 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png index 415ea715ea..70697459fb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb2dacfe6f7e3a3ea7bf0fbe39cd1e0c4d3d0b35c8eefffb3af4fd02426c496e -size 41504 +oid sha256:1e047b2d8c1e780b6b5b17d697ef68329e82965359168c02acab59a641f7c3d8 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png index f2c8a21ef9..8f7840e99f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ab65a8d762003ed2bb22efac17f0c4c04e0e8313292e9777c384fa4fd2a0b96 -size 40803 +oid sha256:4415059f5b3bea2d68cb19bba725004b5ee987ef3480d0b92bb8616df143fe2e +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png index 5b8d2fed5f..0cbf4da1f2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:389470ddc9cecf1134d18f4de75e9307632af041c5fa82f786e212258616ebeb -size 44408 +oid sha256:9bf4008fe7567a29bdc56054d66a2e46f44d4219d7ac4405898dc28844987b4a +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png index 4631940184..800d8b2efa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c9adadd46650c0c8c9768c5383ae6cf3beef220dadb42b49a4df935058061a6 -size 44508 +oid sha256:1939e8ed90c936c52ec73abecc6da3c879d42097aa9463c780bb42cd337d78db +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_0,NEXUS_5,1.0,en].png index 8bb0a53a37..c53281b9c1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9048da25ad83e544de8dfa2ec910b7650e34c78a0864e0e1f9644fa18a0ffba8 -size 13144 +oid sha256:115262f8a16f26506139c7b8f630e85520639ae5dca424028bc9a721942aebc5 +size 13168 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_1,NEXUS_5,1.0,en].png index 8e68fdc6c1..658eb46874 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58660c588499291f198046058bf725844bde74a7db4c371d29ee43fba2780e04 -size 12356 +oid sha256:5055b4cc1bdf5a4b00e901e5e40c0cdcc415d22d7154ef38888936928100928e +size 12365 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_0,NEXUS_5,1.0,en].png index 754ea0cecb..85665e2302 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d4ee26a608ba57aa6e9eca05d4db22a9135243a73faf767cdc10ff0252079a2 -size 12887 +oid sha256:1c4ce3a10a671acfb2e619356cf792b5f11325a55edca30c57813aa7823e878d +size 12591 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_1,NEXUS_5,1.0,en].png index 05e82479e2..119c55ec0b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferencesLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8daffe981e83c5a81b60ec1d00d09a12fb3a67d3bd957bab5b0b924480330bfe -size 12952 +oid sha256:28f315ebf657e95e666b6599b096d1779b1c9f3cefea3e679a9054d80f89d53b +size 12412 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_0,NEXUS_5,1.0,en].png index bf92475b4c..e0211eacbe 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9e26aa62cf66b8fc92025cc7cc4cb4f1a8f298edb1b7ccc2597cb8fc16cc876 -size 29888 +oid sha256:19ac99f81f3e72f0492d33278a9e549763493ea7ff111412821c62d934a6b9f6 +size 29522 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_1,NEXUS_5,1.0,en].png index 360298acc8..e548c3c81f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d05e01f9c8d0a6173debaf8b6a8491e705b19b3dc0ec2ea133d6f59a210d5388 -size 23442 +oid sha256:d15154d8ad933acf9a52a73fb650d28489d780cae71b44c7edf10b682cec1ff9 +size 23143 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_3,NEXUS_5,1.0,en].png index 189dac37b4..487800c8dd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf8fcba0dff3f86fe7334b829e710f31dce67b5ca207474ac6b1a138568cad9c -size 28824 +oid sha256:0958e2a99818be0125b700df470a944a3202e0336d440b073cc73ccc6305173f +size 28482 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_4,NEXUS_5,1.0,en].png index fa4f813321..241c7e1400 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6485d6e4eb3429140d73f54d99467b2058628681f3588c1462d59390ec7b1bd1 -size 28562 +oid sha256:da07b8de2fd24e9e6bc7fea309e4d5fce68f2143316a5940a41f4270e9d48646 +size 28099 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_5,NEXUS_5,1.0,en].png index ff4c6d9a78..1de6e8de96 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:922a88411d9c53be79d4c755c977e3a0102fb5a91003c57084048b1597ac924b -size 28878 +oid sha256:1a7e0644fff4f219712719b4837cf4e5db5c8cf4c9ec1e21fcd1362859a155f3 +size 28607 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_6,NEXUS_5,1.0,en].png index 6a122cd7db..c078ce120e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8911100294fac69ccff4c6d90df5b72e4c45590e369fba3cafca77a0f31b4927 -size 25049 +oid sha256:8444587bfa6ac65c6464b102bbac0bf679fecc0b193cc664540f39518b30c25d +size 24785 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_0,NEXUS_5,1.0,en].png index 4bd5c9f12a..dc1ba40727 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ee4fc0d07c55e3729d2cfa81f1304e3553ef9927dc4d7893c9c893b3e888af2 -size 31129 +oid sha256:22b0a03bec31400bd88a51c160bf63132f0c8f7e1cf02041a901778c3f1e27ef +size 30856 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_1,NEXUS_5,1.0,en].png index 703e90febf..cc49e4274d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ebeb7f5a99059d2a90acb98f6453ae4b9292ccd57a5b17239a58c6e87d83cdd -size 24410 +oid sha256:8308dc746a7a51a7bffa49cbf1436097565a175298073afe3d94558d77307510 +size 24145 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_3,NEXUS_5,1.0,en].png index 17c8b03f44..ce4af21fcf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a90489e5bf9bf1509f17f4f5e36c2b8f72b15e0b9120c29393e6f9d394a58d47 -size 30842 +oid sha256:32f3030c3bfb1bc9ed4c4378698df073cb29285477538c81592c38668953ffc8 +size 30562 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_4,NEXUS_5,1.0,en].png index 2014be77c7..090bda7bbf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88887436882d7a079ea677bd6a3a947f44c961b1715ceca2ff3955dc178ea675 -size 29767 +oid sha256:0587640a3ffb707c94d25da38d9f327500c3d3a96d0b57ba92a3c4201014fc11 +size 29448 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_5,NEXUS_5,1.0,en].png index abf7196fb4..03d72aa037 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed04dcb3e6b3dd792b6500573ee60a64fc08fc6f767e2bb90f54518bcdab385e -size 30862 +oid sha256:1017fcd62a71f7a2a82dbee3ee2a5233747eb3c888c4b6302f65d9aa02c5492c +size 30673 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_6,NEXUS_5,1.0,en].png index 4c4f348533..acd3d83443 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.edit_null_RoomDetailsEditViewLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7470928e4f59c48239d7936805235e3deb04fe5c242d4a5c173649052dff1f5b -size 28255 +oid sha256:4e01d7e598c771260bf4ef91d0ae99081e80f318c1e0300cda3dc1ce780c80ee +size 28078 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_1,NEXUS_5,1.0,en].png index 56593ba4cd..56e17f1b80 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a07fcc59d425bdbe449537e69448fa4603a69e073c88c2e8b8bc6f1719cd18b -size 28638 +oid sha256:44917f87510b19e963ab02f1324057b694815af1437300c8c4763ba617848dc5 +size 29113 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_3,NEXUS_5,1.0,en].png index fed3463750..cdc1d79045 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8321e14c29391b34ab81e6b85f8e095773dcb00e615329e41032a057f2a0d5ab -size 26574 +oid sha256:c5fc0b0ad8f0b5524db430195f56526cf4c0d0ec48ac3bb90e7f339e4859507c +size 27041 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_5,NEXUS_5,1.0,en].png index 02e6fae1b4..ce2912e13f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57eb5eac361c61478e9352b7cd92c37212cccad2f1de2c509f06097eb0ef999a -size 45353 +oid sha256:6d289d744e4ab63c6f9fc679b3e431ca4a8a3e905b0a34f4dd4b6d8ec5f345b3 +size 45347 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_6,NEXUS_5,1.0,en].png index 7a817b8991..ae4b8097c8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:783ad0f124f4f385eaa556ad80daa0621fa6251622c16da8be47dca1937c4ce1 -size 38600 +oid sha256:c61dfd03185e0eff646f3e6d4c433881825299129d979369645d689305a6234c +size 38500 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_1,NEXUS_5,1.0,en].png index 9f479f3f6d..f4b3fc01a4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b013fa7cba8393ec0defe267dcd64427ed2023ceb7a306ad96b9cb03f63a687e -size 29609 +oid sha256:d1071bdafa2d9c1aa08e81ecc4b7017957725eb90d0fcb23f455b5f80f98691e +size 30568 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_3,NEXUS_5,1.0,en].png index 7fee50f122..205a77a3e7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a00fde670a39c0cb6e4e12185f9790d3394be743b034fdd1478824d594812f48 -size 27550 +oid sha256:e391357db9d24d00fa2dcadb4cad590c7e71971b113ccccf7370edc396ae4da7 +size 28171 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_5,NEXUS_5,1.0,en].png index 7fd2027c56..33303a2529 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2dda676216b8f97ef36f02f7bacce4e605bef3c2fa521fbcb59745f27fb36a34 -size 47139 +oid sha256:6fec1683e88a36fa4c95f9bd83b01030f9b375cfff2034f723c81c31c6615f5d +size 46229 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_6,NEXUS_5,1.0,en].png index ffb9c0546f..e50fd62303 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_null_RoomInviteMembersLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc2746f63bf1f5baf6b0a2595adc67063bdfe958596be405ab5aa2385af0cfd6 -size 40697 +oid sha256:03881c5bc6ce8b1008679cdb0db2e86f713db25c9bea1eacecb713fc8274b91a +size 40281 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png index ab649faf22..74a3f56bee 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cc10ed03f4190d4d66f0ece4a4213db4fc071a7bb7867cee3bb1e17b44451e9 -size 19589 +oid sha256:33c80b7d24e8f0ea86d22ac1fa92c72926edc2d51ede177046787c2d43440790 +size 19399 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png index 07e172f24b..54685640c5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6234b7193211bb6b27a174d06ff9bf9f73143c7da668c113a2ae3ac6606b8f6 -size 17393 +oid sha256:a89af486f34afce8a092954f6adbc4a4a38154a8219de751982cddaeec6029d5 +size 17181 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png index ac8594070d..4d2060d15e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8b06ea0eeba1a6603bed919934c6869709f948d1dbc6b8333f10be7493a7b97 -size 20005 +oid sha256:235918bd815cadfd3f202ab259081ef48da0e2fd563547ce7173b098c585bca4 +size 19814 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png index ab649faf22..74a3f56bee 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cc10ed03f4190d4d66f0ece4a4213db4fc071a7bb7867cee3bb1e17b44451e9 -size 19589 +oid sha256:33c80b7d24e8f0ea86d22ac1fa92c72926edc2d51ede177046787c2d43440790 +size 19399 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png index ab649faf22..74a3f56bee 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cc10ed03f4190d4d66f0ece4a4213db4fc071a7bb7867cee3bb1e17b44451e9 -size 19589 +oid sha256:33c80b7d24e8f0ea86d22ac1fa92c72926edc2d51ede177046787c2d43440790 +size 19399 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png index 5e20da1ed3..4a93a6cfa8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewDark--3_3_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4cc58182afd5e7883e2299e882842e252d9d650ede8d692cc5b55d86f0eaaf -size 20597 +oid sha256:cd3658bdce62cf7ca62c9138271df8b2d44ad5c0229b468fb40049a575036070 +size 20418 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png index 3a611ff2b3..c0ca95c0ed 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d06cfa869f95b907f72152ca550536755cde19a9bf14b083ebf4c162208514f1 -size 20037 +oid sha256:342a89e74b6067ff1fa1213edeb76dd05099d91083b6be92f48aff6ea6979393 +size 19817 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png index db428f07ef..42407d39fa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3c0aba4502aeee24df94fb526fde10f7b8b638dedf6a6330babfcd168845d53 -size 17739 +oid sha256:c276773e89fbaba8b81ca5ace972364f2c4de24f670ac8a39f3a66c243a56699 +size 17517 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png index b3db394d03..bb14682c7d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1217f1e8fbad860bc099746765e7321553338b2e49e55458c55ba331c179dce5 -size 20494 +oid sha256:b6bdeb3f25b2f7a98ac55e3ba32858bc2fd2154f1f211e50a9bc36ac1be3090e +size 20270 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png index 3a611ff2b3..c0ca95c0ed 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d06cfa869f95b907f72152ca550536755cde19a9bf14b083ebf4c162208514f1 -size 20037 +oid sha256:342a89e74b6067ff1fa1213edeb76dd05099d91083b6be92f48aff6ea6979393 +size 19817 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png index 3a611ff2b3..c0ca95c0ed 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d06cfa869f95b907f72152ca550536755cde19a9bf14b083ebf4c162208514f1 -size 20037 +oid sha256:342a89e74b6067ff1fa1213edeb76dd05099d91083b6be92f48aff6ea6979393 +size 19817 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png index ee7132040d..4f768927cb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_null_RoomMemberDetailsViewLight--2_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ce5ba511326e7641dfeb27dcfb94e372a75c6265201248b52d88d977fc8e61f -size 21056 +oid sha256:427da8bbf67861ee3214540b57e573ff8b2879aaf3639769d4b9fccd591676c5 +size 20881 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_0,NEXUS_5,1.0,en].png index 8affd2826f..22ab8f88bb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3dd75d76dc7e0202e2ee3aa97c81de15f595776402c105cad3dbf55db44720a5 -size 38338 +oid sha256:deecef6a5d8161828745db707304195d360e3454c1d092fe92c57cf87f55cb9c +size 38196 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_6,NEXUS_5,1.0,en].png index ac29bf0749..de021bcb26 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d002a12bc5821e0cd2838e35d8a74ad9408609acfc8268b002f2066dee143ebe -size 24925 +oid sha256:ae2b5be95736d02b76b34d3156b61a626edb738f3dd64f75c1a6c36b9413df0b +size 24968 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_0,NEXUS_5,1.0,en].png index fafd4618fe..5de307f4aa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:affe57ae0e4a387770f7776ee22be35cdc2bb9fb82281b1c9efdc30e302a64e1 -size 39529 +oid sha256:c6312be03f23e39df742082f9886490107939fef35971c4476f3afe7285848fb +size 38836 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_6,NEXUS_5,1.0,en].png index 4dcd4e578f..4f504958a5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_null_RoomMemberListLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c4c30c30dbcbf0da1238096ea9bb152f4a691bcbf02f2aa0ff31720309a8313 -size 25608 +oid sha256:c526ac195ee5c2d3f691a0fd13ea10a1efdd10b1c56bca579885d90d2f1658ac +size 25308 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png index 27b587680b..19c44f9bfd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e0c2f56556261e230a67520883b44701df6f08cf8233fc71a2940003844faa3 -size 51945 +oid sha256:e7767c8e93ea26db11a587befb060db13cb437019fb4c78a41d1da392631c322 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png index 40e4c913af..6fe16e5227 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:732cb944620c44a48fcbe3a70d5fc5f427283c053bd25bd4a4a303d6276fa0eb -size 51283 +oid sha256:1db1cd829102bb50c4310340574d0c2f77decc5e8ae6ba65ca0bad8a0208ff19 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png index 88da6c0c95..ea7e406b74 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acb6b76239953cb510ac7848e449782022720c1495c93c87326d162c7bd4c55c -size 44390 +oid sha256:11980e20c7f8d12239a5d0b70228a94210d2ce36237568a47557dac023869122 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png index 01e9b8640d..530f986564 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ace15551dd4cbc5420182ed7165074dd1bf95c379f7f98d382eba928a766d1ef -size 52747 +oid sha256:c041251d76ced4b502bb67ad0948c22d7ccdfa21418c19608c57da0d22b2f677 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png index 79f29d60d9..3a813fbda8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f5367624ae4b43f93d54d261937e0118110479564ca6f89f67949596951d243 -size 49777 +oid sha256:f399a87338988677a7e7036cbaa9b13772cae80d51b08a1c2aa6c55968ae909c +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png index c91b29060f..c494f78fd7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b86d2aa08e76a8b9cd4c74a03de6781683088116a0eea422787c1c3344a843b -size 51343 +oid sha256:6a72f7d9deff8576d0a748d3793d4f7abbbb6341372bf9c2645914adb279cecf +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png index c91b29060f..c494f78fd7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b86d2aa08e76a8b9cd4c74a03de6781683088116a0eea422787c1c3344a843b -size 51343 +oid sha256:6a72f7d9deff8576d0a748d3793d4f7abbbb6341372bf9c2645914adb279cecf +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png index c8a0cd40da..ac19b3c914 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99348af52a09443f557621ea57afda20ea76e572f47553e313a877d0486bc46f -size 53068 +oid sha256:cb02dc32e5340e25f928e5c24c2f4975ac439662582842193f40eb37ce9a1baf +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png index 1b3b6c3759..78e138e2e9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13762c8b336a89af7a924eafa3eea24b4f193fcbd83564a6dab716d80cf18a0f -size 52166 +oid sha256:ef2d4fc3e58ca6e0d25d7936f6a318eb54698f476e73f616a0e652f464a8afd2 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_0,NEXUS_5,1.0,en].png index 75e0cd672e..7e3bcfbe2f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:674f0c52fa19856603915157a4578db1235e109417a903b755e1595945adb3b9 -size 53383 +oid sha256:9026859eae0ea32b49a0f5c58729de98fe3ccdfead6f541231c81aeb2fee3d59 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_1,NEXUS_5,1.0,en].png index 04586f43c5..3d31181d34 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80cda3451466a778252da05114c6f1db0520641d5b5a3412ed8fc43b593cfaa3 -size 53840 +oid sha256:e9e752cec376724d4cf0cebebdf627c19f6d253317b08e860d500764771fd0c7 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_2,NEXUS_5,1.0,en].png index 0d78d7f0ef..f5165db0f4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c428774b6365a2a17fe497f9aaa1de8b4ce7ad5ef907dc2ae9aef5cc1dcb5fd -size 46678 +oid sha256:e815c6aa5b0f7ed10ee3d04e8dbb2714475ca3868133a31d6b24e6966e03b813 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_3,NEXUS_5,1.0,en].png index 16f614f322..aea58df185 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:182f3e1fa1ccce51a718b421a405e0768d2069c72357993e02d52082824b2c18 -size 54250 +oid sha256:7770e0eb1a181de8f9005d248b4e26d4397987343021d397ccef2f2a09c71f5a +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_4,NEXUS_5,1.0,en].png index b7a28b64bc..9a8c454cc4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:679e7277ece67229a1506c45ec151b0d12c63a3082b406602bf909e1cedc2d57 -size 51392 +oid sha256:f38f6787a926fa879e83eea7469fc03f237d629e21df7f4796c815fec24a558c +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_5,NEXUS_5,1.0,en].png index f325cd808d..9a323c65cf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:522318ca0416b6b2d440c3282ff1f93b593225b313c85b73c1bdaba4f55759f2 -size 53556 +oid sha256:5ab8bba0ea41d78157dcc8202223af3a3d2a14d78b0d3200b84d33ea61fb5e60 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_6,NEXUS_5,1.0,en].png index f325cd808d..9a323c65cf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:522318ca0416b6b2d440c3282ff1f93b593225b313c85b73c1bdaba4f55759f2 -size 53556 +oid sha256:5ab8bba0ea41d78157dcc8202223af3a3d2a14d78b0d3200b84d33ea61fb5e60 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_7,NEXUS_5,1.0,en].png index f69babbcbe..59fec2a703 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7059167b4526675b8b4c1c3ed226900df50c46064be87c5ae71801386c1b0f2 -size 54554 +oid sha256:e97b101b64b15b884e232520d4adbe3a44f4149538f51e08b86c0d9d2b581f81 +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_8,NEXUS_5,1.0,en].png index a1e86d2acc..38e7a89396 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_null_RoomDetailsLight--0_0_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc7f8911844eae722d9d858c70524b329e312ade14ac031cc8e7ecb682024c0c -size 53657 +oid sha256:70914a3502f1bf0edf5a210192fe19215645a1260ce27ff8224194861890613e +size 287 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarDark_0_null,NEXUS_5,1.0,en].png index a7e8db9f25..3c87d03c4f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15b339b0c15ecf38f094378e8541fcf417fcab80291db47b061e05f12cf9663a -size 10567 +oid sha256:b967a0dc3bd9a466790503dd49c2fff6ce70156b51603022916aa9db3fc5c372 +size 29976 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarLight_0_null,NEXUS_5,1.0,en].png index 438d20a0ba..0d8fd2c681 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_DefaultRoomListTopBarLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da7b416757518cff72cc5bb37b670855fe22119082201871f6fea0bb620a348c -size 10419 +oid sha256:8ec89c6b12c4eb1f8059d9d0d183f6635f5647393134c858720f3ef72b8c4c36 +size 36837 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png index 045521cfc7..1af63ca5ba 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5586f03088e8571e1f2921721e23aa4415b8254a5785d0fe76a600de57c5fefb -size 12102 +oid sha256:701e8c9761795b0d7b31d2e08afe502a56882d7a6ec6b61db1b1c13c59471b9a +size 11864 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png index cebcc7ec15..5d349ba526 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fada88c1a80d3bc00a8dc88ba502744d151dfb9af014d3efea3f3d7500641f83 -size 9376 +oid sha256:329326c10ecb636896916019d113e777cb606d1ee1ded5578b7dbfbc38e0189e +size 9143 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png index abd40193da..5d673a1cff 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0abb3a07dc5283ae1411151caa4b33dfc4f153f70aa0cfe6a9287d47763097de -size 12479 +oid sha256:e710857b4a1c3da12ceb51e7400229d0fd7f42d6d6158a07e2c2814c728abfdc +size 12229 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png index bece04c505..d0ec7b4cb1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf7e89a8bb33b5f9d17f04c35fc9ac78c362f0576333c82d36aa2929da9e8cdf -size 13376 +oid sha256:db014e76a88015053b1be458ac758dde99f8cf01a7a78850c0bf6ff4db27dcbc +size 13173 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png index d86cb78f0c..606a183dce 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d16f977abe60656440752e82da244ada6de9dd2ce329679e7c21d4c03df4d306 -size 13710 +oid sha256:4d062feda6fc808aa87df5a5f4b3229153f7e50925e3c55a8c8a99a583212857 +size 13458 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png index 7010d8c333..7078257343 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbc96b4dc345f95d4cee8b2840431ac203e3ad43dc2cf3d8dce688dd4cb28ce6 -size 22111 +oid sha256:79b24ca5c6426aa966024d79bdbbc4a612169037cf6bda7496299ea298fa7517 +size 21951 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png index f841f89d1e..d80e1cc282 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a09f04c04925b55b268f16a95ddb9ecadce377ae3bd593bb370488268b230b1 -size 11873 +oid sha256:fde0cc6b7268856b611db66b66b22c51df155f605988fc54f5c7ba18e5f1f713 +size 11456 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png index af204846e7..73ca4a2eb7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63cf90f5dd21d3267bc1aa003861cbd375006176c7ab20285905caddbfb59e9e -size 9228 +oid sha256:ca1cb753204502aa929f52e8b222b86bd845086f841fb9ff2936886e1dcea526 +size 8778 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png index 23e410fcfa..e471eae254 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ac700080f4b413f4b7ad1fdcefb5c6c7ce2fb0086f76c01e95946d77641f595 -size 12254 +oid sha256:9c9b33e480bfad9c55d55e25cef2a0ab1f25235382e1b53c93107a75e95fa90e +size 11840 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png index b9cf9a3e68..582d30fdfc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:145e9daa38533395e411299d9aa2ff09852a8e474e94df20efcb24415934a8bc -size 13215 +oid sha256:824e708f277ea66a161f17cb07a3070e03ddcfc326b9dd73d0ad6b7e62da32c1 +size 12807 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png index da811477a2..0de6b64bec 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa5d7338831e74563c8d93c1627dd1236c511765c1049100370e3d0be2dc6e76 -size 13690 +oid sha256:39b7b8354b98bca791698ad65f4e21619136f446b2fd050c302bdd27ce138a4a +size 13263 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png index c9f2fe1d4b..fcf8b20fdc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8605388c16b1099f10b8bfccde3f5c84669fb252951425d56c2ac5d95ecff5e -size 22492 +oid sha256:64e7ca21b0213413ed45d0640295ceb2764b01b2bce20a01518e55e620925086 +size 22193 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_ContentTo_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_ContentTo_0_null,NEXUS_5,1.0,en].png index c2f594936c..976af63965 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_ContentTo_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_ContentTo_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:519b00c507122ea88d0a43b9774faffba08bf9008208a8cb2e63c7fc4c7c0395 -size 30497 +oid sha256:0a7a00ebee01aea35dd50cfa5155f6ff58efb21fdf62efb934bf70411563d7cc +size 29908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentDark_0_null,NEXUS_5,1.0,en].png index 4d21322aa4..5d23ac3205 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccb53997d492a88ccd952a7b9e4832ef697b78853b5f10c5c21b3d2a3100ec3e -size 29744 +oid sha256:392c865a3d45c0ab5270241d31159075edfe020526b0fbe39593e46b29da958b +size 29819 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentLight_0_null,NEXUS_5,1.0,en].png index c2f594936c..976af63965 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_null_RoomListSearchResultContentLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:519b00c507122ea88d0a43b9774faffba08bf9008208a8cb2e63c7fc4c7c0395 -size 30497 +oid sha256:0a7a00ebee01aea35dd50cfa5155f6ff58efb21fdf62efb934bf70411563d7cc +size 29908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_0,NEXUS_5,1.0,en].png index eba7c38ad3..dc92a3a311 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b9ffcde3ac2129ae6bde0cf165b47ec42fb7c436140b5da87ccdfd203076a41 -size 35563 +oid sha256:71c2ac04d7fb904b648fc383f430e3b8a4003e93e5964b60eb545248186cd8dc +size 53912 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_1,NEXUS_5,1.0,en].png index 87a72dfed2..b8c2394645 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3696cfbb10e89433efbf86d8b3c5dbfa4f3110e4b348811f48b73355aec7ba8 -size 58615 +oid sha256:3277b51e1a7153487e2d9764d5c495419f8df7fa635574302237ebf3549a4b3c +size 76821 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_2,NEXUS_5,1.0,en].png index eba7c38ad3..dc92a3a311 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b9ffcde3ac2129ae6bde0cf165b47ec42fb7c436140b5da87ccdfd203076a41 -size 35563 +oid sha256:71c2ac04d7fb904b648fc383f430e3b8a4003e93e5964b60eb545248186cd8dc +size 53912 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_3,NEXUS_5,1.0,en].png index 034f8eef9d..d6981eb457 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:113f877cb441ed9960785eb6b6ff37ed876f50e2a82e232d43130de50e171e90 -size 37464 +oid sha256:0104fa9aca0b3d25f06225f54529e88d78f7d42aabd102b2c065c774427bbf3b +size 55038 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_4,NEXUS_5,1.0,en].png index 9d2b285afe..efe91c2960 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19b85235a764b18aab53b00e75f7513ab357cb8876d77912ba32c73fbeaf2762 -size 36848 +oid sha256:c2bf9d1733fc7230816cbcfbcef6780d34e687a064c289f2f9d651cf3047a1bd +size 55107 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_5,NEXUS_5,1.0,en].png index e5cfd4c10f..f280592154 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88afc93c92cf92ac492a82b753c53fde12e34b2e12cee8a67c40570388f33569 -size 37201 +oid sha256:a3cdb5de68791a0e5425ff9a29eaa8c1dc09aea9448211b9ad32f136bf11ace7 +size 55418 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_7,NEXUS_5,1.0,en].png index 4d21322aa4..5d23ac3205 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewDark_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccb53997d492a88ccd952a7b9e4832ef697b78853b5f10c5c21b3d2a3100ec3e -size 29744 +oid sha256:392c865a3d45c0ab5270241d31159075edfe020526b0fbe39593e46b29da958b +size 29819 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_0,NEXUS_5,1.0,en].png index b936e1b8e4..aa486dbf8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b79fdca5780e49319367b0a9d8d965e4d8b290313ac68ae9035ef781bd55a49f -size 38247 +oid sha256:3a9958e430c07445b9dcdc31a483c7925e2bd2a0d3fbbaf1ee5482051e2afe51 +size 65488 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_1,NEXUS_5,1.0,en].png index e4b351db01..4ae4e14d2e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd7d4fe0a126ea90cde648ac9f461f1ffb715737330038a7b31f944be3814ccc -size 62183 +oid sha256:c238218a16f7dc5c25125177434a8397821d5881971dd0404b9d79b7b181a592 +size 89294 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_2,NEXUS_5,1.0,en].png index b936e1b8e4..aa486dbf8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b79fdca5780e49319367b0a9d8d965e4d8b290313ac68ae9035ef781bd55a49f -size 38247 +oid sha256:3a9958e430c07445b9dcdc31a483c7925e2bd2a0d3fbbaf1ee5482051e2afe51 +size 65488 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_3,NEXUS_5,1.0,en].png index cb279871dd..b12ac33325 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:684de1bfd46b540064668dfa54179705f8b3a0ef2ed2b34cdf17c5dafddd510d -size 40281 +oid sha256:8176884d48589815521a35465f564858b4082b523f905e8d4cf7cbc96e25ac5e +size 65496 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_4,NEXUS_5,1.0,en].png index 57dac5e082..1f5a18bf95 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0649ff24ba1b1efb8a310202e6bc111db19defcee2d86fedb0c7aa7ee860a8c0 -size 39566 +oid sha256:86f43007158129ed8e4a637df2db4044bdeb9d504e2a5c40da957bedea9ff395 +size 66618 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_5,NEXUS_5,1.0,en].png index b8bae6649c..f8847daf13 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35e68fda7b2beeab9e716f10fa9a1160af7a427354e8809d7270740047e3f0ae -size 39930 +oid sha256:4543927c7e26cd464d28d3fe4dffda79479bcc8835d82088863031775bf6262e +size 67004 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_7,NEXUS_5,1.0,en].png index c2f594936c..976af63965 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_null_RoomListViewLight_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:519b00c507122ea88d0a43b9774faffba08bf9008208a8cb2e63c7fc4c7c0395 -size 30497 +oid sha256:0a7a00ebee01aea35dd50cfa5155f6ff58efb21fdf62efb934bf70411563d7cc +size 29908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_0,NEXUS_5,1.0,en].png index afc4d7c496..b0851b0bde 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a33d4b4ed08044b8f539914da03e0228e121c1f82f3b1f0acfe34f22dfd4f47 -size 17915 +oid sha256:877bec872499d1a1f15b50c95c507be6233a2ea121cf3429e255b3bde66c1d7b +size 18331 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_1,NEXUS_5,1.0,en].png index e0079e5e3a..3cf0d12761 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0712f3a8e86eb5ba2588a03fe79bb4e29e2c6d341f80906d0b39301c694d054 -size 17361 +oid sha256:84da3f320738e96b2c90724b278b0b2538e55f4089753f10e4913f7f759fd1c3 +size 17533 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_10,NEXUS_5,1.0,en].png index 97ba3cfdc1..56cb2f3f4d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65aef36778adb865c6320a13e6a2bb297cd8837079adab8524d656266ccc48d1 -size 19095 +oid sha256:385ccb31d2b5a286e2bc4db603d0332295fb6f31841e59440f042c99c5c3d5d5 +size 18837 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_12,NEXUS_5,1.0,en].png index 2083ced12f..86f5cac7e8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2a0967d18a75294f09aafd3b116c270b5c88d9e49a2918f6229a9d6f97cf099 -size 20277 +oid sha256:ed8573768b874987b1a313482c9dd38695711ab8e67e81c2cc263da8236e9dc1 +size 19998 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_13,NEXUS_5,1.0,en].png index cf6ea92cff..3cbd9f9818 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_13,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_13,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc3d57b23d0665737f57a2aa6da5133f08410ee12a9fa9112334357960889e3d -size 19021 +oid sha256:7e8e8e31b6f3b43b53d43ec0e067e1f554d2b287909425af625f3c3a187d3b57 +size 18571 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_15,NEXUS_5,1.0,en].png index a41f70f1e1..2c47cc04a5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_15,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_15,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f1f23fc00e55e3ed3f185b931274656e23b5307b238c02188baf5a994d8ef69 -size 22917 +oid sha256:fd6f03742a1840a794f6d506a70ab1dbbb5a1e745e054e47dd4d2292983cdaa1 +size 22491 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_16,NEXUS_5,1.0,en].png index 1d8aac11ea..3a9f7dafc8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_16,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_16,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e7a7bc8d46ac765cc79ebb456d789cd919a3b5270d0d682b6964d329525b1f9 -size 20845 +oid sha256:c9d94151ec126ccd1780326a56cce74bcd2a6bafb4a39ff06d6787c9be078d3a +size 20049 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_18,NEXUS_5,1.0,en].png index 3aea92db8b..fdc359107b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_18,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_18,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed96f288c1137cd68c524910fa461336694cd867f2533f92adef228b86738fd7 -size 16680 +oid sha256:ce07aa02c49b32690e95f626edf7b9d6152eac078f4bd46a96ccb3c9c9c69859 +size 16531 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_19,NEXUS_5,1.0,en].png index 6ec22f322a..b1d7be7920 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_19,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_19,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5a87ff184c790e5763c6a4249d4f6e3c2a4cd40308c35260578d70b7c6d3db0 -size 15899 +oid sha256:a57d40118355c3b039b915462987b1bc960d9ca501d6c725937112fd8cb0b84b +size 15644 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_2,NEXUS_5,1.0,en].png index d05e5b744b..2b087814f2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9b589643f99328d44a38a263b50c4447df7104a6fc0494dd80bbb2920752cb1 -size 19519 +oid sha256:ce86a5fe5ee55b411bd1f76b59e12baafb0cbc963251c9e58d047b1a2a200331 +size 20274 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_21,NEXUS_5,1.0,en].png index 9d2ba67cf8..d10bba8289 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_21,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_21,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9603c41811649cf758e94bd229ed2fbd6eaada6648adf37b7acc4bb0def80665 -size 19586 +oid sha256:c0593fb2b66b37bfd2abc3b4c5cc44a9fd808bb192ab8c972998d424c30aca36 +size 19310 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_22,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_22,NEXUS_5,1.0,en].png index 9c3ef31c21..6868bc07ad 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_22,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_22,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84c1d472d23a09383dbeae496c9d2fe54aba37a1ff93e08da0bb4b419242e505 -size 18320 +oid sha256:c0bfae52f5a63263322d31c60a63f8715afd8b1846bc1cb264bcb1f5629927d2 +size 17871 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_24,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_24,NEXUS_5,1.0,en].png index 779efb9dda..7d870f1ce4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_24,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_24,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8853a6320f11de9e77c115afd576a1fefec9b72086b9c818cf7a83d98bb3902 -size 20512 +oid sha256:4f43a9df02fb5e54b9a0cba1e4975c5029dfa5e71ed14efb0e435dc8c27617b6 +size 20213 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_25,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_25,NEXUS_5,1.0,en].png index 144b992b4d..23724b3516 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_25,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_25,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00c21652a153330deeb12bbcd95968ef9032cfff38f9d6b325d7d98de6fbb7b4 -size 19256 +oid sha256:6747bc029d66697c13e7ec048020ad77391dfac6a60ca30d1e01fe3fd8a53beb +size 18784 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_27,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_27,NEXUS_5,1.0,en].png index ce9cb0fcae..5f02a6a11d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_27,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_27,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:848bd4e8f7954582d803715d7b04a7dca6888c32c5ac0843e8a6880d1c99bd61 -size 16255 +oid sha256:451a6870ceebaafc86252e72c71c96aebfa92a70dcc0516946c0879850f530da +size 16135 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_28,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_28,NEXUS_5,1.0,en].png index 9579b215da..cd8e301f95 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_28,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_28,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4f8280513fd8b8f950511389e0cbdd8d1d45a78b5eb5b17592b95ff05b68efe -size 15577 +oid sha256:7abb38bc4f7c9fca7875d9b5c8c7674c4c2b24c6cae6a122d833cd63ff16a0fe +size 15344 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_3,NEXUS_5,1.0,en].png index b4b7bade0a..4b307b5a87 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f48d8ed65f7b190c5bd0ef69b07da3e8388697a04d2598aa642cf57494d762c -size 23333 +oid sha256:c6c2991436f603bc75c9838008c30201ae3dade211852bfc713c8c2ab9d795d9 +size 22929 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_30,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_30,NEXUS_5,1.0,en].png index 23d4f42f4f..2ca5005092 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_30,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_30,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:744e8704616c4792d19f4b89b8fdd1ebab7f95c155344edcee839383f87d0297 -size 17063 +oid sha256:c61707b4444693c7e4d58edeec7bb32de1bcaeb76e03a806a883eea7685a9bf0 +size 16962 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_31,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_31,NEXUS_5,1.0,en].png index a8e354900d..a510b71d5c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_31,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_31,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bec608ddf547a8436f7f2c7e707409d9a5375010a60f97228068b7255c78cc6f -size 16409 +oid sha256:8a637c8d265e3cd9c8ff0e8e804e46c415338bdbb4bc842c81bf5ef31146d53e +size 16177 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png index 9655b03e8e..df93febb6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_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:8b84b5f310a6d04511e06208eb638dfc344f0629c6cae50644a1d925e286917b -size 20737 +oid sha256:748a2562d8c3e9b5e4def3b8ea3c782868e56f449de1b1ddb4bc36cd84945318 +size 20642 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png index 2f81f23152..f97e401957 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_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:56857d24261e0385855825f310c1e76c4eb244957d6afc48657794231b2041ec -size 20096 +oid sha256:9a6ce0c3b6fe3ac503ee7019dee234a9f96552e1106fe6858030a7c70c506d4f +size 19869 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png index 3f27b667aa..d21aa10955 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_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:5ba04160e69b6a05b81f121f64c375809006bf77899be22e3e4fe07a627921b3 -size 18853 +oid sha256:0c49437c9f943cf044ec64a0ecab2b70d1fa811a34aff4baa8ed0ed846a4f5c5 +size 18595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png index 42da6e6b20..726d7087b0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_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:b30a0deed825c98ef6be90b0b11485a76547a77959058ed816b133f9190b4f6c -size 17694 +oid sha256:063cdf0bbdfc846d3040af143f79a0b6eb325beab62cba5657236d028db6bc26 +size 17267 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png index 707c54ca65..38c479cfdc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_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:7e4cc6b4362468eb094d478cf613b272cfe4b55db4be989e33e56d12dd886672 +oid sha256:032ed67865d3ad800371e193ea922589f27cf03697e3c91c8d698957befa8d42 size 14590 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_4,NEXUS_5,1.0,en].png index a0d5273229..b109475f03 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e40bf3da586f0ecbfd3794d20fb5b1fc65f4b662aab920fd417a40c17c48326f -size 21308 +oid sha256:c5b4dbf61e9b79fa1a884941cc5b92b71b13287109d28837a7fe76fd9c4286db +size 20533 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png index 24b67b4efc..8152088666 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_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:0971b7af9ec67b0c6ba30dd21a21e9ca780c87862499135afa104ffbbfb70fbd -size 14289 +oid sha256:54b00e4382ea77eb90e6de983ec1b363b504456cebd1754946759fd2ee1e9b85 +size 14232 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.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_null_Avatars_Avatar_0_null_42,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_null_Avatars_Avatar_0_null_42,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_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_null_Avatars_Avatar_0_null_43,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_null_Avatars_Avatar_0_null_43,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_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_null_Avatars_Avatar_0_null_44,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_null_Avatars_Avatar_0_null_44,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_null_Avatars_Avatar_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_6,NEXUS_5,1.0,en].png index a957af4444..6d21931fa0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7653f42cb5eaaaebcd81d3ccedcfdaebc31d8ea228f7c28d0faf0e288b3d7e1e -size 18762 +oid sha256:250b9abfa2b27ea622f43a7d553ee6323eb44afa2288ebc110c0ce2b192f40b0 +size 18504 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_7,NEXUS_5,1.0,en].png index 0296201a02..217c6fc156 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bef3f8dabcaf1e4143e3b9f9b965f452d291704dc37b33f0653c1b85c17037af -size 17614 +oid sha256:a87e06ec084f377cc608790d326adced33100f8821162ca28bab24856f7ee52d +size 17176 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_9,NEXUS_5,1.0,en].png index 60bc9b48ee..b886f9140e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3675b41c8caf7069d18e31a5f4a45d0bbb89ec6c468475e39441bd6108cba11b -size 19875 +oid sha256:73356882450cd90a01b8056379848967d3bea1e640ee4e901d9d21a1e823054d +size 19753 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_UserAvatar-D_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_UserAvatar-D_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5c14f662d3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_UserAvatar-D_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85c3b45953da8370bf043b3bfee87304b1c1703e11722c7c796a9e8cf5e84de6 +size 41413 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_UserAvatar-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_UserAvatar-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..acf8d2793c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_UserAvatar-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ab02c533d9866bb6e6ac9b6d732c0d81cfcd080e1321df2834937e6274c9363 +size 40896 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2c9be4925e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a2f25931e9de2edbcc29d149fcb656683d1ff30757b9acde43e058f214d8afc +size 70873 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a6024a3965 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52d6fc6f88ce1e80dfe48ad83476e70afa5b5499ec24e8a94bbbc4b0e611e16d +size 76231 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..51aed9c9e7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4611148bcc9140bf40a63859ec88bac212a9774d81a5fe5abcfea9afec4192bf +size 75130 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0860041efc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39473b8fb90fcef316df257c4b3a45aa9c441199ef169b275e660d58d7fae52b +size 72606 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b5b50230ac --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:572db9d4c1796ec37e113907d5c13728cb87ce9959d94bb9396d7ea38914e52b +size 76355 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c7f31854cb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ca9d811ba70d8642e4831a1d62b94960c79fb28f93a3c36d67d2d67c1b376fc +size 74706 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..81a543bd06 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2319d9245e6f218f0b77d419d99ba101eda112184fc70729633006da0a4ee03d +size 74372 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..781cf6f3a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6131175dabf7e1835856d37c37f8cdacaaa7294b44d4676a0bd4d32d4d2a1337 +size 79118 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9f56ea678a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_BloomInitials-N_1_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce7f7219bd9027f0e6f3d8b44a4170b1e058c549cc143fbf63d67eab3e0eee2b +size 69351 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d06c97ace6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bf9d2e16fa86ec87e38fd82ecde90176542057d58729837d8aee73eb4828d12 +size 63936 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f0e507f655 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6575825152b070f8c2f6abe5ab89f5d1426d76df908638f3934c217193f1aac +size 58297 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4f5be56d3f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17d225fbb3e9d7f1a954c0bc94b94cc1d7f131d226347b32c30940ba03991154 +size 67928 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cd121a5eb5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f05a11dcd4174a1784d6c7fcd32154a1064d0a3369b18c857f051ef72c09943 +size 64470 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..053b94e8a0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e6b22c6a7acbdbc77ebad06831318467ad40bdfbe8c56a0a81e8e9d69de4ff0 +size 66145 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a51c3b1758 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29fd768d347c14c0e9f99de9e2c4e341f76850b8447ccd30fb240590bc8cd706 +size 68885 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5328ba5fe0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e18c010bfd1cf93b5b30cf38b58b3cc24d3e1fdb992050786e35004d112563f +size 61822 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..46e82926c4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_BloomInitials_0_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21465f36c39930d08edbe2b099ff0cd2765292ee29a724720f75760066845440 +size 67954 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_Bloom_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_Bloom_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..17ad5a90a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Bloom_Bloom_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b06bbc06573f8b25cb97e6de6c621c2580a29a2b0c688a5e5135e886af524eb8 +size 77557 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_FloatingActionButtons_FloatingActionButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_FloatingActionButtons_FloatingActionButton_0_null,NEXUS_5,1.0,en].png index 9d9156d829..a09bf2aa83 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_FloatingActionButtons_FloatingActionButton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_FloatingActionButtons_FloatingActionButton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5931cf1a6098e1b3011d81e544b65b6612a603ec3f5fc46de55b73a115ed5f8 -size 9692 +oid sha256:63f00a1f3ec151b25e481f9ac389944d457e08b2c5ed21912dbcdbb2b47a579d +size 11510 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png index d56de11ee3..4dc5242c19 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb0428758e6ef33fc1852566606de812234ca5f01a069fe170002e60739b38cb -size 29257 +oid sha256:49f83ea2049468223d654efe71eb572af225fae3563f307ee45b95d220e8fa6e +size 29450 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png index 9ef8e06a2c..03afa4ded3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ac5ec83fe207221f064ba9eb4f33ad34c4df69cd10a42c85e7d10982ebb73b3 -size 27535 +oid sha256:de59712837eaa588f9daaa55983f44bfa39fd9d0fa3b10678b4e04c7b5571f5e +size 27655 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png index a2649e498b..28c78fd115 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d91bcfb40c3ba7ab56323c028dae8b6771f9c4a4134e352c67b3981c68b66d14 -size 29638 +oid sha256:2d91c6228c80d1d38217e557552e18e64494a94d284de8702d07cc3793150406 +size 29177 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png index 96c0c46024..f1abda92fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableMatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f5b9648f35fb2e7e5832d46fd7d5abf93cd3c0cc7a272b27c6fa23f1361da2f -size 29382 +oid sha256:eeb5b50675e186318458d0900b2d3872ec52710df8e25ba7b633a4be7e13c8da +size 28196 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png index e3db6e41f1..a65a5470e1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_CheckableUnresolvedUserRow_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f1802db5efefe0117383026cfb5310c8b8d391a15013387be6be8f6aa43bf05 -size 116202 +oid sha256:fd6796e3461493cde5e861a05dfccd8bf40e1ceebb66273d1c5486a7a96684b1 +size 116160 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_0,NEXUS_5,1.0,en].png index 8bb0a53a37..c53281b9c1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9048da25ad83e544de8dfa2ec910b7650e34c78a0864e0e1f9644fa18a0ffba8 -size 13144 +oid sha256:115262f8a16f26506139c7b8f630e85520639ae5dca424028bc9a721942aebc5 +size 13168 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_1,NEXUS_5,1.0,en].png index 8e68fdc6c1..658eb46874 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58660c588499291f198046058bf725844bde74a7db4c371d29ee43fba2780e04 -size 12356 +oid sha256:5055b4cc1bdf5a4b00e901e5e40c0cdcc415d22d7154ef38888936928100928e +size 12365 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_0,NEXUS_5,1.0,en].png index 754ea0cecb..85665e2302 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d4ee26a608ba57aa6e9eca05d4db22a9135243a73faf767cdc10ff0252079a2 -size 12887 +oid sha256:1c4ce3a10a671acfb2e619356cf792b5f11325a55edca30c57813aa7823e878d +size 12591 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_1,NEXUS_5,1.0,en].png index 05e82479e2..119c55ec0b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserHeaderLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8daffe981e83c5a81b60ec1d00d09a12fb3a67d3bd957bab5b0b924480330bfe -size 12952 +oid sha256:28f315ebf657e95e666b6599b096d1779b1c9f3cefea3e679a9054d80f89d53b +size 12412 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png index e333bab625..2e089c96eb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd957f1da256db3c598e38d2d6221738ec3feec9124ad00ae6a48ad20e814418 -size 11200 +oid sha256:3c174381b49b1d3d4043164db6621867193576e3b90cc7ff1c5eddf7244074f8 +size 11249 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png index 6bc076c6e2..8c97c7049c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b7779aec984398ff8fd44dfaf156fc3af31a43073238faa57cd69e2d6ad67cf -size 10648 +oid sha256:8b7ad04703963062b5a6490434a572473582ec7f9fd5f96155f5723b0dfaae2c +size 10693 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png index b92144d166..76b6e0d9ab 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8d6f64ccc3f43f057f6558340fea61ececbe78655c4ec441f0a4ce3cdbae62e -size 10970 +oid sha256:7eb78dcf579ebd555e77fa74a40732458ae11bca79c8244f12e93eee605d9694 +size 10862 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png index 7cf5f228fc..739a9ec9c9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_MatrixUserRowLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ba7e3a152cd236450eb13bd05f4bbb153c38dbc66c4411d2b5e5cfb990d3a2f -size 10997 +oid sha256:20a51ed0238f83b99d849325c254919028c0011616698b042fc1f407841805b8 +size 10713 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomDark_0_null,NEXUS_5,1.0,en].png index 0cbcead563..e008079f3b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53e540fd5760b5df921fee79d6ee5387e8d4a830a100c21c699d973ff63844f2 -size 9336 +oid sha256:9a70b86bbd2db875ab61aabd34084aaebc9c12472e8548817be628bb1980547d +size 9311 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomLight_0_null,NEXUS_5,1.0,en].png index 141d3a059d..0fa91f8ca3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedRoomLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:366b834d60978a9c8fe677660feedcc16dfeb4c725f249d788a8b90740f76e9a -size 9128 +oid sha256:5d725ba73157dbb895b34594538ebc149765db558d87a463e7ec24b424bac4da +size 8791 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserDark_0_null,NEXUS_5,1.0,en].png index 7669bcfbf5..6cc609e8ec 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66ee0c1c8a79faa3067e4fb8c2349c459d9a26db099ed06f9050f6498705d93c -size 9503 +oid sha256:0046609fce701f35dd8a81cca93aba806782924a893919a6582bccc019899d3b +size 9467 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserLight_0_null,NEXUS_5,1.0,en].png index ebcee9e7cd..e766c6c946 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUserLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4a756b6fcfa482996055448296f06aed6aaca2849c116692e4a8f2f8c2b455a -size 9173 +oid sha256:2b00ab54a071453b87e3243bc8bc2f07520af89864c3776c112b23748e2e6e5e +size 8965 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListDark_0_null,NEXUS_5,1.0,en].png index 425afced51..46ab9e4895 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e71b7329a2f6f4763f2062e0aa4206c62848f566e210eac3fb2787fc53cd08ac -size 74783 +oid sha256:ebb4d2e8a884af7bbded12fcee2b6e74ebaa1c54223566d62cd5e9cdb8f78bed +size 75290 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListLight_0_null,NEXUS_5,1.0,en].png index 637bd77768..ac50871cb5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_SelectedUsersListLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be62026c5821e47b15b8a2d7f03f9c4ea0fe3dbead9ac12c33d23c72b777f3c4 -size 74045 +oid sha256:0d3fa7cc10b81df36f01ef13c39f033d5cda8489bc2226f5358f21fb7ad9801f +size 74098 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_UnresolvedUserRow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_UnresolvedUserRow_0_null,NEXUS_5,1.0,en].png index 7f3f19bea6..89a8e3dcfd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_UnresolvedUserRow_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.matrix.ui.components_null_UnresolvedUserRow_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffbc6dae4b06297fbc248d7ac7e0972037492ea3e1d3bae74176b08d1616df1e -size 32171 +oid sha256:7beca493206b2dd0c48792e391ae5b980ee6bd5e12ee435dadabaab647b816f6 +size 32239 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-1_2_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 4e97d85b18..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-1_2_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b8c4dfcbfb6c97b9bb8a33df6fd1ef57ef436b2c7e7e9bfa5d6a59fcae2515be -size 14140 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9d906ebe35 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46683edccf9c7686a07d7098c4387f296556ad2678381aad35efe2787cf6ad0a +size 14173 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-1_3_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 4fe86c030b..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-1_3_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4fd3c3f554bd668552863a6f7cd3b43cd15cf4a29b2fd67265e3a9a8a05d4258 -size 13216 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b9d77d2b1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6485de8235af05a9815a558b1af03b51e8c4b30ad0372aea0aa9fb2303525c9b +size 13286 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..760cf75800 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0e4c8da669ee5383a7d0f828a3300b923ec38ab5e04bc388da0e83f1cf1ccf3 +size 38291 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0b9f9f3ab7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f26aa7b13403cfaa864778a6ec5c26edde5932f3d887c7148d3a2b9f60f6e6a +size 36392 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-2_3_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 26c038b0a4..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-2_3_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:049d237aa3e2ba18df462caca17d059fa47e25de9bee8f1fbfd310106a1de07c -size 81319 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..37e8a8561f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e65e15c721a939ed700719cacbdee57e1ccb89d984e88bdb5ed772c4b583472a +size 81494 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-2_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-2_4_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 0e609116be..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-2_4_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d45e4a53b9280b295b731ac1287ea31a8c2b653e0ae5dedd82fd2c0a6c2078ba -size 78323 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..466f321959 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49f6d3b7f93008640abf30a5a65ddca6845e16343ef97c3450faf9dd6d9b9cec +size 78788 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png index ef4d9ab56c..d6beb7c22c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a817f9c6e2d9823dc7f4d20669c80a3cf8f39e6a4468d7dde7159cb7920f20fd -size 35134 +oid sha256:2a998d3db9e4454fe74f31028b1dc61e4d6c07c824192f5e75563234dedf0b26 +size 44108 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png index 9b3ff03b0b..863e8b8628 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a67d75e43fff35eaa3fff9c17245738b16cb7687f6915c1857a2ba85a61037bf -size 33576 +oid sha256:97ab0a0b64ea7704a1557fc34b24d7caea34f9951948f5f5637f0bda596288dd +size 41455 diff --git a/tools/localazy/README.md b/tools/localazy/README.md index b0b5c7e980..2da7afd8bb 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -26,13 +26,15 @@ Never edit manually the files `localazy.xml` or `translations.xml`!. For code clarity and in order to download strings to the correct module, here are some naming rules to follow as much as possible: -- Keys for common strings, i.e. strings that can be used at multiple places must start by `action_` if this is a verb, or `common_` if not; -- Keys for common accessibility strings must start by `a11y_`. Example: `a11y_hide_password`; +- Keys for common strings, i.e. strings that can be used at multiple places must start by `action.` if this is a verb, or `common.` if not; +- Keys for common accessibility strings must start by `a11y.`. Example: `a11y.hide_password`; +- Keys for common strings should be named to match the string. Example: `action.copy_link` for the string `Copy link`; +- When creating common strings, make sure to enable "Use dot (.) to create nested keys"; - Keys for strings used in a single screen must start with `screen_` followed by the screen name, followed by a free name. Example: `screen_onboarding_welcome_title`; - Keys can have `_title` or `_subtitle` suffixes. Example: `screen_onboarding_welcome_title`, `screen_change_server_subtitle`; - For dialogs, keys can have `_dialog_title`, `_dialog_content`, and `_dialog_submit` suffixes. Example: `screen_signout_confirmation_dialog_title`, `screen_signout_confirmation_dialog_content`, `screen_signout_confirmation_dialog_submit`; -- `a11y_` pattern can be used for strings that are only used for accessibility. Example: `a11y_hide_password`, `screen_roomlist_a11y_create_message`; -- Strings for error message can start by `error_`, or contain `_error_` if used in a specific screen only. Example: `error_some_messages_have_not_been_sent`, `screen_change_server_error_invalid_homeserver`. +- `a11y.` pattern can be used for strings that are only used for accessibility. Example: `a11y.hide_password`, `screen_roomlist_a11y_create_message`; +- Strings for error message can start by `error_`, or contain `_error_` if used in a specific screen only. Example: `error_some_messages_have_not_been_sent`, `screen_change_server_error_invalid_homeserver`; *Note*: those rules applies for `strings` and for `plurals`. diff --git a/tools/localazy/config.json b/tools/localazy/config.json index b991581f9b..b92e02f670 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -45,7 +45,7 @@ ] }, { - "name": ":libraries:textcomposer", + "name": ":libraries:textcomposer:impl", "includeRegex": [ "rich_text_editor_.*" ] @@ -119,7 +119,8 @@ "name": ":features:ftue:impl", "includeRegex": [ "screen_welcome_.*", - "screen_migration_.*" + "screen_migration_.*", + "screen_notification_optin_.*" ] }, { diff --git a/tools/release/release.sh b/tools/release/release.sh index 20005d2816..a5701ea5e1 100755 --- a/tools/release/release.sh +++ b/tools/release/release.sh @@ -143,14 +143,14 @@ git commit -a -m "Setting version for the release ${version}" printf "\n================================================================================\n" printf "Building the bundle locally first...\n" -./gradlew clean bundleRelease +./gradlew clean app:bundleRelease printf "\n================================================================================\n" printf "Running towncrier...\n" yes | towncrier build --version "v${version}" printf "\n================================================================================\n" -read -p "Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things. Do not commit your change. Press enter when it's done." +read -p "Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things. Do not commit your change. Press enter to continue. " # Get the changes to use it to create the GitHub release changelogUrlEncoded=`git diff CHANGES.md | grep ^+ | tail -n +2 | cut -c2- | jq -sRr @uri | sed s/\(/%28/g | sed s/\)/%29/g` @@ -168,7 +168,7 @@ fastlaneFile="4${versionMajor2Digits}${versionMinor2Digits}${versionPatch2Digits fastlanePathFile="./fastlane/metadata/android/en-US/changelogs/${fastlaneFile}" printf "Main changes in this version: TODO.\nFull changelog: https://github.com/vector-im/element-x-android/releases" > ${fastlanePathFile} -read -p "I have created the file ${fastlanePathFile}, please edit it and press enter when it's done." +read -p "I have created the file ${fastlanePathFile}, please edit it and press enter to continue. " git add ${fastlanePathFile} git commit -a -m "Adding fastlane file for version ${version}" @@ -200,7 +200,7 @@ sed "s/private const val versionPatch = .*/private const val versionPatch = ${ne rm ${versionsFileBak} printf "\n================================================================================\n" -read -p "I have updated the versions to prepare the next release, please check that the change are correct and press enter so I can commit." +read -p "I have updated the versions to prepare the next release, please check that the change are correct and press enter so I can commit. " printf "Committing...\n" git commit -a -m 'version++' @@ -263,25 +263,32 @@ printf "Version name: " bundletool dump manifest --bundle=${signedBundlePath} --xpath=/manifest/@android:versionName printf "\n" -read -p "Does it look correct? Press enter when it's done." +read -p "Does it look correct? Press enter to continue. " printf "\n================================================================================\n" printf "The file ${signedBundlePath} has been signed and can be uploaded to the PlayStore!\n" printf "\n================================================================================\n" -read -p "Do you want to install the application to your device? Make sure there is a connected device first. (yes/no) default to yes " doDeploy -doDeploy=${doDeploy:-yes} +read -p "Do you want to build the APKs from the app bundle? You need to do this step if you want to install the application to your device. (yes/no) default to yes " doBuildApks +doBuildApks=${doBuildApks:-yes} -if [ ${doDeploy} == "yes" ]; then +if [ ${doBuildApks} == "yes" ]; then printf "Building apks...\n" bundletool build-apks --bundle=${signedBundlePath} --output=${targetPath}/elementx.apks \ --ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \ --overwrite - printf "Installing apk for your device...\n" - bundletool install-apks --apks=${targetPath}/elementx.apks - read -p "Please run the application on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done." + + read -p "Do you want to install the application to your device? Make sure there is one (and only one!) connected device first. (yes/no) default to yes " doDeploy + doDeploy=${doDeploy:-yes} + if [ ${doDeploy} == "yes" ]; then + printf "Installing apk for your device...\n" + bundletool install-apks --apks=${targetPath}/elementx.apks + read -p "Please run the application on your phone to check that the upgrade went well. Press enter to continue. " + else + printf "APK will not be deployed!\n" + fi else - printf "Apk will not be deployed!\n" + printf "APKs will not be generated!\n" fi printf "\n================================================================================\n" @@ -292,15 +299,15 @@ printf "Then\n" printf " - copy paste the section of the file CHANGES.md for this release (if not there yet)\n" printf " - click on the 'Generate releases notes' button\n" printf " - Add the file ${signedBundlePath} to the GitHub release.\n" -read -p ". Press enter when it's done. " +read -p ". Press enter to continue. " printf "\n================================================================================\n" printf "Message for the Android internal room:\n\n" -message="@room Element X Android ${version} is ready to be tested. You can get it from https://github.com/vector-im/element-x-android/releases/tag/v${version}. Please report any feedback here. Thanks!" +message="@room Element X Android ${version} is ready to be tested. You can get it from https://github.com/vector-im/element-x-android/releases/tag/v${version}. Installation instructions can be found [here](https://github.com/vector-im/element-x-android/blob/develop/docs/install_from_github_release.md). Please report any feedback. Thanks!" printf "${message}\n\n" if [[ -z "${elementBotToken}" ]]; then - read -p "ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment. Cannot send the message for you. Please send it manually, and press enter when it's done " + read -p "ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment. Cannot send the message for you. Please send it manually, and press enter to continue. " else read -p "Send this message to the room (yes/no) default to yes? " doSend doSend=${doSend:-yes}